osm2geojson-lite
Version:
a lightweight yet faster osm (either in xml or in json formats) to geojson convertor - 4x faster than xmldom + osmtogeojson in most situations - implemented in pure JavaScript without any 3rd party dependency
925 lines (924 loc) • 25 kB
JavaScript
//#region \0rolldown/runtime.js
var __esmMin = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
var __commonJSMin = (cb, mod) => () => (mod || (cb((mod = { exports: {} }).exports, mod), cb = null), mod.exports);
//#endregion
//#region src/utils.ts
function purgeProps(obj, blacklist) {
if (obj) {
const rs = Object.assign({}, obj);
if (blacklist) for (const prop of blacklist) delete rs[prop];
return rs;
}
return {};
}
function first(a) {
return a[0];
}
function last(a) {
return a[a.length - 1];
}
function coordsToKey(a) {
return a.join(",");
}
function addToMap(m, k, v) {
const a = m[k];
if (a) a.push(v);
else m[k] = [v];
}
function removeFromMap(m, k, v) {
const a = m[k];
let idx = -1;
if (a) idx = a.indexOf(v);
if (idx >= 0) a.splice(idx, 1);
}
function getFirstFromMap(m, k) {
const a = m[k];
if (a && a.length > 0) return a[0];
return null;
}
function isRing(a) {
return a.length > 3 && coordsToKey(first(a)) === coordsToKey(last(a));
}
function ringDirection(a, xIdx, yIdx) {
xIdx = xIdx || 0, yIdx = yIdx || 1;
const m = a.reduce((maxxIdx, v, idx) => a[maxxIdx][xIdx || 0] > v[xIdx || 0] ? maxxIdx : idx, 0);
const l = m <= 0 ? a.length - 2 : m - 1;
const r = m >= a.length - 1 ? 1 : m + 1;
const xa = a[l][xIdx];
const xb = a[m][xIdx];
const xc = a[r][xIdx];
const ya = a[l][yIdx];
const yb = a[m][yIdx];
const yc = a[r][yIdx];
return (xb - xa) * (yc - ya) - (xc - xa) * (yb - ya) < 0 ? "clockwise" : "counterclockwise";
}
function pointInsidePolygon(pt, polygon, xIdx, yIdx) {
xIdx = xIdx || 0, yIdx = yIdx || 1;
let result = false;
for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) if ((polygon[i][xIdx] <= pt[xIdx] && pt[xIdx] < polygon[j][xIdx] || polygon[j][xIdx] <= pt[xIdx] && pt[xIdx] < polygon[i][xIdx]) && pt[yIdx] < (polygon[j][yIdx] - polygon[i][yIdx]) * (pt[xIdx] - polygon[i][xIdx]) / (polygon[j][xIdx] - polygon[i][xIdx]) + polygon[i][yIdx]) result = !result;
return result;
}
function strArrayArrayToFloat(el) {
return el.map(strArrayToFloat);
}
function strArrayToFloat(el) {
return el.map(parseFloat);
}
var init_utils = __esmMin((() => {}));
//#endregion
//#region src/xmlparser.ts
function conditioned(evt) {
return evt.match(/^(.+?)\[(.+?)\]>$/g) !== null;
}
function parseEvent(evt) {
const match = /^(.+?)\[(.+?)\]>$/g.exec(evt);
if (match) return {
evt: match[1] + ">",
exp: match[2]
};
return { evt };
}
function genConditionFunc(cond) {
const body = "return " + cond.replace(/(\$.+?)(?=[=!.])/g, "node.$&") + ";";
return new Function("node", body);
}
var XmlParser;
var init_xmlparser = __esmMin((() => {
XmlParser = class {
constructor(opts) {
this.queryParent = false;
this.progressive = false;
this.parentMap = /* @__PURE__ */ new WeakMap();
if (opts) {
this.queryParent = opts.queryParent ? true : false;
this.progressive = opts.progressive;
if (this.queryParent) this.parentMap = /* @__PURE__ */ new WeakMap();
}
this.evtListeners = {};
}
parse(xml, parent, dir) {
dir = dir ? dir + "." : "";
const nodeRegEx = /<([^ >\/]+)(.*?)>/gm;
const nodes = [];
let nodeMatch = nodeRegEx.exec(xml);
while (nodeMatch) {
const tag = nodeMatch[1];
const node = { $tag: tag };
const fullTag = dir + tag;
const attrText = nodeMatch[2].trim();
let closed = false;
if (attrText.endsWith("/") || tag.startsWith("?") || tag.startsWith("!")) closed = true;
const attRegEx1 = /([^ ]+?)="(.+?)"/g;
const attRegEx2 = /([^ ]+?)='(.+?)'/g;
let attMatch = attRegEx1.exec(attrText);
let hasAttrs = false;
while (attMatch) {
hasAttrs = true;
node[attMatch[1]] = attMatch[2];
attMatch = attRegEx1.exec(attrText);
}
if (!hasAttrs) {
attMatch = attRegEx2.exec(attrText);
while (attMatch) {
hasAttrs = true;
node[attMatch[1]] = attMatch[2];
attMatch = attRegEx2.exec(attrText);
}
}
if (!hasAttrs && attrText !== "") node.text = attrText;
if (this.progressive) this.emit(`<${fullTag}>`, node, parent);
if (!closed) {
const innerRegEx = new RegExp(`([^]+?)<\/${tag}>`, "g");
innerRegEx.lastIndex = nodeRegEx.lastIndex;
const innerMatch = innerRegEx.exec(xml);
if (innerMatch && innerMatch[1]) {
nodeRegEx.lastIndex = innerRegEx.lastIndex;
const innerNodes = this.parse(innerMatch[1], node, fullTag);
if (innerNodes.length > 0) node.$innerNodes = innerNodes;
else node.$innerText = innerMatch[1];
}
}
if (this.queryParent && parent) this.parentMap.set(node, parent);
if (this.progressive) this.emit(`</${fullTag}>`, node, parent);
nodes.push(node);
nodeMatch = nodeRegEx.exec(xml);
}
return nodes;
}
getParent(node) {
if (this.queryParent) return this.parentMap.get(node);
return null;
}
addListener(evt, func) {
if (conditioned(evt)) {
const ev = parseEvent(evt);
if (ev.exp) func.condition = genConditionFunc(ev.exp);
evt = ev.evt;
}
this.$addListener(evt, func);
}
removeListener(evt, func) {
if (conditioned(evt)) evt = parseEvent(evt).evt;
this.$removeListener(evt, func);
}
on(evt, func) {
this.addListener(evt, func);
}
off(evt, func) {
this.removeListener(evt, func);
}
$addListener(evt, func) {
const funcs = this.evtListeners[evt];
if (funcs) funcs.push(func);
else this.evtListeners[evt] = [func];
}
$removeListener(evt, func) {
const funcs = this.evtListeners[evt];
let idx = -1;
if (funcs) idx = funcs.indexOf(func);
if (idx >= 0) funcs.splice(idx, 1);
}
emit(evt, ...args) {
const funcs = this.evtListeners[evt];
if (funcs) for (const func of funcs) if (func.condition) {
if (func.condition.apply(null, args) === true) func.apply(null, args);
} else func.apply(null, args);
}
};
}));
//#endregion
//#region src/osm-object.ts
var OsmObject;
var init_osm_object = __esmMin((() => {
OsmObject = class {
constructor(type, id, refElems) {
this.type = type;
this.id = id;
this.refElems = refElems;
this.tags = {};
this.props = { id: this.getCompositeId() };
this.refCount = 0;
this.hasTag = false;
if (refElems) refElems.add(this.getCompositeId(), this);
}
addTags(tags) {
this.tags = Object.assign(this.tags, tags);
this.hasTag = true;
}
addTag(k, v) {
this.tags[k] = v;
this.hasTag = true;
}
addProp(k, v) {
this.props[k] = v;
}
addProps(props) {
this.props = Object.assign(this.props, props);
}
getCompositeId() {
return `${this.type}/${this.id}`;
}
getProps() {
return Object.assign(this.props, this.tags);
}
};
}));
//#endregion
//#region src/node.ts
var Node;
var init_node = __esmMin((() => {
init_osm_object();
init_utils();
Node = class extends OsmObject {
constructor(id, refElems) {
super("node", id, refElems);
}
setLatLng(latLng) {
this.latLng = latLng;
}
toFeatureArray() {
if (this.latLng) return [{
type: "Feature",
id: this.getCompositeId(),
properties: this.getProps(),
geometry: {
type: "Point",
coordinates: strArrayToFloat([this.latLng.lon, this.latLng.lat])
}
}];
return [];
}
getLatLng() {
return this.latLng;
}
};
}));
//#endregion
//#region src/late-binder.ts
var LateBinder;
var init_late_binder = __esmMin((() => {
LateBinder = class {
constructor(container, valueFunc, ctx, args) {
this.container = container;
this.valueFunc = valueFunc;
this.ctx = ctx;
this.args = args;
}
bind() {
const v = this.valueFunc.apply(this.ctx, this.args);
const idx = this.container.indexOf(this);
if (idx < 0) return;
const args = [idx, 1];
if (v) args.push(v);
Array.prototype.splice.apply(this.container, args);
}
};
}));
//#endregion
//#region src/polytags.json
init_xmlparser();
init_node();
init_late_binder();
var polytags_default = {
building: {},
highway: { "whitelist": [
"services",
"rest_area",
"escape",
"elevator"
] },
natural: { "blacklist": [
"coastline",
"cliff",
"ridge",
"arete",
"tree_row"
] },
landuse: {},
waterway: { "whitelist": [
"riverbank",
"dock",
"boatyard",
"dam"
] },
amenity: {},
leisure: {},
barrier: { "whitelist": [
"city_wall",
"ditch",
"hedge",
"retaining_wall",
"wall",
"spikes"
] },
railway: { "whitelist": [
"station",
"turntable",
"roundhouse",
"platform"
] },
area: {},
boundary: {},
man_made: { "blacklist": [
"cutline",
"embankment",
"pipeline"
] },
power: { "whitelist": [
"plant",
"substation",
"generator",
"transformer"
] },
place: {},
shop: {},
aeroway: { "blacklist": ["taxiway"] },
tourism: {},
historic: {},
public_transport: {},
office: {},
"building:part": {},
military: {},
ruins: {},
"area:highway": {},
craft: {},
golf: {},
indoor: {}
};
//#endregion
//#region src/way.ts
var Way;
var init_way = __esmMin((() => {
init_osm_object();
init_utils();
Way = class extends OsmObject {
constructor(id, refElems) {
super("way", id, refElems);
this.latLngArray = [];
this.isPolygon = false;
}
addLatLng(latLng) {
this.latLngArray.push(latLng);
}
setLatLngArray(latLngArray) {
this.latLngArray = latLngArray;
}
addNodeRef(ref) {
const binder = new LateBinder(this.latLngArray, (id) => {
const node = this.refElems.get(`node/${id}`);
if (node) {
node.refCount++;
return node.getLatLng();
}
}, this, [ref]);
this.latLngArray.push(binder);
this.refElems.addBinder(binder);
}
addTags(tags) {
super.addTags(tags);
for (const [k, v] of Object.entries(tags)) this.analyzeTag(k, v);
}
addTag(k, v) {
super.addTag(k, v);
this.analyzeTag(k, v);
}
toCoordsArray() {
return this.latLngArray.map((latLng) => [latLng.lon, latLng.lat]);
}
toFeatureArray() {
let coordsArrayString = this.toCoordsArray();
if (coordsArrayString.length > 1) {
const coordsArray = strArrayArrayToFloat(coordsArrayString);
const feature = {
type: "Feature",
id: this.getCompositeId(),
properties: this.getProps(),
geometry: {
type: "LineString",
coordinates: coordsArray
}
};
if (this.isPolygon && isRing(coordsArray)) {
if (ringDirection(coordsArray) !== "counterclockwise") coordsArray.reverse();
feature.geometry = {
type: "Polygon",
coordinates: [coordsArray]
};
return [feature];
}
return [feature];
}
return [];
}
analyzeTag(k, v) {
const o = polytags_default[k];
if (o) {
this.isPolygon = true;
if (o.whitelist) this.isPolygon = o.whitelist.indexOf(v) >= 0 ? true : false;
else if (o.blacklist) this.isPolygon = o.blacklist.indexOf(v) >= 0 ? false : true;
}
}
};
}));
//#endregion
//#region src/way-collection.ts
var WayCollection;
var init_way_collection = __esmMin((() => {
init_utils();
WayCollection = class extends Array {
constructor() {
super();
this.firstMap = {};
this.lastMap = {};
}
addWay(way) {
const w = way.toCoordsArray();
if (w.length > 0) {
this.push(w);
addToMap(this.firstMap, coordsToKey(first(w)), w);
addToMap(this.lastMap, coordsToKey(last(w)), w);
}
}
mergeWays() {
const strings = [];
let way = this.shift();
while (way) {
removeFromMap(this.firstMap, coordsToKey(first(way)), way);
removeFromMap(this.lastMap, coordsToKey(last(way)), way);
let current = way;
let next;
do {
let nextWay = this.getNextWay(current);
next = nextWay.next;
let mergeType = nextWay.mergeType;
if (!next) continue;
this.splice(this.indexOf(next), 1);
removeFromMap(this.firstMap, coordsToKey(first(next)), next);
removeFromMap(this.lastMap, coordsToKey(last(next)), next);
switch (mergeType) {
case 0:
current = current.concat(next.slice(1));
break;
case 1:
next.reverse();
current = current.concat(next.slice(1));
break;
case 2:
current.reverse();
current = current.concat(next.slice(1));
break;
case 3:
current = next.concat(current.slice(1));
current.reverse();
break;
}
} while (next);
strings.push(strArrayArrayToFloat(current));
way = this.shift();
}
return strings;
}
/**
* Try to find the next way to add to the current way.
* It first tries the next way in the array, and if this doesn't work, try any other way.
*/
getNextWay(current) {
const lastKey = coordsToKey(last(current));
const firstKey = coordsToKey(first(current));
let next = this.length > 0 ? this[0] : null;
if (next) {
const nextFirstKey = coordsToKey(first(next));
const nextLastKey = coordsToKey(last(next));
if (lastKey === nextFirstKey) return {
next,
mergeType: 0
};
if (lastKey === nextLastKey) return {
next,
mergeType: 1
};
if (firstKey === nextFirstKey) return {
next,
mergeType: 2
};
if (firstKey === nextLastKey) return {
next,
mergeType: 3
};
}
next = getFirstFromMap(this.firstMap, lastKey);
if (next) return {
next,
mergeType: 0
};
next = getFirstFromMap(this.lastMap, lastKey);
return {
next,
mergeType: 1
};
}
toRings(direction) {
const strings = this.mergeWays();
const rings = [];
let str = strings.shift();
while (str) {
if (isRing(str)) {
if (ringDirection(str) !== direction) str.reverse();
rings.push(str);
}
str = strings.shift();
}
return rings;
}
};
}));
//#endregion
//#region src/relation.ts
var Relation;
var init_relation = __esmMin((() => {
init_osm_object();
init_way();
init_way_collection();
init_late_binder();
init_utils();
Relation = class extends OsmObject {
constructor(id, refElems) {
super("relation", id, refElems);
this.relations = [];
this.nodes = [];
this.bounds = void 0;
this.ways = [];
this.roles = [];
}
setBounds(bounds) {
this.bounds = bounds;
}
addMember(member) {
switch (member.type) {
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.role) member.role = "";
if (member.geometry) {
const way = new Way(member.ref, this.refElems);
way.setLatLngArray(member.geometry);
way.refCount++;
this.ways.push(way);
this.roles.push(member.role);
} 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);
this.roles.push(member.role);
} else {
let binder = new LateBinder(this.ways, (nid) => {
const way = this.refElems.get(`way/${nid}`);
if (way) {
way.refCount++;
return way;
}
}, this, [member.ref]);
this.ways.push(binder);
this.roles.push(member.role);
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.addProp(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]);
ring = innerRings.shift();
while (ring) {
for (const idx in outerRings) if (pointInsidePolygon(first(ring), outerRings[idx])) {
compositPolyons[idx].push(ring);
break;
}
ring = innerRings.shift();
}
if (compositPolyons.length === 1) return {
type: "Polygon",
coordinates: compositPolyons[0]
};
return {
type: "MultiPolygon",
coordinates: compositPolyons
};
}
return null;
}
collectAllWaysForRelation(relation, relationToWaysMap) {
const ways = [...relation.ways];
const roles = [...relation.roles];
if (relation.relations.length === 0) {
relationToWaysMap.set(relation.id, {
ways,
roles
});
return {
ways,
roles
};
}
for (const subRelation of relation.relations) {
if (!subRelation) continue;
if (!relationToWaysMap.has(subRelation.id)) this.collectAllWaysForRelation(subRelation, relationToWaysMap);
const entry = relationToWaysMap.get(subRelation.id);
for (let i = 0; i < entry.ways.length; i++) {
ways.push(entry.ways[i]);
roles.push(entry.roles[i]);
}
}
relationToWaysMap.set(relation.id, {
ways,
roles
});
return {
ways,
roles
};
}
toFeatureArray() {
const polygonFeatures = [];
const stringFeatures = [];
let pointFeatures = [];
const relationToWaysMap = /* @__PURE__ */ new Map();
const waysAndRoles = this.collectAllWaysForRelation(this, relationToWaysMap);
let templateFeature = {
type: "Feature",
id: this.getCompositeId(),
bbox: this.bounds,
properties: this.getProps(),
geometry: null
};
if (!this.bounds) delete templateFeature.bbox;
if (this.roles.some((r) => r === "outer")) {
const outerWayCollection = new WayCollection();
const innerWayCollection = new WayCollection();
for (let i = 0; i < waysAndRoles.ways.length; i++) {
const way = waysAndRoles.ways[i];
const role = waysAndRoles.roles[i];
if (role === "outer") outerWayCollection.addWay(way);
else if (role === "inner") innerWayCollection.addWay(way);
}
let feature = Object.assign({}, templateFeature);
let geometry = this.constructPolygonGeometry(outerWayCollection, innerWayCollection);
if (geometry) {
feature.geometry = geometry;
polygonFeatures.push(feature);
}
} else {
const wayCollection = new WayCollection();
for (let way of waysAndRoles.ways) wayCollection.addWay(way);
let geometry = this.constructStringGeometry(wayCollection);
if (geometry) {
let feature = Object.assign({}, templateFeature);
feature.geometry = geometry;
stringFeatures.push(feature);
}
}
for (let node of this.nodes) pointFeatures = pointFeatures.concat(node.toFeatureArray());
return [
...polygonFeatures,
...stringFeatures,
...pointFeatures
];
}
};
}));
//#endregion
//#region src/ref-elements.ts
var RefElements;
var init_ref_elements = __esmMin((() => {
RefElements = class extends Map {
constructor() {
super();
this.binders = [];
}
add(k, v) {
this.set(k, v);
}
addBinder(binder) {
this.binders.push(binder);
}
bindAll() {
this.binders.forEach((binder) => binder.bind());
}
};
}));
//#endregion
//#region src/index.ts
var require_src = /* @__PURE__ */ __commonJSMin(((exports, module) => {
init_utils();
init_node();
init_way();
init_relation();
init_ref_elements();
function parseOptions(options) {
if (!options) return {
completeFeature: false,
renderTagged: false,
excludeWay: true
};
let excludeWay = options.excludeWay === void 0 || options.excludeWay;
return {
completeFeature: options.completeFeature ? true : false,
renderTagged: options.renderTagged ? true : false,
excludeWay
};
}
function detectFormat(o) {
if (o.elements) return "json";
if (o.indexOf("<osm") >= 0) return "xml";
if (o.trim().startsWith("{")) return "json-raw";
return "invalid";
}
function analyzeFeaturesFromJson(osm, refElements) {
for (const elem of osm.elements) switch (elem.type) {
case "node":
const node = new Node(elem.id, refElements);
if (elem.tags) node.addTags(elem.tags);
node.addProps(purgeProps(elem, [
"id",
"type",
"tags",
"lat",
"lon"
]));
node.setLatLng(elem);
break;
case "way":
const way = new Way(elem.id, refElements);
if (elem.tags) way.addTags(elem.tags);
way.addProps(purgeProps(elem, [
"id",
"type",
"tags",
"nodes",
"geometry"
]));
if (elem.geometry) way.setLatLngArray(elem.geometry);
else if (elem.nodes) for (const n of elem.nodes) way.addNodeRef(n);
break;
case "relation":
const relation = new Relation(elem.id, refElements);
if (elem.bounds) relation.setBounds([
parseFloat(elem.bounds.minlon),
parseFloat(elem.bounds.minlat),
parseFloat(elem.bounds.maxlon),
parseFloat(elem.bounds.maxlat)
]);
if (elem.tags) relation.addTags(elem.tags);
relation.addProps(purgeProps(elem, [
"id",
"type",
"tags",
"bounds",
"members"
]));
if (elem.members) for (const member of elem.members) relation.addMember(member);
default: break;
}
}
function analyzeFeaturesFromXml(osm, refElements) {
const xmlParser = new XmlParser({ progressive: true });
xmlParser.on("</osm.node>", (node) => {
const nd = new Node(node.id, refElements);
for (const [k, v] of Object.entries(node)) if (!k.startsWith("$") && [
"id",
"lon",
"lat"
].indexOf(k) < 0) nd.addProp(k, v);
nd.setLatLng(node);
if (node.$innerNodes) {
for (const ind of node.$innerNodes) if (ind.$tag === "tag") nd.addTag(ind.k, ind.v);
}
});
xmlParser.on("</osm.way>", (node) => {
const way = new Way(node.id, refElements);
for (const [k, v] of Object.entries(node)) if (!k.startsWith("$") && ["id"].indexOf(k) < 0) way.addProp(k, v);
if (node.$innerNodes) {
for (const ind of node.$innerNodes) if (ind.$tag === "nd") {
if (ind.lon && ind.lat) way.addLatLng(ind);
else if (ind.ref) way.addNodeRef(ind.ref);
} else if (ind.$tag === "tag") way.addTag(ind.k, ind.v);
}
});
xmlParser.on("<osm.relation>", (node) => new Relation(node.id, refElements));
xmlParser.on("</osm.relation.member>", (node, parent) => {
const relation = refElements.get(`relation/${parent?.id}`);
const member = {
type: node.type,
role: node.role ? node.role : "",
ref: node.ref
};
if (node.lat && node.lon) {
member.lat = node.lat, member.lon = node.lon, member.tags = {};
for (const [k, v] of Object.entries(node)) if (!k.startsWith("$") && [
"type",
"lat",
"lon"
].indexOf(k) < 0) member[k] = v;
}
if (node.$innerNodes) {
const geometry = [];
const nodes = [];
for (const ind of node.$innerNodes) if (ind.lat && ind.lon) geometry.push(ind);
else if (ind.ref) nodes.push(ind.ref);
if (geometry.length > 0) member.geometry = geometry;
else if (nodes.length > 0) member.nodes = nodes;
}
relation.addMember(member);
});
xmlParser.on("</osm.relation.bounds>", (node, parent) => {
refElements.get(`relation/${parent?.id}`).setBounds([
parseFloat(node.minlon),
parseFloat(node.minlat),
parseFloat(node.maxlon),
parseFloat(node.maxlat)
]);
});
xmlParser.on("</osm.relation.tag>", (node, parent) => {
refElements.get(`relation/${parent?.id}`).addTag(node.k, node.v);
});
xmlParser.parse(osm);
}
function osm2geojson(osm, opts) {
let { completeFeature, renderTagged, excludeWay } = parseOptions(opts);
let format = detectFormat(osm);
const refElements = new RefElements();
let featureArray = [];
if (format === "json-raw") {
osm = JSON.parse(osm);
if (osm.elements) format = "json";
else format = "invalid";
}
if (format === "json") analyzeFeaturesFromJson(osm, refElements);
else if (format === "xml") analyzeFeaturesFromXml(osm, refElements);
refElements.bindAll();
for (const v of refElements.values()) {
if (v.refCount > 0 && (!v.hasTag || !renderTagged || v instanceof Way && excludeWay)) continue;
const features = v.toFeatureArray();
if (v instanceof Relation && !completeFeature && features.length > 0) return features[0].geometry;
featureArray = featureArray.concat(features);
}
return {
type: "FeatureCollection",
features: featureArray
};
}
module.exports = osm2geojson;
}));
//#endregion
export default require_src();