UNPKG

s2-tilejson

Version:

Backwards compatible JSON format for describing s2 map tilesets.

302 lines 9.88 kB
import S2TileJSONSchema from './s2tilejson.schema.json' with { type: 'json' }; import ShapeSchema from './shape.schema.json' with { type: 'json' }; import TileJSONSchema from './tilejson.schema.json' with { type: 'json' }; export { ShapeSchema, TileJSONSchema, S2TileJSONSchema }; /** * List of possible draw types: * 1: points, * 2: lines, * 3: polys, * 4: points3D, * 5: lines3D, * 6: polys3D, * 7: raster, * 8: grid data */ export const DrawType = { /** POINTS = 1 */ Points: 1, /** LINES = 2 */ Lines: 2, /** POLYS = 3 */ Polys: 3, /** POINTS 3D = 4 */ Points3D: 4, /** LINES 3D = 5 */ Lines3D: 5, /** POLYS 3D = 6 */ Polys3D: 6, /** RASTER = 7 */ Raster: 7, /** GRID = 8 */ Grid: 8, }; /** Builder class to help build the metadata */ export class MetadataBuilder { #faces = new Set(); #metadata = { s2tilejson: '1.0.0', version: '1.0.0', name: 'default', scheme: 'fzxy', extension: 'pbf', description: 'Built with s2maps-cli', type: 'vector', encoding: 'none', faces: [], wmbounds: {}, s2bounds: { 0: {}, 1: {}, 2: {}, 3: {}, 4: {}, 5: {} }, bounds: [Infinity, Infinity, -Infinity, -Infinity], minzoom: Infinity, maxzoom: -Infinity, centerpoint: { lon: 0, lat: 0, zoom: 0 }, attributions: {}, tilestats: { total: 0, 0: 0, 1: 0, 2: 0, 3: 0, 4: 0, 5: 0 }, layers: {}, vector_layers: [], }; /** @returns - resultant metadata */ commit() { // set the center this.#updateCenter(); // set the faces this.#metadata.faces = [...this.#faces]; // return the result return this.#metadata; } /** * Set the name * @param name - name of the data */ setName(name) { this.#metadata.name = name; } /** * Set the extension * @param extension - extension of the data */ setExtension(extension) { this.#metadata.extension = extension; } /** * Set the scheme of the data. [default=fzxy] * @param scheme - scheme of the data */ setScheme(scheme) { this.#metadata.scheme = scheme; } /** * Set the type of the data. [default=vector] * @param type - type of the data */ setType(type) { this.#metadata.type = type; } /** * Set the version of the data * @param version - version of the data */ setVersion(version) { this.#metadata.version = version; } /** * Describe the data * @param description - description of the data */ setDescription(description) { this.#metadata.description = description; } /** * Set the encoding of each vector tile. [default=none] * @param encoding - encoding of each tile */ setEncoding(encoding) { this.#metadata.encoding = encoding; } /** * Add an attribution * @param displayName - name of the attribution * @param href - link to the attribution */ addAttribution(displayName, href) { this.#metadata.attributions[displayName] = href; } /** * Add the layer metadata * @param name - name of the layer * @param layer - layer metadata */ addLayer(name, layer) { // add layer this.#metadata.layers[name] = layer; // add vector layer this.#metadata.vector_layers.push({ id: name, description: layer.description, minzoom: layer.minzoom, maxzoom: layer.maxzoom, fields: {}, }); // update minzoom and maxzoom if (layer.minzoom < this.#metadata.minzoom) this.#metadata.minzoom = layer.minzoom; if (layer.maxzoom > this.#metadata.maxzoom) this.#metadata.maxzoom = layer.maxzoom; } /** * Add the WM tile metadata * @param zoom - zoom of the tile * @param x - x position of the tile * @param y - y position of the tile * @param llBounds - the lon-lat bounds of the tile */ addTileWM(zoom, x, y, llBounds) { const metadata = this.#metadata; // update tile stats if (metadata.tilestats !== undefined) metadata.tilestats.total++; this.#faces.add(0); this.#addBoundsWM(zoom, x, y); // update lon lat this.#updateLonLatBounds(llBounds); } /** * Add the S2 tile metadata * @param face - face of the tile * @param zoom - zoom of the tile * @param x - x position of the tile * @param y - y position of the tile * @param llBounds - the lon-lat bounds of the tile */ addTileS2(face, zoom, x, y, llBounds) { const metadata = this.#metadata; // update tile stats if (metadata.tilestats !== undefined) { metadata.tilestats.total++; metadata.tilestats[face]++; } this.#faces.add(face); this.#addBoundsS2(face, zoom, x, y); // update lon lat this.#updateLonLatBounds(llBounds); } /** * Update the center now that all tiles have been added */ #updateCenter() { const { minzoom, maxzoom } = this.#metadata; const [minlon, minlat, maxlon, maxlat] = this.#metadata.bounds; this.#metadata.centerpoint = { lon: (minlon + maxlon) >> 1, lat: (minlat + maxlat) >> 1, zoom: (minzoom + maxzoom) >> 1, }; } /** * Add the bounds of the tile for WM data * @param zoom - zoom of the tile * @param x - x position of the tile * @param y - y position of the tile */ #addBoundsWM(zoom, x, y) { if (this.#metadata.wmbounds[zoom] === undefined) { this.#metadata.wmbounds[zoom] = [Infinity, Infinity, -Infinity, -Infinity]; } const bbox = this.#metadata.wmbounds[zoom]; bbox[0] = Math.min(bbox[0], x); bbox[1] = Math.min(bbox[1], y); bbox[2] = Math.max(bbox[2], x); bbox[3] = Math.max(bbox[3], y); } /** * Add the bounds of the tile for S2 data * @param face - face of the tile * @param zoom - zoom of the tile * @param x - x position of the tile * @param y - y position of the tile */ #addBoundsS2(face, zoom, x, y) { if (this.#metadata.s2bounds[face][zoom] === undefined) { this.#metadata.s2bounds[face][zoom] = [Infinity, Infinity, -Infinity, -Infinity]; } const bbox = this.#metadata.s2bounds[face][zoom]; bbox[0] = Math.min(bbox[0], x); bbox[1] = Math.min(bbox[1], y); bbox[2] = Math.max(bbox[2], x); bbox[3] = Math.max(bbox[3], y); } /** * Update the lon-lat bounds so eventually we can find the center point of the data * @param llBounds - the lon-lat bounds of the tile */ #updateLonLatBounds(llBounds) { const [minlon, minlat, maxlon, maxlat] = llBounds; const bounds = this.#metadata.bounds; bounds[0] = Math.min(bounds[0], minlon); bounds[1] = Math.min(bounds[1], minlat); bounds[2] = Math.max(bounds[2], maxlon); bounds[3] = Math.max(bounds[3], maxlat); } } /** * If you're not sure which tilejson you are reading (Mapbox's spec or S2's spec), you can treat * the input as either and ensure the output is the same * @param metadatas - the S2 TileJSON or Mapbox TileJSON * @returns - S2 TileJSON */ export function toMetadata(metadatas) { if ('s2tilejson' in metadatas) { return metadatas; } else { const [lon, lat, zoom] = metadatas.center ?? [0, 0, 0]; return { ...metadatas, s2tilejson: '1.0.0', version: metadatas.version ?? '1.0.0', name: metadatas.name ?? 'Converted from Mapbox TileJSON to S2 TileJSON', extension: metadatas.tiles?.[0].split('.')[1] ?? 'pbf', scheme: metadatas.scheme ?? 'xyz', description: metadatas.description ?? '', /** The type of the data */ type: 'vector', /** The encoding of the data */ encoding: 'none', /** List of faces that have data */ faces: [0], /** WM Tile fetching bounds. Helpful to not make unecessary requests for tiles we know don't exist */ wmbounds: {}, /** S2 Tile fetching bounds. Helpful to not make unecessary requests for tiles we know don't exist */ s2bounds: { 0: {}, 1: {}, 2: {}, 3: {}, 4: {}, 5: {} }, /** minzoom at which to request tiles. [default=0] */ minzoom: metadatas.minzoom ?? 0, /** maxzoom at which to request tiles. [default=27] */ maxzoom: metadatas.maxzoom ?? 27, /** The center of the data */ centerpoint: { lon, lat, zoom }, /** { ['human readable string']: 'href' } */ attributions: extractLinkInfo(metadatas.attribution) ?? {}, /** Track layer metadata */ layers: {}, /** Old spec, track basic layer metadata */ vector_layers: metadatas.vector_layers ?? [], }; } } /** * Extract href and text from <a href="href">text</a> * @param htmlString - html string to extract href and text from * @returns - { [name]: href } */ function extractLinkInfo(htmlString) { if (htmlString === undefined) return; const hrefMatch = htmlString.match(/href='([^']*)'/); const textMatch = htmlString.match(/>([^<]*)<\/a>/); if (hrefMatch !== null && textMatch !== null) { const hrefValue = hrefMatch[1]; const textValue = textMatch[1]; return { [textValue]: hrefValue }; } } //# sourceMappingURL=index.js.map