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
188 lines (187 loc) • 9.01 kB
JavaScript
import { parse } from "txml";
import { purgeProps } from "./utils.js";
import { Node } from "./node.js";
import { Output } from "./output.js";
import { Way } from "./way.js";
import { Relation } from "./relation.js";
function setTagsFromXML(elNode, obj) {
for (const elChild of elNode.children) {
if (elChild.tagName === "tag") {
obj.addTag(elChild.attributes.k, elChild.attributes.v);
}
}
}
export default function analyzeFeaturesFromXml(osm, refElements) {
const parsed = parse(osm, { noChildNodes: [] });
for (const rootNode of parsed) {
if (rootNode.tagName !== "osm")
continue;
for (const elNode of rootNode.children) {
// Check children for evidence this is a derived element and process if so
if (elNode.children.find((c) => ["point", "vertex", "linestring", "group"].includes(c.tagName))) {
// TODO: other derived output geoms?
const obj = new Output(elNode.tagName, elNode.attributes.id, refElements);
obj.addMetas(purgeProps(elNode.attributes, [
"id",
"type",
"tags",
"geometry",
]));
setTagsFromXML(elNode, obj);
const coordinates = [];
const geometries = [];
for (const elChild of elNode.children) {
switch (elChild.tagName) {
case "point":
obj.setGeometry({
type: "Point",
coordinates: [
parseFloat(elChild.attributes.lon),
parseFloat(elChild.attributes.lat),
],
});
break;
case "vertex":
coordinates.push([
parseFloat(elChild.attributes.lon),
parseFloat(elChild.attributes.lat),
]);
break;
case "linestring":
const ring = [];
for (const vertex of elChild.children) {
ring.push([
parseFloat(vertex.attributes.lon),
parseFloat(vertex.attributes.lat),
]);
}
obj.setGeometry({ type: "Polygon", coordinates: [ring] });
break;
case "group":
const groupCoords = [];
for (const groupChild of elChild.children) {
switch (groupChild.tagName) {
case "point":
geometries.push({
type: "Point",
coordinates: [
parseFloat(groupChild.attributes.lon),
parseFloat(groupChild.attributes.lat),
],
});
break;
case "vertex":
groupCoords.push([
parseFloat(groupChild.attributes.lon),
parseFloat(groupChild.attributes.lat),
]);
break;
}
}
if (groupCoords.length > 0) {
geometries.push({
type: "LineString",
coordinates: groupCoords,
});
}
break;
}
}
if (coordinates.length > 0) {
obj.setGeometry({ type: "LineString", coordinates });
}
else if (geometries.length > 0) {
obj.setGeometry({ type: "GeometryCollection", geometries });
}
continue;
}
// handle nodes/ways/relations
switch (elNode.tagName) {
case "node":
const node = new Node(elNode.attributes.id, refElements);
setTagsFromXML(elNode, node);
node.addMetas(purgeProps(elNode.attributes, [
"id",
"lon",
"lat",
]));
node.setLatLng(elNode.attributes);
break;
case "way":
const way = new Way(elNode.attributes.id, refElements);
setTagsFromXML(elNode, way);
way.addMetas(purgeProps(elNode.attributes, [
"id",
"type",
]));
for (const elChild of elNode.children) {
switch (elChild.tagName) {
case "center":
way.setCenter(elChild.attributes);
break;
case "nd":
if (elChild.attributes.lon && elChild.attributes.lat) {
way.addLatLng(elChild.attributes);
}
else {
way.addNodeRef(elChild.attributes.ref);
}
break;
}
}
break;
case "relation":
const rel = new Relation(elNode.attributes.id, refElements);
setTagsFromXML(elNode, rel);
for (const elChild of elNode.children) {
switch (elChild.tagName) {
case "center":
rel.setCenter(elChild.attributes);
break;
case "member":
const member = {
type: elChild.attributes.type,
role: elChild.attributes.role || "",
ref: elChild.attributes.ref,
};
if (elChild.attributes.type === "node" &&
elChild.attributes.lon &&
elChild.attributes.lat) {
member.lon = elChild.attributes.lon;
member.lat = elChild.attributes.lat;
}
else {
const geometry = [];
const nodes = [];
for (const memChild of elChild.children) {
if (memChild.attributes.lon && memChild.attributes.lat) {
geometry.push(memChild.attributes);
}
else if (memChild.attributes.ref) {
nodes.push(memChild.attributes.ref);
}
}
if (geometry.length > 0) {
member.geometry = geometry;
}
else if (nodes.length > 0) {
member.nodes = nodes;
}
}
rel.addMember(member);
break;
case "bounds":
rel.setBounds([
parseFloat(elChild.attributes.minlon),
parseFloat(elChild.attributes.minlat),
parseFloat(elChild.attributes.maxlon),
parseFloat(elChild.attributes.maxlat),
]);
break;
}
}
break;
}
}
}
}