From c19cdc926742f4f1c0a478526605dcb79b65b050 Mon Sep 17 00:00:00 2001 From: Brandon Liu Date: Tue, 9 Jan 2024 15:20:11 +0800 Subject: [PATCH 1/3] more renames of public_hostname to public_url; update caddy module [#110] --- caddy/Caddyfile | 2 +- caddy/pmtiles_proxy.go | 16 ++++++++-------- main.go | 6 +++--- pmtiles/server.go | 36 ++++++++++++++++++------------------ pmtiles/show.go | 2 -- 5 files changed, 30 insertions(+), 32 deletions(-) diff --git a/caddy/Caddyfile b/caddy/Caddyfile index e996716..cdc3aaf 100644 --- a/caddy/Caddyfile +++ b/caddy/Caddyfile @@ -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 } } } diff --git a/caddy/pmtiles_proxy.go b/caddy/pmtiles_proxy.go index db3a248..e7aeabe 100644 --- a/caddy/pmtiles_proxy.go +++ b/caddy/pmtiles_proxy.go @@ -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. @@ -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 } @@ -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() } } diff --git a/main.go b/main.go index a922aad..0e8e4e4 100644 --- a/main.go +++ b/main.go @@ -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 { @@ -82,7 +82,7 @@ var cli struct { 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"` + 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 { @@ -125,7 +125,7 @@ func main() { logger.Fatalf("Failed to show tile, %v", err) } case "serve ": - 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) diff --git a/pmtiles/server.go b/pmtiles/server.go index 52f049e..672206f 100644 --- a/pmtiles/server.go +++ b/pmtiles/server.go @@ -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() @@ -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 @@ -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") } diff --git a/pmtiles/show.go b/pmtiles/show.go index 8338c0f..c4655a2 100644 --- a/pmtiles/show.go +++ b/pmtiles/show.go @@ -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 { From 2fa5a2762a2b4c37ffd4b53a0dccd7b59633c17b Mon Sep 17 00:00:00 2001 From: Brandon Liu Date: Tue, 9 Jan 2024 15:20:34 +0800 Subject: [PATCH 2/3] TileJSON does not emit null values for optional string fields [#110] --- pmtiles/tilejson.go | 27 ++++++++++++++++++++----- pmtiles/tilejson_test.go | 43 ++++++++++++++++++++++++++++++++++++++-- 2 files changed, 63 insertions(+), 7 deletions(-) diff --git a/pmtiles/tilejson.go b/pmtiles/tilejson.go index 45aa8b8..207a979 100644 --- a/pmtiles/tilejson.go +++ b/pmtiles/tilejson.go @@ -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} diff --git a/pmtiles/tilejson_test.go b/pmtiles/tilejson_test.go index 2af533e..108543b 100644 --- a/pmtiles/tilejson_test.go +++ b/pmtiles/tilejson_test.go @@ -18,6 +18,7 @@ func TestCreateTilejson(t *testing.T) { MaxLatE7: 483000000, CenterLonE7: -1141500000, CenterLatE7: 481000000, + TileType: Mvt, } metadataBytes := []byte(` { @@ -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) @@ -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"]) @@ -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") +} From 98bf7d91b8143de654df84bc23e5f9ed6396cb56 Mon Sep 17 00:00:00 2001 From: Brandon Liu Date: Tue, 9 Jan 2024 15:24:39 +0800 Subject: [PATCH 3/3] fix formatting --- main.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/main.go b/main.go index 0e8e4e4..cca4084 100644 --- a/main.go +++ b/main.go @@ -76,12 +76,12 @@ 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"` + 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."`