Skip to content

Commit

Permalink
Print a JSON representation of part of the header [#63]
Browse files Browse the repository at this point in the history
* add new CLI commands for writing header and metadata (stubs)
  • Loading branch information
bdon committed Sep 15, 2024
1 parent 5882fe8 commit bb2413c
Show file tree
Hide file tree
Showing 4 changed files with 120 additions and 18 deletions.
26 changes: 19 additions & 7 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,12 @@ var cli struct {
} `cmd:"" help:"Convert an MBTiles or older spec version to PMTiles."`

Show struct {
Path string `arg:""`
Bucket string `help:"Remote bucket"`
Metadata bool `help:"Print only the JSON metadata."`
Tilejson bool `help:"Print the TileJSON."`
PublicURL string `help:"Public base URL of tile endpoint for TileJSON e.g. https://example.com/tiles"`
Path string `arg:""`
Bucket string `help:"Remote bucket"`
Metadata bool `help:"Print only the JSON metadata."`
HeaderJson bool `help:Print a JSON representation of the header information.`
Tilejson bool `help:"Print the TileJSON."`
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 All @@ -51,6 +52,17 @@ var cli struct {
Bucket string `help:"Remote bucket"`
} `cmd:"" help:"Fetch one tile from a local or remote archive and output on stdout."`

WriteHeader struct {
Input string `arg:"" help:"Input archive file." type:"existingfile"`
HeaderJsonFile string `arg:"" help:"Input header JSON (written by show --header-json)." type:"existingfile"`
} `cmd:"" help:"Write header data to an existing archive in-place."`

WriteMetadata struct {
Input string `arg:"" help:"Input archive file." type:"existingfile"`
MetadataFile string `arg:"" help:"Input metadata JSON." type:"existingfile"`
Tmpdir string `help:"An optional path to a folder for tmp data." type:"existingdir"`
} `cmd:"" help:"Write JSON metadata to an existing archive in-place."`

Extract struct {
Input string `arg:"" help:"Input local or remote archive."`
Output string `arg:"" help:"Output archive." type:"path"`
Expand Down Expand Up @@ -122,12 +134,12 @@ func main() {

switch ctx.Command() {
case "show <path>":
err := pmtiles.Show(logger, cli.Show.Bucket, cli.Show.Path, cli.Show.Metadata, cli.Show.Tilejson, cli.Show.PublicURL, false, 0, 0, 0)
err := pmtiles.Show(logger, cli.Show.Bucket, cli.Show.Path, cli.Show.HeaderJson, cli.Show.Metadata, cli.Show.Tilejson, cli.Show.PublicURL, false, 0, 0, 0)
if err != nil {
logger.Fatalf("Failed to show archive, %v", err)
}
case "tile <path> <z> <x> <y>":
err := pmtiles.Show(logger, cli.Tile.Bucket, cli.Tile.Path, false, false, "", true, cli.Tile.Z, cli.Tile.X, cli.Tile.Y)
err := pmtiles.Show(logger, cli.Tile.Bucket, cli.Tile.Path, false, false, false, "", true, cli.Tile.Z, cli.Tile.X, cli.Tile.Y)
if err != nil {
logger.Fatalf("Failed to show tile, %v", err)
}
Expand Down
62 changes: 55 additions & 7 deletions pmtiles/directory.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"bytes"
"compress/gzip"
"encoding/binary"
"encoding/json"
"fmt"
)

Expand Down Expand Up @@ -63,6 +64,23 @@ type HeaderV3 struct {
CenterLatE7 int32
}

// HeaderJson is a human-readable representation of parts of the binary header
// that may need to be manually edited.
// Omitted parts are the responsibility of the generated program and not editable.
type HeaderJson struct {
TileCompression string
TileType string
MinZoom int
MaxZoom int
MinLon float64
MinLat float64
MaxLon float64
MaxLat float64
CenterZoom int
CenterLon float64
CenterLat float64
}

func headerContentType(header HeaderV3) (string, bool) {
switch header.TileType {
case Mvt:
Expand All @@ -80,23 +98,31 @@ func headerContentType(header HeaderV3) (string, bool) {
}
}

func headerExt(header HeaderV3) string {
switch header.TileType {
func stringifiedTileType(t TileType) string {
switch t {
case Mvt:
return ".mvt"
return "mvt"
case Png:
return ".png"
return "png"
case Jpeg:
return ".jpg"
return "jpg"
case Webp:
return ".webp"
return "webp"
case Avif:
return ".avif"
return "avif"
default:
return ""
}
}

func headerExt(header HeaderV3) string {
base := stringifiedTileType(header.TileType)
if base == "" {
return ""
}
return "." + base
}

func headerContentEncoding(compression Compression) (string, bool) {
switch compression {
case Gzip:
Expand All @@ -108,6 +134,28 @@ func headerContentEncoding(compression Compression) (string, bool) {
}
}

func headerToJson(header HeaderV3) HeaderJson {
compressionString, _ := headerContentEncoding(header.TileCompression)
return HeaderJson{
TileCompression: compressionString,
TileType: stringifiedTileType(header.TileType),
MinZoom: int(header.MinZoom),
MaxZoom: int(header.MaxZoom),
MinLon: float64(header.MinLonE7) / 10000000,
MinLat: float64(header.MinLatE7) / 10000000,
MaxLon: float64(header.MaxLonE7) / 10000000,
MaxLat: float64(header.MaxLatE7) / 10000000,
CenterZoom: int(header.CenterZoom),
CenterLon: float64(header.CenterLonE7) / 10000000,
CenterLat: float64(header.CenterLatE7) / 10000000,
}
}

func headerToStringifiedJson(header HeaderV3) string {
s, _ := json.Marshal(headerToJson(header))
return string(s)
}

// EntryV3 is an entry in a PMTiles spec version 3 directory.
type EntryV3 struct {
TileID uint64
Expand Down
40 changes: 40 additions & 0 deletions pmtiles/directory_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,33 @@ func TestHeaderRoundtrip(t *testing.T) {
assert.Equal(t, int32(32000000), result.CenterLatE7)
}

func TestHeaderJsonRoundtrip(t *testing.T) {
header := HeaderV3{}
header.TileCompression = Brotli
header.TileType = Mvt
header.MinZoom = 1
header.MaxZoom = 3
header.MinLonE7 = 1.1 * 10000000
header.MinLatE7 = 2.1 * 10000000
header.MaxLonE7 = 1.2 * 10000000
header.MaxLatE7 = 2.2 * 10000000
header.CenterZoom = 2
header.CenterLonE7 = 3.1 * 10000000
header.CenterLatE7 = 3.2 * 10000000
j := headerToJson(header)
assert.Equal(t, "br", j.TileCompression)
assert.Equal(t, "mvt", j.TileType)
assert.Equal(t, 1, j.MinZoom)
assert.Equal(t, 3, j.MaxZoom)
assert.Equal(t, 2, j.CenterZoom)
assert.Equal(t, 1.1, j.MinLon)
assert.Equal(t, 2.1, j.MinLat)
assert.Equal(t, 1.2, j.MaxLon)
assert.Equal(t, 2.2, j.MaxLat)
assert.Equal(t, 3.1, j.CenterLon)
assert.Equal(t, 3.2, j.CenterLat)
}

func TestOptimizeDirectories(t *testing.T) {
rand.Seed(3857)
entries := make([]EntryV3, 0)
Expand Down Expand Up @@ -171,3 +198,16 @@ func TestBuildRootsLeaves(t *testing.T) {
_, _, numLeaves := buildRootsLeaves(entries, 1)
assert.Equal(t, 1, numLeaves)
}

func TestStringifiedCompression(t *testing.T) {

}

func TestStringifiedExtension(t *testing.T) {
assert.Equal(t, "", headerExt(HeaderV3{}))
assert.Equal(t, ".mvt", headerExt(HeaderV3{TileType: Mvt}))
assert.Equal(t, ".png", headerExt(HeaderV3{TileType: Png}))
assert.Equal(t, ".jpg", headerExt(HeaderV3{TileType: Jpeg}))
assert.Equal(t, ".webp", headerExt(HeaderV3{TileType: Webp}))
assert.Equal(t, ".avif", headerExt(HeaderV3{TileType: Avif}))
}
10 changes: 6 additions & 4 deletions pmtiles/show.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import (
)

// Show prints detailed information about an archive.
func Show(_ *log.Logger, bucketURL string, key string, showMetadataOnly bool, showTilejson bool, publicURL string, showTile bool, z int, x int, y int) error {
func Show(_ *log.Logger, bucketURL string, key string, showHeaderJsonOnly bool, showMetadataOnly bool, showTilejson bool, publicURL string, showTile bool, z int, x int, y int) error {
ctx := context.Background()

bucketURL, key, err := NormalizeBucketKey(bucketURL, "", key)
Expand Down Expand Up @@ -90,11 +90,13 @@ func Show(_ *log.Logger, bucketURL string, key string, showMetadataOnly bool, sh
metadataReader.Close()

if showMetadataOnly && showTilejson {
return fmt.Errorf("cannot use --metadata and --tilejson together")
return fmt.Errorf("cannot use more than one of --header-json, --metadata, and --tilejson together")
}

if showMetadataOnly {
fmt.Print(string(metadataBytes))
if showHeaderJsonOnly {
fmt.Println(headerToStringifiedJson(header))
} else if showMetadataOnly {
fmt.Println(string(metadataBytes))
} else if showTilejson {
if publicURL == "" {
// Using Fprintf instead of logger here, as this message should be written to Stderr in case
Expand Down

0 comments on commit bb2413c

Please sign in to comment.