Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tilejson fixups #114

Merged
merged 3 commits into from
Jan 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion caddy/Caddyfile
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ localhost:2019 {
cache_size 256

# used to embed a tiles URL in TileJSON.
public_hostname https://localhost:2019/tiles
public_url https://localhost:2019/tiles
}
}
}
16 changes: 8 additions & 8 deletions caddy/pmtiles_proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,11 @@ func init() {

// Middleware creates a Z/X/Y tileserver backed by a local or remote bucket of PMTiles archives.
type Middleware struct {
Bucket string `json:"bucket"`
CacheSize int `json:"cache_size"`
PublicHostname string `json:"public_hostname"`
logger *zap.Logger
server *pmtiles.Server
Bucket string `json:"bucket"`
CacheSize int `json:"cache_size"`
PublicUrl string `json:"public_url"`
logger *zap.Logger
server *pmtiles.Server
}

// CaddyModule returns the Caddy module information.
Expand All @@ -45,7 +45,7 @@ func (m *Middleware) Provision(ctx caddy.Context) error {
m.logger = ctx.Logger()
logger := log.New(io.Discard, "", log.Ldate)
prefix := "." // serve only the root of the bucket for now, at the root route of Caddyfile
server, err := pmtiles.NewServer(m.Bucket, prefix, logger, m.CacheSize, "", m.PublicHostname)
server, err := pmtiles.NewServer(m.Bucket, prefix, logger, m.CacheSize, "", m.PublicUrl)
if err != nil {
return err
}
Expand Down Expand Up @@ -95,8 +95,8 @@ func (m *Middleware) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
return d.ArgErr()
}
m.CacheSize = num
case "public_hostname":
if !d.Args(&m.PublicHostname) {
case "public_url":
if !d.Args(&m.PublicUrl) {
return d.ArgErr()
}
}
Expand Down
18 changes: 9 additions & 9 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ var cli struct {
Bucket string `help:"Remote bucket"`
Metadata bool `help:"Print only the JSON metadata."`
Tilejson bool `help:"Print the TileJSON."`
PublicUrl string `help:"Public URL of tile endpoint to use in the Tilejson output e.g. https://example.com/tiles/pmtiles/{z}/{x}/{y}"`
PublicUrl string `help:"Public base URL of tile endpoint for TileJSON e.g. https://example.com/tiles"`
} `cmd:"" help:"Inspect a local or remote archive."`

Tile struct {
Expand Down Expand Up @@ -76,13 +76,13 @@ var cli struct {
} `cmd:"" hidden:""`

Serve struct {
Path string `arg:"" help:"Local path or bucket prefix"`
Interface string `default:"0.0.0.0"`
Port int `default:8080`
Cors string `help:"Value of HTTP CORS header."`
CacheSize int `default:64 help:"Size of cache in Megabytes."`
Bucket string `help:"Remote bucket"`
PublicHostname string `help:"Public hostname of tile endpoint e.g. https://example.com"`
Path string `arg:"" help:"Local path or bucket prefix"`
Interface string `default:"0.0.0.0"`
Port int `default:8080`
Cors string `help:"Value of HTTP CORS header."`
CacheSize int `default:64 help:"Size of cache in Megabytes."`
Bucket string `help:"Remote bucket"`
PublicUrl string `help:"Public base URL of tile endpoint for TileJSON e.g. https://example.com/tiles/"`
} `cmd:"" help:"Run an HTTP proxy server for Z/X/Y tiles."`

Download struct {
Expand Down Expand Up @@ -125,7 +125,7 @@ func main() {
logger.Fatalf("Failed to show tile, %v", err)
}
case "serve <path>":
server, err := pmtiles.NewServer(cli.Serve.Bucket, cli.Serve.Path, logger, cli.Serve.CacheSize, cli.Serve.Cors, cli.Serve.PublicHostname)
server, err := pmtiles.NewServer(cli.Serve.Bucket, cli.Serve.Path, logger, cli.Serve.CacheSize, cli.Serve.Cors, cli.Serve.PublicUrl)

if err != nil {
logger.Fatalf("Failed to create new server, %v", err)
Expand Down
36 changes: 18 additions & 18 deletions pmtiles/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,15 +39,15 @@ type Response struct {
}

type Server struct {
reqs chan Request
bucket Bucket
logger *log.Logger
cacheSize int
cors string
publicHostname string
reqs chan Request
bucket Bucket
logger *log.Logger
cacheSize int
cors string
publicUrl string
}

func NewServer(bucketURL string, prefix string, logger *log.Logger, cacheSize int, cors string, publicHostname string) (*Server, error) {
func NewServer(bucketURL string, prefix string, logger *log.Logger, cacheSize int, cors string, publicUrl string) (*Server, error) {

ctx := context.Background()

Expand All @@ -63,20 +63,20 @@ func NewServer(bucketURL string, prefix string, logger *log.Logger, cacheSize in
return nil, err
}

return NewServerWithBucket(bucket, prefix, logger, cacheSize, cors, publicHostname)
return NewServerWithBucket(bucket, prefix, logger, cacheSize, cors, publicUrl)
}

func NewServerWithBucket(bucket Bucket, prefix string, logger *log.Logger, cacheSize int, cors string, publicHostname string) (*Server, error) {
func NewServerWithBucket(bucket Bucket, prefix string, logger *log.Logger, cacheSize int, cors string, publicUrl string) (*Server, error) {

reqs := make(chan Request, 8)

l := &Server{
reqs: reqs,
bucket: bucket,
logger: logger,
cacheSize: cacheSize,
cors: cors,
publicHostname: publicHostname,
reqs: reqs,
bucket: bucket,
logger: logger,
cacheSize: cacheSize,
cors: cors,
publicUrl: publicUrl,
}

return l, nil
Expand Down Expand Up @@ -234,11 +234,11 @@ func (server *Server) get_tilejson(ctx context.Context, http_headers map[string]
var metadata_map map[string]interface{}
json.Unmarshal(metadata_bytes, &metadata_map)

if server.publicHostname == "" {
return 501, http_headers, []byte("PUBLIC_HOSTNAME must be set for TileJSON")
if server.publicUrl == "" {
return 501, http_headers, []byte("PUBLIC_URL must be set for TileJSON")
}

tilejson_bytes, err := CreateTilejson(header, metadata_bytes, server.publicHostname+"/"+name+"/{z}/{x}/{y}"+headerExt(header))
tilejson_bytes, err := CreateTilejson(header, metadata_bytes, server.publicUrl+"/"+name)
if err != nil {
return 500, http_headers, []byte("Error generating tilejson")
}
Expand Down
2 changes: 0 additions & 2 deletions pmtiles/show.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,6 @@ func Show(logger *log.Logger, bucketURL string, key string, show_metadata_only b
// Using Fprintf instead of logger here, as this message should be written to Stderr in case
// Stdout is being redirected.
fmt.Fprintln(os.Stderr, "Warning: No --public-url specified; using placeholder tiles URL.")
public_url = "https://example.com/{z}/{x}/{y}.mvt"

}
tilejson_bytes, err := CreateTilejson(header, metadata_bytes, public_url)
if err != nil {
Expand Down
27 changes: 22 additions & 5 deletions pmtiles/tilejson.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,29 @@ func CreateTilejson(header HeaderV3, metadata_bytes []byte, tileUrl string) ([]b

tilejson["tilejson"] = "3.0.0"
tilejson["scheme"] = "xyz"
tilejson["tiles"] = []string{tileUrl}

if tileUrl == "" {
tileUrl = "https://example.com"
}

tilejson["tiles"] = []string{tileUrl + "/{z}/{x}/{y}" + headerExt(header)}
tilejson["vector_layers"] = metadata_map["vector_layers"]
tilejson["attribution"] = metadata_map["attribution"]
tilejson["description"] = metadata_map["description"]
tilejson["name"] = metadata_map["name"]
tilejson["version"] = metadata_map["version"]

if val, ok := metadata_map["attribution"]; ok {
tilejson["attribution"] = val
}

if val, ok := metadata_map["description"]; ok {
tilejson["description"] = val
}

if val, ok := metadata_map["name"]; ok {
tilejson["name"] = val
}

if val, ok := metadata_map["version"]; ok {
tilejson["version"] = val
}

E7 := 10000000.0
tilejson["bounds"] = []float64{float64(header.MinLonE7) / E7, float64(header.MinLatE7) / E7, float64(header.MaxLonE7) / E7, float64(header.MaxLatE7) / E7}
Expand Down
43 changes: 41 additions & 2 deletions pmtiles/tilejson_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ func TestCreateTilejson(t *testing.T) {
MaxLatE7: 483000000,
CenterLonE7: -1141500000,
CenterLatE7: 481000000,
TileType: Mvt,
}
metadataBytes := []byte(`
{
Expand All @@ -27,7 +28,7 @@ func TestCreateTilejson(t *testing.T) {
"name": "Name",
"version": "1.0"
}`)
tileURL := "https://example.com/tiles.pmtiles/{z}/{x}/{y}"
tileURL := "https://example.com/foo"

// Call the function
tilejsonBytes, err := CreateTilejson(header, metadataBytes, tileURL)
Expand All @@ -46,7 +47,7 @@ func TestCreateTilejson(t *testing.T) {

assert.Equal(t, "3.0.0", tilejson["tilejson"])
assert.Equal(t, "xyz", tilejson["scheme"])
assert.Equal(t, []interface{}{"https://example.com/tiles.pmtiles/{z}/{x}/{y}"}, tilejson["tiles"])
assert.Equal(t, []interface{}{"https://example.com/foo/{z}/{x}/{y}.mvt"}, tilejson["tiles"])
assert.Equal(t, []interface{}{map[string]interface{}{"id": "layer1"}}, tilejson["vector_layers"])
assert.Equal(t, "Attribution", tilejson["attribution"])
assert.Equal(t, "Description", tilejson["description"])
Expand All @@ -58,3 +59,41 @@ func TestCreateTilejson(t *testing.T) {
assert.Equal(t, 0.0, tilejson["minzoom"])
assert.Equal(t, 14.0, tilejson["maxzoom"])
}

func TestCreateTilejsonOptionalFields(t *testing.T) {
header := HeaderV3{
MinZoom: 0.0,
MaxZoom: 14.0,
MinLonE7: -1144000000,
MinLatE7: 479000000,
MaxLonE7: -1139000000,
MaxLatE7: 483000000,
CenterLonE7: -1141500000,
CenterLatE7: 481000000,
TileType: Png,
}
metadataBytes := []byte(`
{
}`)

tilejsonBytes, err := CreateTilejson(header, metadataBytes, "")

// Check for errors
if err != nil {
t.Errorf("CreateTilejson returned an error: %v", err)
}

var tilejson map[string]interface{}
err = json.Unmarshal(tilejsonBytes, &tilejson)
if err != nil {
t.Errorf("Failed to parse the generated TileJSON: %v", err)
}

assert.Equal(t, "3.0.0", tilejson["tilejson"])
assert.Equal(t, "xyz", tilejson["scheme"])
assert.Equal(t, []interface{}{"https://example.com/{z}/{x}/{y}.png"}, tilejson["tiles"])
assert.NotContains(t, tilejson, "attribution")
assert.NotContains(t, tilejson, "description")
assert.NotContains(t, tilejson, "name")
assert.NotContains(t, tilejson, "version")
}