UNPKG

osm2geojson-ultra

Version:

a faster & more complete OSM & Overpass (either in xml or in json formats) to geojson convertor - 4x faster than xmldom + osmtogeojson in most situations - implemented in TypeScript with txml for XML parsing

246 lines (245 loc) 9.05 kB
import { OsmObject } from "./osm-object.js"; import { Way } from "./way.js"; import { Node } from "./node.js"; import { WayCollection } from "./way-collection.js"; import { LateBinder } from "./late-binder.js"; import { first, pointInsidePolygon, strArrayToFloat } from "./utils.js"; export class Relation extends OsmObject { constructor(id, refElems) { super("relation", id, refElems); this.relations = []; this.nodes = []; this.bounds = undefined; this.center = null; this.ways = []; this.members = []; } setBounds(bounds) { this.bounds = bounds; } setCenter(center) { this.center = center; } addMember(member) { this.members.push(member); switch (member.type) { // super relation, need to do combination case "relation": let binder = new LateBinder(this.relations, (id) => { const relation = this.refElems.get(`relation/${id}`); if (relation) { relation.refCount++; return relation; } }, this, [member.ref]); this.relations.push(binder); this.refElems.addBinder(binder); break; case "way": if (member.geometry) { const way = new Way(member.ref, this.refElems); way.setLatLngArray(member.geometry); way.refCount++; this.ways.push(way); } else if (member.nodes) { const way = new Way(member.ref, this.refElems); for (const nid of member.nodes) { way.addNodeRef(nid); } way.refCount++; this.ways.push(way); } else { let binder = new LateBinder(this.ways, (wid) => { const way = this.refElems.get(`way/${wid}`); if (way) { way.refCount++; return way; } }, this, [member.ref]); this.ways.push(binder); this.refElems.addBinder(binder); } break; case "node": let node = null; if (member.lat && member.lon) { node = new Node(member.ref, this.refElems); node.setLatLng({ lon: member.lon, lat: member.lat }); if (member.tags) { node.addTags(member.tags); } for (const [k, v] of Object.entries(member)) { if (["id", "type", "lat", "lon"].indexOf(k) < 0) { node.addMeta(k, v); } } node.refCount++; this.nodes.push(node); } else { let binder = new LateBinder(this.nodes, (id) => { const nn = this.refElems.get(`node/${id}`); if (nn) { nn.refCount++; return nn; } }, this, [member.ref]); this.nodes.push(binder); this.refElems.addBinder(binder); } break; } } constructStringGeometry(ws) { const strings = ws ? ws.mergeWays() : []; if (strings.length === 0) { return null; } return { type: "MultiLineString", coordinates: strings, }; } constructPolygonGeometry(ows, iws) { const outerRings = ows ? ows.toRings("counterclockwise") : []; const innerRings = iws ? iws.toRings("clockwise") : []; if (outerRings.length > 0) { const compositPolyons = []; let ring; for (ring of outerRings) { compositPolyons.push([ring]); } // link inner polygons to outer containers ring = innerRings.shift(); while (ring) { for (const idx in outerRings) { if (pointInsidePolygon(first(ring), outerRings[idx])) { compositPolyons[idx].push(ring); break; } } ring = innerRings.shift(); } // construct the Polygon/MultiPolygon geometry if (compositPolyons.length === 1) { return { type: "Polygon", coordinates: compositPolyons[0], }; } return { type: "MultiPolygon", coordinates: compositPolyons, }; } return null; } toFeature() { const geometries = []; const polygonFeatures = []; const stringFeatures = []; let pointFeatures = []; let tainted = false; const membersAccountedFor = []; const feature = { type: "Feature", id: this.getCompositeId(), bbox: this.bounds, properties: this.getProps(), geometry: null, }; if (!this.bounds) { delete feature.bbox; } if (this.members.some(({ role }) => role === "outer")) { const outerWayCollection = new WayCollection(); const innerWayCollection = new WayCollection(); for (const { type, ref, role } of this.members) { if (type === "way" && ["inner", "outer"].includes(role)) { const wid = `way/${ref}`; membersAccountedFor.push(wid); const way = this.ways.find((way) => way.getCompositeId() === wid); if (way) { if (role === "outer") { outerWayCollection.addWay(way); } else if (role === "inner") { innerWayCollection.addWay(way); } } else { tainted = true; } } } let geometry = this.constructPolygonGeometry(outerWayCollection, innerWayCollection); if (geometry) { geometries.push(geometry); } } else if (["multilinestring", "route", "waterway"].includes(this.tags.type)) { const wayCollection = new WayCollection(); for (const { type, ref, role } of this.members) { if (type === "way") { const wid = `way/${ref}`; membersAccountedFor.push(wid); const way = this.ways.find((way) => way.getCompositeId() === wid); if (way) { wayCollection.addWay(way); } else { tainted = true; } } } let geometry = this.constructStringGeometry(wayCollection); if (geometry) { geometries.push(geometry); } } for (const { type, ref, role } of this.members) { const mid = `${type}/${ref}`; if (membersAccountedFor.includes(mid)) continue; let obj; switch (type) { case "node": obj = this.nodes.find((node) => node.getCompositeId() === mid); break; case "way": obj = this.ways.find((way) => way.getCompositeId() === mid); break; case "relation": obj = this.relations.find((relation) => relation.getCompositeId() === mid); break; } if (obj) { const feature = obj.toFeature(); if (feature === null || feature === void 0 ? void 0 : feature.geometry) { geometries.push(feature.geometry); } } else { tainted = true; } } if (this.center !== null) { geometries.push({ type: "Point", coordinates: strArrayToFloat([this.center.lon, this.center.lat]), }); } if (tainted) { feature.properties["@tainted"] = tainted; } if (geometries.length === 1) { feature.geometry = geometries[0]; } else { feature.geometry = { type: "GeometryCollection", geometries }; } return feature; } }