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
247 lines (246 loc) • 9.1 kB
JavaScript
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, undefined);
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, undefined);
node.setLatLng({ lon: member.lon, lat: member.lat });
// vvv Is this stuff necessary? vvv
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;
}
}