open-vector-tile
Version:
This library reads/writes Open Vector Tiles
258 lines • 8.7 kB
JavaScript
import { BaseVectorPolysFeature } from '../base/index.js';
import { OVectorLayer } from '../open/vectorLayer.js';
import { Pbf as Protobuf } from 'pbf-ts';
import { commandEncode, zigzag } from '../util.js';
/**
* Write old schema Mapbox vector tiles with extra features (backwards compatible)
* @param tile - the tile to serialize. Either a BaseVectorTile or a MapboxVectorTile
* @param mapboxSupport - support old school mapbox tooling
* @returns - a Uint8Array of the tile
*/
export default function writeMVTile(tile, mapboxSupport = false) {
const out = new Protobuf();
writeTile(tile, out, mapboxSupport);
return out.commit();
}
/**
* @param tile - the tile to serialize. Either a BaseVectorTile or a MapboxVectorTile
* @param pbf - the Protobuf object to write to
* @param mapbox - support old school mapbox tooling
*/
function writeTile(tile, pbf, mapbox) {
for (const key in tile.layers) {
const layer = tile.layers[key];
if (layer instanceof OVectorLayer)
continue;
pbf.writeMessage(mapbox ? 3 : 1, writeLayer, { layer, mapbox });
}
}
/**
* @param layerContext - the layer to serialize. Either a BaseVectorLayer or a MapboxVectorLayer
* @param pbf - the Protobuf object to write to
*/
function writeLayer(layerContext, pbf) {
const { layer, mapbox } = layerContext;
pbf.writeVarintField(15, mapbox ? 1 : 5);
pbf.writeStringField(1, layer.name ?? '');
pbf.writeVarintField(5, layer.extent ?? 4096);
let i;
const context = {
keys: [],
values: [],
keycache: {},
valuecache: {},
};
const ll = layer.length;
for (i = 0; i < ll; i++) {
const feature = layer.feature(i);
pbf.writeMessage(2, writeFeature, { context, feature, mapbox });
}
const keys = context.keys;
for (i = 0; i < keys.length; i++) {
pbf.writeStringField(3, keys[i]);
}
const values = context.values;
for (i = 0; i < values.length; i++) {
pbf.writeMessage(4, writeValue, values[i]);
}
}
/**
* @param contextWF - the context and feature to write
* @param pbf - the Protobuf object to write to
*/
function writeFeature(contextWF, pbf) {
const { feature, mapbox } = contextWF;
// fix BaseVectorPolysFeature to work with S2
if (feature instanceof BaseVectorPolysFeature)
feature.type = 4;
// if id write it
if (typeof feature.id === 'number')
pbf.writeVarintField(mapbox ? 1 : 15, feature.id);
// properties
pbf.writeMessage(mapbox ? 2 : 1, writeProperties, contextWF);
let featureType = feature.type;
if (mapbox && featureType === 4)
featureType = 3;
pbf.writeVarintField(mapbox ? 3 : 2, featureType);
// geoemtry, indices
pbf.writeMessage(mapbox ? 4 : 3, writeGeometry, contextWF);
if ('indices' in feature && feature.indices.length > 0) {
pbf.writeMessage(mapbox ? 5 : 4, writeIndices, feature.indices);
}
if ('tessellation' in feature && feature.tessellation.length > 0) {
pbf.writeMessage(mapbox ? 6 : 5, writeTessellation, feature.tessellation);
}
}
/**
* @param contextWF - the context and feature to write
* @param pbf - the Protobuf object to write to
*/
function writeProperties(contextWF, pbf) {
const { feature, context } = contextWF;
const { keys, values, keycache, valuecache } = context;
const { properties } = feature;
for (const key in properties) {
let keyIndex = keycache[key];
if (typeof keyIndex === 'undefined') {
keys.push(key);
keyIndex = keys.length - 1;
keycache[key] = keyIndex;
}
pbf.writeVarint(keyIndex);
let value = properties[key];
const type = typeof value;
if (type !== 'string' && type !== 'boolean' && type !== 'number') {
value = JSON.stringify(value);
}
const valueKey = type + ':' + String(value);
let valueIndex = valuecache[valueKey];
if (typeof valueIndex === 'undefined') {
values.push(value);
valueIndex = values.length - 1;
valuecache[valueKey] = valueIndex;
}
pbf.writeVarint(valueIndex);
}
}
/**
* @param indices - the indices of the geometry
* @param pbf - the Protobuf object to write to
*/
function writeIndices(indices, pbf) {
let curr = 0;
for (const index of indices) {
const dCurr = index - curr;
pbf.writeVarint(zigzag(dCurr));
curr += dCurr;
}
}
/**
* just an array of points that are used inside an extent x extent tile block
* @param geometry - the geometry to write
* @param pbf - the Protobuf object to write to
*/
function writeTessellation(geometry, pbf) {
let x = 0;
let y = 0;
for (const point of geometry) {
const dx = point.x - x;
const dy = point.y - y;
pbf.writeVarint(zigzag(dx));
pbf.writeVarint(zigzag(dy));
x += dx;
y += dy;
}
}
/**
* @param featureContext - the feature to use for the geometry to write
* @param pbf - the Protobuf object to write to
*/
function writeGeometry(featureContext, pbf) {
const { feature, mapbox } = featureContext;
const { type } = feature;
const geometry = feature.loadGeometry();
if (type === 1)
writePointGeometry(geometry, pbf);
else if (type === 4)
writeMultiPolyGeometry(geometry, pbf, mapbox);
else
writeLinesGeometry(geometry, type === 3, pbf);
}
/**
* @param geometry - the geometry to encode the points from
* @param pbf - the Protobuf object to write to
*/
function writePointGeometry(geometry, pbf) {
let x = 0;
let y = 0;
for (const point of geometry) {
// move
pbf.writeVarint(commandEncode(1, 1)); // moveto
// store
const dx = point.x - x;
const dy = point.y - y;
pbf.writeVarint(zigzag(dx));
pbf.writeVarint(zigzag(dy));
// update position
x += dx;
y += dy;
}
}
/**
* @param geometry - the geometry to encode
* @param polygon - true if the geometry is a polygon, otherwise its a set of lines
* @param pbf - the Protobuf object to write to
*/
function writeLinesGeometry(geometry, polygon, pbf) {
let x = 0;
let y = 0;
for (let r = 0, gl = geometry.length; r < gl; r++) {
const ring = geometry[r];
pbf.writeVarint(commandEncode(1, 1)); // moveto
// do not write polygon closing path as lineto
const lineCount = polygon ? ring.length - 1 : ring.length;
for (let i = 0; i < lineCount; i++) {
if (i === 1)
pbf.writeVarint(commandEncode(2, lineCount - 1)); // lineto
const dx = ring[i].x - x;
const dy = ring[i].y - y;
pbf.writeVarint(zigzag(dx));
pbf.writeVarint(zigzag(dy));
x += dx;
y += dy;
}
if (polygon)
pbf.writeVarint(commandEncode(7, 1)); // closepath
}
}
/**
* @param geometry - the geometry to encode
* @param pbf - the Protobuf object to write to
* @param mapbox - support old school mapbox tooling
*/
function writeMultiPolyGeometry(geometry, pbf, mapbox) {
let x = 0;
let y = 0;
for (const poly of geometry) {
for (const ring of poly) {
pbf.writeVarint(commandEncode(1, 1)); // moveto
const lineCount = ring.length - 1;
for (let i = 0; i < lineCount; i++) {
if (i === 1)
pbf.writeVarint(commandEncode(2, lineCount - 1)); // lineto
const dx = ring[i].x - x;
const dy = ring[i].y - y;
pbf.writeVarint(zigzag(dx));
pbf.writeVarint(zigzag(dy));
x += dx;
y += dy;
}
pbf.writeVarint(commandEncode(7, 1)); // ClosePath
}
pbf.writeVarint(commandEncode(mapbox ? 7 : 4, 1)); // ClosePolygon (Mapbox does not support so close path if not supported)
}
}
/**
* @param value - the value to write (can be string, number, null, or boolean)
* @param pbf - the Protobuf object to write to
*/
function writeValue(value, pbf) {
if (typeof value === 'string') {
pbf.writeStringField(1, value);
}
else if (typeof value === 'boolean') {
pbf.writeBooleanField(7, value);
}
else if (typeof value === 'number') {
if (value % 1 !== 0) {
pbf.writeDoubleField(3, value);
}
else if (value < 0) {
pbf.writeSVarintField(6, value);
}
else {
pbf.writeVarintField(5, value);
}
}
}
//# sourceMappingURL=writeMVTile.js.map