UNPKG

@loaders.gl/mvt

Version:

Loader for Mapbox Vector Tiles

263 lines 8.52 kB
// loaders.gl // SPDX-License-Identifier: MIT // Copyright vis.gl contributors // This code is inspired by https://github.com/mapbox/vector-tile-js under BSD 3-clause license. import Protobuf from 'pbf'; import * as MVT from "./mvt-constants.js"; import { readBoundingBoxFromPBF, loadFlatGeometryFromPBF } from "./parse-geometry-from-pbf.js"; const DEFAULT_LAYER = { version: 1, name: '', extent: 4096, length: 0, schema: { fields: [], metadata: {} }, columns: {}, idColumn: [], geometryTypeColumn: [], geometryColumn: [], boundingBoxColumn: [] }; const DEFAULT_LAYER_DATA = { currentFeature: 0, keys: [], values: [], types: [], columnTypes: [], columnNullable: [], featurePositions: [], geometryPositions: [] }; /** Parse an MVT tile from an ArrayBuffer */ export function parseMVT(arrayBuffer) { const pbf = new Protobuf(arrayBuffer); return parseMVTTile(pbf); } /** Parse an MVT tile from a PBF buffer */ export function parseMVTTile(pbf, end) { const tile = { layers: {} }; try { pbf.readFields(readTileFieldFromPBF, tile, end); } catch (error) { // eslint-disable-next-line no-console console.warn(error); } return tile; } /** * Protobuf read callback for a top-level tile object's PBF tags * @param tag * @param layers * @param pbf */ function readTileFieldFromPBF(tag, tile, pbf) { if (!pbf || !tile) { return; } switch (tag) { case MVT.TileInfo.layers: // get the byte length and end of the layer const byteLength = pbf.readVarint(); const end = byteLength + pbf.pos; const layer = parseLayer(pbf, end); tile.layers[layer.name] = layer; break; default: // ignore? log? } } /** Parse an MVT layer from BPF at current position */ export function parseLayer(pbf, end) { const layerData = { layer: { ...DEFAULT_LAYER }, ...DEFAULT_LAYER_DATA }; pbf.readFields(readLayerFieldFromPBF, layerData, end); // Read features for (let featureIndex = 0; featureIndex < layerData.featurePositions.length; featureIndex++) { // Determine start and end of feature in PBF const featurePosition = layerData.featurePositions[featureIndex]; pbf.pos = featurePosition; const byteLength = pbf.readVarint(); const end = byteLength + pbf.pos; layerData.currentFeature = featureIndex; pbf.readFields(readFeatureFieldFromPBF, layerData, end); readBoundingBoxesFromPDF(pbf, layerData); readGeometriesFromPBF(pbf, layerData); } // Post processing const { layer } = layerData; layer.length = layerData.featurePositions.length; layer.schema = makeMVTSchema(layerData); return layer; } /** * * @param tag * @param layer * @param pbf */ function readLayerFieldFromPBF(tag, layerData, pbf) { if (!layerData || !pbf) { return; } switch (tag) { case MVT.LayerInfo.version: layerData.layer.version = pbf.readVarint(); break; case MVT.LayerInfo.name: layerData.layer.name = pbf.readString(); break; case MVT.LayerInfo.extent: layerData.layer.extent = pbf.readVarint(); break; case MVT.LayerInfo.features: layerData.featurePositions.push(pbf.pos); break; case MVT.LayerInfo.keys: layerData.keys.push(pbf.readString()); break; case MVT.LayerInfo.values: const [value, type] = parseValues(pbf); layerData.values.push(value); layerData.types.push(type); break; default: // ignore? Log? } } /** * @param pbf * @returns value */ function parseValues(pbf) { const end = pbf.readVarint() + pbf.pos; let value = null; // not a valid property type so we use it to detect multiple values let type = -1; // TODO - can we have multiple values? while (pbf.pos < end) { if (type !== -1) { throw new Error('MVT: Multiple values not supported'); } type = pbf.readVarint() >> 3; value = readValueFromPBF(pbf, type); } return [value, type]; } /** Read a type tagged value from the protobuf at current position */ function readValueFromPBF(pbf, type) { switch (type) { case MVT.PropertyType.string_value: return pbf.readString(); case MVT.PropertyType.float_value: return pbf.readFloat(); case MVT.PropertyType.double_value: return pbf.readDouble(); case MVT.PropertyType.int_value: return pbf.readVarint64(); case MVT.PropertyType.uint_value: return pbf.readVarint(); case MVT.PropertyType.sint_value: return pbf.readSVarint(); case MVT.PropertyType.bool_value: return pbf.readBoolean(); default: return null; } } /** * * @param tag * @param feature * @param pbf */ function readFeatureFieldFromPBF(tag, layerData, pbf) { if (!pbf || !layerData) { return; } switch (tag) { case MVT.FeatureInfo.id: const id = pbf.readVarint(); layerData.layer.idColumn[layerData.currentFeature] = id; break; case MVT.FeatureInfo.tags: parseColumnValues(pbf, layerData); break; case MVT.FeatureInfo.type: const type = pbf.readVarint(); layerData.layer.geometryTypeColumn[layerData.currentFeature] = type; break; case MVT.FeatureInfo.geometry: layerData.geometryPositions[layerData.currentFeature] = pbf.pos; break; default: // ignore? log? } } /** * * @param pbf * @param feature */ function parseColumnValues(pbf, layerData) { const end = pbf.readVarint() + pbf.pos; while (pbf.pos < end) { const keyIndex = pbf.readVarint(); const valueIndex = pbf.readVarint(); const columnName = layerData.keys[keyIndex]; const value = layerData.values[valueIndex]; layerData.columnTypes[columnName] = layerData.types[valueIndex]; layerData.columnNullable[columnName] ||= value === null; layerData.layer.columns[columnName] ||= []; layerData.layer.columns[columnName].push(value); } } // Geometry readers function readBoundingBoxesFromPDF(pbf, layerData) { for (let row = 0; row < layerData.geometryPositions.length; row++) { pbf.pos = layerData.geometryPositions[row]; const boundingBox = readBoundingBoxFromPBF(pbf); layerData.layer.boundingBoxColumn[row] = boundingBox; } } function readGeometriesFromPBF(pbf, layerData) { for (let row = 0; row < layerData.geometryPositions.length; row++) { pbf.pos = layerData.geometryPositions[row]; const flatGeometry = loadFlatGeometryFromPBF(pbf); layerData.layer.geometryColumn[row] = flatGeometry; } } // Schema Builder function makeMVTSchema(layerData) { const { keys, columnTypes, columnNullable } = layerData; const fields = []; for (let i = 0; i < keys.length; i++) { const key = keys[i]; const nullable = columnNullable[key]; switch (columnTypes[key]) { case MVT.PropertyType.string_value: fields.push({ name: keys[i], type: 'utf8', nullable }); break; case MVT.PropertyType.float_value: fields.push({ name: keys[i], type: 'float32', nullable }); break; case MVT.PropertyType.double_value: fields.push({ name: keys[i], type: 'float64', nullable }); break; case MVT.PropertyType.int_value: fields.push({ name: keys[i], type: 'int32', nullable }); break; case MVT.PropertyType.uint_value: fields.push({ name: keys[i], type: 'uint32', nullable }); break; case MVT.PropertyType.sint_value: fields.push({ name: keys[i], type: 'int32', nullable }); break; case MVT.PropertyType.bool_value: fields.push({ name: keys[i], type: 'bool', nullable }); break; default: // ignore? } } return { fields, metadata: {} }; } //# sourceMappingURL=parse-mvt-from-pbf.js.map