s2-tilejson
Version:
Backwards compatible JSON format for describing s2 map tilesets.
302 lines • 9.93 kB
JavaScript
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: {}, 6: {} },
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, 6: 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: {}, 6: {} },
/** 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 An object with `{ [name]: href }` or undefined
*/
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