diff --git a/js/index.ts b/js/index.ts index 9d83c782..b1210aee 100644 --- a/js/index.ts +++ b/js/index.ts @@ -150,6 +150,15 @@ export interface Entry { runLength: number; } +interface MetadataLike { + attribution?: string; + name?: string; + version?: string; + // biome-ignore lint: TileJSON spec + vector_layers?: string; + description?: string; +} + /** * Enum representing a compression algorithm used. * 0 = unknown compression, for if you must use a different or unspecified algorithm. @@ -212,6 +221,15 @@ export enum TileType { Avif = 5, } +export function tileTypeExt(t: TileType): string { + if (t === TileType.Mvt) return ".mvt"; + if (t === TileType.Png) return ".png"; + if (t === TileType.Jpeg) return ".jpg"; + if (t === TileType.Webp) return ".webp"; + if (t === TileType.Avif) return ".avif"; + return ""; +} + const HEADER_SIZE_BYTES = 127; /** @@ -1056,4 +1074,33 @@ export class PMTiles { throw e; } } + + /** + * Return TileJSON 3.0.0: https://github.com/mapbox/tilejson-spec + * + * baseTilesURL is the URL, excluding the suffix /{z}/{x}/{y}.{ext}. + * For example, if the desired tiles URL is http://example.com/tileset/{z}/{x}/{y}.mvt, + * the baseTilesURL should be https://example.com/tileset + */ + async getTileJson(baseTilesUrl: string): Promise { + const header = await this.getHeader(); + const metadata = (await this.getMetadata()) as MetadataLike; + const ext = tileTypeExt(header.tileType); + + return { + tilejson: "3.0.0", + scheme: "xyz", + tiles: [`${baseTilesUrl}/{z}/{x}/{y}${ext}`], + // biome-ignore lint: TileJSON spec + vector_layers: metadata.vector_layers, + attribution: metadata.attribution, + description: metadata.description, + name: metadata.name, + version: metadata.version, + bounds: [header.minLon, header.minLat, header.maxLon, header.maxLat], + center: [header.centerLon, header.centerLat, header.centerZoom], + minzoom: header.minZoom, + maxzoom: header.maxZoom, + }; + } } diff --git a/js/test/v3.test.ts b/js/test/v3.test.ts index 6ad38eb1..079cb60a 100644 --- a/js/test/v3.test.ts +++ b/js/test/v3.test.ts @@ -11,10 +11,12 @@ import { RangeResponse, SharedPromiseCache, Source, + TileType, findTile, getUint64, readVarint, tileIdToZxy, + tileTypeExt, zxyToTileId, } from "../index"; @@ -376,3 +378,40 @@ test("pmtiles get metadata", async () => { }); // echo '{"type":"Polygon","coordinates":[[[0,0],[0,1],[1,0],[0,0]]]}' | ./tippecanoe -zg -o test_fixture_2.pmtiles + +test("get file extension", async () => { + assert.equal("", tileTypeExt(TileType.Unknown)); + assert.equal(".mvt", tileTypeExt(TileType.Mvt)); + assert.equal(".png", tileTypeExt(TileType.Png)); + assert.equal(".jpg", tileTypeExt(TileType.Jpeg)); + assert.equal(".webp", tileTypeExt(TileType.Webp)); + assert.equal(".avif", tileTypeExt(TileType.Avif)); +}); + +interface TileJsonLike { + tilejson: string; + scheme: string; + tiles: string[]; + description?: string; + name?: string; + attribution?: string; + version?: string; +} + +test("pmtiles get TileJSON", async () => { + const source = new TestNodeFileSource( + "test/data/test_fixture_1.pmtiles", + "1" + ); + const p = new PMTiles(source); + const tilejson = (await p.getTileJson( + "https://example.com/foo" + )) as TileJsonLike; + assert.equal("3.0.0", tilejson.tilejson); + assert.equal("xyz", tilejson.scheme); + assert.equal("https://example.com/foo/{z}/{x}/{y}.mvt", tilejson.tiles[0]); + assert.equal(undefined, tilejson.attribution); + assert.equal("test_fixture_1.pmtiles", tilejson.description); + assert.equal("test_fixture_1.pmtiles", tilejson.name); + assert.equal("2", tilejson.version); +}); diff --git a/serverless/aws/src/index.ts b/serverless/aws/src/index.ts index 1322db04..437a1692 100644 --- a/serverless/aws/src/index.ts +++ b/serverless/aws/src/index.ts @@ -12,7 +12,7 @@ import { Source, TileType, } from "../../../js/index"; -import { pmtiles_path, tileJSON, tile_path } from "../../shared/index"; +import { pmtiles_path, tile_path } from "../../shared/index"; import { createHash } from "crypto"; import zlib from "zlib"; @@ -177,15 +177,7 @@ export const handlerRaw = async ( } headers["Content-Type"] = "application/json"; - const t = tileJSON( - header, - await p.getMetadata(), - process.env.PUBLIC_HOSTNAME || - event.headers["x-distribution-domain-name"] || - "", - name - ); - + const t = p.getTileJson(`https://${process.env.PUBLIC_HOSTNAME || event.headers["x-distribution-domain-name"] || ""}/${name}`); return apiResp(200, JSON.stringify(t), false, headers); } diff --git a/serverless/cloudflare/src/index.ts b/serverless/cloudflare/src/index.ts index c9f15b3b..f909da7c 100644 --- a/serverless/cloudflare/src/index.ts +++ b/serverless/cloudflare/src/index.ts @@ -7,7 +7,7 @@ import { Source, TileType, } from "../../../js/index"; -import { pmtiles_path, tileJSON, tile_path } from "../../shared/index"; +import { pmtiles_path, tile_path } from "../../shared/index"; interface Env { // biome-ignore lint: config name @@ -159,14 +159,9 @@ export default { if (!tile) { cacheableHeaders.set("Content-Type", "application/json"); - - const t = tileJSON( - pHeader, - await p.getMetadata(), - env.PUBLIC_HOSTNAME || url.hostname, - name + const t = p.getTileJson( + `https://${env.PUBLIC_HOSTNAME || url.hostname}/${name}` ); - return cacheableResponse(JSON.stringify(t), cacheableHeaders, 200); } diff --git a/serverless/shared/index.ts b/serverless/shared/index.ts index 8780e264..d0f6b76b 100644 --- a/serverless/shared/index.ts +++ b/serverless/shared/index.ts @@ -1,5 +1,3 @@ -import { Header, TileType } from "../../js/index"; - export const pmtiles_path = (name: string, setting?: string): string => { if (setting) { return setting.replaceAll("{name}", name); @@ -35,39 +33,4 @@ export const tile_path = ( } return { ok: false, name: "", tile: [0, 0, 0], ext: "" }; -}; - -export const tileJSON = ( - header: Header, - metadata: any, - hostname: string, - tileset_name: string -) => { - let ext = ""; - if (header.tileType === TileType.Mvt) { - ext = ".mvt"; - } else if (header.tileType === TileType.Png) { - ext = ".png"; - } else if (header.tileType === TileType.Jpeg) { - ext = ".jpg"; - } else if (header.tileType === TileType.Webp) { - ext = ".webp"; - } else if (header.tileType === TileType.Avif) { - ext = ".avif"; - } - - return { - tilejson: "3.0.0", - scheme: "xyz", - tiles: ["https://" + hostname + "/" + tileset_name + "/{z}/{x}/{y}" + ext], - vector_layers: metadata.vector_layers, - attribution: metadata.attribution, - description: metadata.description, - name: metadata.name, - version: metadata.version, - bounds: [header.minLon, header.minLat, header.maxLon, header.maxLat], - center: [header.centerLon, header.centerLat, header.centerZoom], - minzoom: header.minZoom, - maxzoom: header.maxZoom, - }; -}; +}; \ No newline at end of file