UNPKG

s2-tools

Version:

A collection of geospatial tools primarily designed for WGS84, Web Mercator, and S2.

256 lines 8.84 kB
import { intermediateRelationToVectorFeature } from './relation'; import { mergeBBoxes } from '../../geometry'; import { DenseInfo, Info } from './info'; /** * Merge an associated relation if it exists * @param feature - the node vector feature * @param reader - the OSM reader */ export async function mergeRelationIfExists(feature, reader) { const { nodeRelationPairs, relations, addBBox } = reader; const { id, metadata } = feature; const pair = await nodeRelationPairs.get(id ?? -1); if (pair === undefined) return; const { relationID, role } = pair; const relation = await relations.get(relationID); if (relation === undefined) return; const { properties } = relation; if (metadata !== undefined) metadata.relation = { role, properties }; if (addBBox) { const relationVectorFeature = await intermediateRelationToVectorFeature(relation, reader); if (relationVectorFeature === undefined) return; feature.geometry.bbox = mergeBBoxes(feature.geometry.bbox, relationVectorFeature.geometry.bbox); } } /** * Node class * contains a single node. */ export class Node { primitiveBlock; reader; id = 1; info; lat = 0.0; lon = 0.0; #keys = []; #vals = []; #properties; /** * @param primitiveBlock - the primitive block to access keys and values * @param reader - the OSM reader * @param pbf - the Protobuf object to read from */ constructor(primitiveBlock, reader, pbf) { this.primitiveBlock = primitiveBlock; this.reader = reader; this.primitiveBlock = primitiveBlock; if (pbf !== undefined) pbf.readMessage(this.#readLayer, this); } /** * Create a node from a dense representation * @param id - the node id * @param info - the node info * @param keys - list of keys * @param vals - list of values * @param lat - the latitude * @param lon - the longitude * @param pb - the primitive block to access keys and values * @param reader - the OSM reader * @returns - the node */ static fromDense(id, info, keys, vals, lat, lon, pb, reader) { const node = new Node(pb, reader); node.id = id; node.info = info; node.lat = 0.000000001 * (pb.latOffset + pb.granularity * lat); node.lon = 0.000000001 * (pb.latOffset + pb.granularity * lon); node.#keys = keys; node.#vals = vals; return node; } /** * Check if the node is filterable * @returns - true if the node is filterable */ isFilterable() { const { primitiveBlock: pb, reader } = this; const { tagFilter, removeEmptyNodes, skipNodes } = reader; if (skipNodes) return true; if (removeEmptyNodes && this.#keys.length === 0) return true; if (tagFilter !== undefined) { for (let i = 0; i < this.#keys.length; i++) { const keyStr = pb.getString(this.#keys[i]); const valStr = pb.getString(this.#vals[i]); if (tagFilter.matchFound('Node', keyStr, valStr)) return false; } // if we make it here, we didn't find any matching tags return true; } return false; } /** * Get the properties of the node * @returns - the properties */ properties() { if (this.#properties !== undefined) return this.#properties; this.#properties = this.primitiveBlock.tags(this.#keys, this.#vals); return this.#properties; } /** * @param tag - the tag of the message * @param node - the node * @param pbf - the Protobuf object to read from */ #readLayer(tag, node, pbf) { const { primitiveBlock: pb } = node; if (tag === 1) this.id = pbf.readVarint(); else if (tag === 2) this.#keys = pbf.readPackedVarint(); else if (tag === 3) this.#vals = pbf.readPackedVarint(); else if (tag === 4) this.info = new Info(pb, pbf); else if (tag === 8) this.lat = 0.000000001 * pb.latOffset + pb.granularity * pbf.readSVarint(); else if (tag === 9) this.lon = 0.000000001 * pb.latOffset + pb.granularity * pbf.readSVarint(); else throw new Error(`Unknown tag: ${tag}`); } /** * Gain access to the nodes geometry * @returns - the vector feature */ toVectorGeometry() { // if feature has altitude or something defining its z position, make feature 3D const { altitude, ele, elevation, height, depth } = this.properties(); const z = parseAltitude(ele ?? height ?? altitude ?? elevation ?? (depth !== undefined ? `-${depth}` : '')); return { x: this.lon, y: this.lat, z }; } /** * Convert the node to a vector feature * @returns - the vector feature */ toVectorFeature() { const { addBBox } = this.reader; const bbox = addBBox ? this.buildBBox() : undefined; const coordinates = this.toVectorGeometry(); return { id: this.id, type: 'VectorFeature', properties: this.properties(), geometry: { type: 'Point', is3D: coordinates.z !== undefined, coordinates, bbox }, metadata: { info: this.info?.toBlock() ?? {} }, }; } /** @returns - the bounding box for this node */ buildBBox() { return [this.lon, this.lat, this.lon, this.lat]; } } /** * Used to densly represent a sequence of nodes that do not have any tags. * We represent these nodes columnwise as five columns: ID's, lats, and * lons, all delta coded. When metadata is not omitted, * We encode keys & vals for all nodes as a single array of integers * containing key-stringid and val-stringid, using a stringid of 0 as a * delimiter between nodes. * ( (<keyid> <valid>)* '0' )* */ export class DenseNodes { primitiveBlock; reader; ids = []; // DELTA coded denseinfo; lats = []; // DELTA coded lons = []; // DELTA coded // Special packing of keys and vals into one array. May be empty if all nodes in this block are tagless. keysVals = []; /** * @param primitiveBlock - the primitive block to access keys and values * @param reader - the OSM reader * @param pbf - the Protobuf object to read from */ constructor(primitiveBlock, reader, pbf) { this.primitiveBlock = primitiveBlock; this.reader = reader; this.primitiveBlock = primitiveBlock; pbf.readMessage(this.#readLayer, this); } /** * Access the nodes in this block * @returns - the nodes in this block */ nodes() { const { primitiveBlock: pb, reader } = this; const res = []; const infoMap = this.denseinfo?.infos(); let j = 0; let curId = 0; let curLat = 0; let curLon = 0; for (let i = 0; i < this.ids.length; i++) { const curInfo = infoMap?.[i]; curId += this.ids[i]; curLat += this.lats[i]; curLon += this.lons[i]; const keys = []; const vals = []; if (this.keysVals.length > 0) { while (this.keysVals[j] !== 0) { keys.push(this.keysVals[j]); vals.push(this.keysVals[j + 1]); j += 2; } j += 1; } const node = Node.fromDense(curId, curInfo, keys, vals, curLat, curLon, pb, reader); res.push(node); } return res; } /** * @param tag - the tag of the message * @param denseNodes - the DenseNodes object * @param pbf - the Protobuf object to read from */ #readLayer(tag, denseNodes, pbf) { const { primitiveBlock: pb } = denseNodes; if (tag === 1) denseNodes.ids = pbf.readPackedSVarint(); else if (tag === 5) denseNodes.denseinfo = new DenseInfo(pb, pbf); else if (tag === 8) denseNodes.lats = pbf.readPackedSVarint(); else if (tag === 9) denseNodes.lons = pbf.readPackedSVarint(); else if (tag === 10) denseNodes.keysVals = pbf.readPackedVarint(); else throw new Error('unknown tag ' + tag); } } /** * @param alt - the altitude to parse * @returns the altitude assuming it is in meters */ function parseAltitude(alt) { alt = alt.replace(/\D/g, ''); if (alt === '') return undefined; // common inputs: 246,62␣m - remove all non-digits return Number(alt); } //# sourceMappingURL=node.js.map