osm2geojson-lite 2.0.1 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1,8 +1,4 @@
1
- //#region \0rolldown/runtime.js
2
- var __esmMin = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
3
- var __commonJSMin = (cb, mod) => () => (mod || (cb((mod = { exports: {} }).exports, mod), cb = null), mod.exports);
4
- //#endregion
5
- //#region src/utils.ts
1
+ // src/utils.ts
6
2
  function purgeProps(obj, blacklist) {
7
3
  if (obj) {
8
4
  const rs = Object.assign({}, obj);
@@ -37,11 +33,56 @@ function getFirstFromMap(m, k) {
37
33
  return null;
38
34
  }
39
35
  function isRing(a) {
40
- return a.length > 3 && coordsToKey(first(a)) === coordsToKey(last(a));
36
+ if (a.length <= 3) return false;
37
+ const firstPoint = a[0];
38
+ const lastPoint = a[a.length - 1];
39
+ return firstPoint[0] === lastPoint[0] && firstPoint[1] === lastPoint[1];
41
40
  }
41
+ /**
42
+ * Build the closed rectangle used by Overpass `out bb` responses when an
43
+ * element carries only a bounding box.
44
+ * @param bounds `[minlon, minlat, maxlon, maxlat]`
45
+ */
46
+ function boundsToRing(bounds) {
47
+ const [minLon, minLat, maxLon, maxLat] = bounds;
48
+ return [
49
+ [minLon, minLat],
50
+ [maxLon, minLat],
51
+ [maxLon, maxLat],
52
+ [minLon, maxLat],
53
+ [minLon, minLat]
54
+ ];
55
+ }
56
+ /**
57
+ * Bounds produced by `ringDirection` while it already scans the ring. Polygon
58
+ * assembly needs both, so reusing that pass avoids a second full scan on
59
+ * relation-heavy data.
60
+ */
61
+ const ringBoundsCache = /* @__PURE__ */ new WeakMap();
42
62
  function ringDirection(a, xIdx, yIdx) {
43
63
  xIdx = xIdx || 0, yIdx = yIdx || 1;
44
- const m = a.reduce((maxxIdx, v, idx) => a[maxxIdx][xIdx || 0] > v[xIdx || 0] ? maxxIdx : idx, 0);
64
+ let m = 0;
65
+ let maxX = a[0][xIdx];
66
+ let minX = maxX;
67
+ let minY = a[0][yIdx];
68
+ let maxY = minY;
69
+ for (let idx = 1; idx < a.length; idx++) {
70
+ const point = a[idx];
71
+ const x = point[xIdx];
72
+ const y = point[yIdx];
73
+ if (x >= maxX) {
74
+ maxX = x;
75
+ m = idx;
76
+ } else if (x < minX) minX = x;
77
+ if (y < minY) minY = y;
78
+ else if (y > maxY) maxY = y;
79
+ }
80
+ if (xIdx === 0 && yIdx === 1) ringBoundsCache.set(a, {
81
+ minX,
82
+ maxX,
83
+ minY,
84
+ maxY
85
+ });
45
86
  const l = m <= 0 ? a.length - 2 : m - 1;
46
87
  const r = m >= a.length - 1 ? 1 : m + 1;
47
88
  const xa = a[l][xIdx];
@@ -52,242 +93,152 @@ function ringDirection(a, xIdx, yIdx) {
52
93
  const yc = a[r][yIdx];
53
94
  return (xb - xa) * (yc - ya) - (xc - xa) * (yb - ya) < 0 ? "clockwise" : "counterclockwise";
54
95
  }
96
+ function ringBounds(ring) {
97
+ const cached = ringBoundsCache.get(ring);
98
+ if (cached) return cached;
99
+ let minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity;
100
+ for (let i = 0; i < ring.length; i++) {
101
+ const point = ring[i];
102
+ const x = point[0];
103
+ const y = point[1];
104
+ if (x < minX) minX = x;
105
+ if (x > maxX) maxX = x;
106
+ if (y < minY) minY = y;
107
+ if (y > maxY) maxY = y;
108
+ }
109
+ const bounds = {
110
+ minX,
111
+ maxX,
112
+ minY,
113
+ maxY
114
+ };
115
+ ringBoundsCache.set(ring, bounds);
116
+ return bounds;
117
+ }
55
118
  function pointInsidePolygon(pt, polygon, xIdx, yIdx) {
56
119
  xIdx = xIdx || 0, yIdx = yIdx || 1;
120
+ const px = pt[xIdx];
121
+ const py = pt[yIdx];
57
122
  let result = false;
58
- 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;
123
+ let xj = polygon[polygon.length - 1][xIdx];
124
+ let yj = polygon[polygon.length - 1][yIdx];
125
+ for (let i = 0; i < polygon.length; i++) {
126
+ const point = polygon[i];
127
+ const xi = point[xIdx];
128
+ const yi = point[yIdx];
129
+ if ((xi <= px && px < xj || xj <= px && px < xi) && py < (yj - yi) * (px - xi) / (xj - xi) + yi) result = !result;
130
+ xj = xi;
131
+ yj = yi;
132
+ }
59
133
  return result;
60
134
  }
61
- function strArrayArrayToFloat(el) {
62
- return el.map(strArrayToFloat);
63
- }
64
- function strArrayToFloat(el) {
65
- return el.map(parseFloat);
66
- }
67
- var init_utils = __esmMin((() => {}));
68
- //#endregion
69
- //#region src/xmlparser.ts
70
- function conditioned(evt) {
71
- return evt.match(/^(.+?)\[(.+?)\]>$/g) !== null;
72
- }
73
- function parseEvent(evt) {
74
- const match = /^(.+?)\[(.+?)\]>$/g.exec(evt);
75
- if (match) return {
76
- evt: match[1] + ">",
77
- exp: match[2]
78
- };
79
- return { evt };
80
- }
81
- function genConditionFunc(cond) {
82
- const body = "return " + cond.replace(/(\$.+?)(?=[=!.])/g, "node.$&") + ";";
83
- return new Function("node", body);
84
- }
85
- var XmlParser;
86
- var init_xmlparser = __esmMin((() => {
87
- XmlParser = class {
88
- constructor(opts) {
89
- this.queryParent = false;
90
- this.progressive = false;
91
- this.parentMap = /* @__PURE__ */ new WeakMap();
92
- if (opts) {
93
- this.queryParent = opts.queryParent ? true : false;
94
- this.progressive = opts.progressive;
95
- if (this.queryParent) this.parentMap = /* @__PURE__ */ new WeakMap();
96
- }
97
- this.evtListeners = {};
98
- }
99
- parse(xml, parent, dir) {
100
- dir = dir ? dir + "." : "";
101
- const nodeRegEx = /<([^ >\/]+)(.*?)>/gm;
102
- const nodes = [];
103
- let nodeMatch = nodeRegEx.exec(xml);
104
- while (nodeMatch) {
105
- const tag = nodeMatch[1];
106
- const node = { $tag: tag };
107
- const fullTag = dir + tag;
108
- const attrText = nodeMatch[2].trim();
109
- let closed = false;
110
- if (attrText.endsWith("/") || tag.startsWith("?") || tag.startsWith("!")) closed = true;
111
- const attRegEx1 = /([^ ]+?)="(.+?)"/g;
112
- const attRegEx2 = /([^ ]+?)='(.+?)'/g;
113
- let attMatch = attRegEx1.exec(attrText);
114
- let hasAttrs = false;
115
- while (attMatch) {
116
- hasAttrs = true;
117
- node[attMatch[1]] = attMatch[2];
118
- attMatch = attRegEx1.exec(attrText);
119
- }
120
- if (!hasAttrs) {
121
- attMatch = attRegEx2.exec(attrText);
122
- while (attMatch) {
123
- hasAttrs = true;
124
- node[attMatch[1]] = attMatch[2];
125
- attMatch = attRegEx2.exec(attrText);
126
- }
127
- }
128
- if (!hasAttrs && attrText !== "") node.text = attrText;
129
- if (this.progressive) this.emit(`<${fullTag}>`, node, parent);
130
- if (!closed) {
131
- const innerRegEx = new RegExp(`([^]+?)<\/${tag}>`, "g");
132
- innerRegEx.lastIndex = nodeRegEx.lastIndex;
133
- const innerMatch = innerRegEx.exec(xml);
134
- if (innerMatch && innerMatch[1]) {
135
- nodeRegEx.lastIndex = innerRegEx.lastIndex;
136
- const innerNodes = this.parse(innerMatch[1], node, fullTag);
137
- if (innerNodes.length > 0) node.$innerNodes = innerNodes;
138
- else node.$innerText = innerMatch[1];
139
- }
140
- }
141
- if (this.queryParent && parent) this.parentMap.set(node, parent);
142
- if (this.progressive) this.emit(`</${fullTag}>`, node, parent);
143
- nodes.push(node);
144
- nodeMatch = nodeRegEx.exec(xml);
145
- }
146
- return nodes;
147
- }
148
- getParent(node) {
149
- if (this.queryParent) return this.parentMap.get(node);
150
- return null;
151
- }
152
- addListener(evt, func) {
153
- if (conditioned(evt)) {
154
- const ev = parseEvent(evt);
155
- if (ev.exp) func.condition = genConditionFunc(ev.exp);
156
- evt = ev.evt;
135
+ // src/osm-object.ts
136
+ var OsmObject = class {
137
+ constructor(type, id, refElems, register = true) {
138
+ this.type = type;
139
+ this.id = id;
140
+ this.refElems = refElems;
141
+ this.tags = {};
142
+ this.props = { id: this.getCompositeId() };
143
+ this.refCount = 0;
144
+ this.hasTag = false;
145
+ this.tainted = false;
146
+ if (refElems && register) refElems.add(this.getCompositeId(), this);
147
+ }
148
+ addTags(tags) {
149
+ this.tags = Object.assign(this.tags, tags);
150
+ this.hasTag = true;
151
+ }
152
+ addTag(k, v) {
153
+ this.tags[k] = v;
154
+ this.hasTag = true;
155
+ }
156
+ addProp(k, v) {
157
+ this.props[k] = v;
158
+ }
159
+ addProps(props) {
160
+ this.props = Object.assign(this.props, props);
161
+ }
162
+ getCompositeId() {
163
+ return `${this.type}/${this.id}`;
164
+ }
165
+ getProps() {
166
+ const props = Object.assign({}, this.props, this.tags);
167
+ if (this.tainted && this.refElems && this.refElems.reportTainted) props["@tainted"] = true;
168
+ return props;
169
+ }
170
+ };
171
+ // src/node.ts
172
+ var Node = class extends OsmObject {
173
+ constructor(id, refElems, register = true) {
174
+ super("node", id, refElems, register);
175
+ }
176
+ setLatLng(latLng) {
177
+ this.latLng = latLng;
178
+ }
179
+ toFeatureArray() {
180
+ if (this.latLng) return [{
181
+ type: "Feature",
182
+ id: this.getCompositeId(),
183
+ properties: this.getProps(),
184
+ geometry: {
185
+ type: "Point",
186
+ coordinates: [+this.latLng.lon, +this.latLng.lat]
157
187
  }
158
- this.$addListener(evt, func);
159
- }
160
- removeListener(evt, func) {
161
- if (conditioned(evt)) evt = parseEvent(evt).evt;
162
- this.$removeListener(evt, func);
163
- }
164
- on(evt, func) {
165
- this.addListener(evt, func);
166
- }
167
- off(evt, func) {
168
- this.removeListener(evt, func);
169
- }
170
- $addListener(evt, func) {
171
- const funcs = this.evtListeners[evt];
172
- if (funcs) funcs.push(func);
173
- else this.evtListeners[evt] = [func];
174
- }
175
- $removeListener(evt, func) {
176
- const funcs = this.evtListeners[evt];
177
- let idx = -1;
178
- if (funcs) idx = funcs.indexOf(func);
179
- if (idx >= 0) funcs.splice(idx, 1);
180
- }
181
- emit(evt, ...args) {
182
- const funcs = this.evtListeners[evt];
183
- if (funcs) for (const func of funcs) if (func.condition) {
184
- if (func.condition.apply(null, args) === true) func.apply(null, args);
185
- } else func.apply(null, args);
186
- }
187
- };
188
- }));
189
- //#endregion
190
- //#region src/osm-object.ts
191
- var OsmObject;
192
- var init_osm_object = __esmMin((() => {
193
- OsmObject = class {
194
- constructor(type, id, refElems) {
195
- this.type = type;
196
- this.id = id;
197
- this.refElems = refElems;
198
- this.tags = {};
199
- this.props = { id: this.getCompositeId() };
200
- this.refCount = 0;
201
- this.hasTag = false;
202
- if (refElems) refElems.add(this.getCompositeId(), this);
203
- }
204
- addTags(tags) {
205
- this.tags = Object.assign(this.tags, tags);
206
- this.hasTag = true;
207
- }
208
- addTag(k, v) {
209
- this.tags[k] = v;
210
- this.hasTag = true;
211
- }
212
- addProp(k, v) {
213
- this.props[k] = v;
214
- }
215
- addProps(props) {
216
- this.props = Object.assign(this.props, props);
217
- }
218
- getCompositeId() {
219
- return `${this.type}/${this.id}`;
220
- }
221
- getProps() {
222
- return Object.assign(this.props, this.tags);
223
- }
224
- };
225
- }));
226
- //#endregion
227
- //#region src/node.ts
228
- var Node;
229
- var init_node = __esmMin((() => {
230
- init_osm_object();
231
- init_utils();
232
- Node = class extends OsmObject {
233
- constructor(id, refElems) {
234
- super("node", id, refElems);
235
- }
236
- setLatLng(latLng) {
237
- this.latLng = latLng;
238
- }
239
- toFeatureArray() {
240
- if (this.latLng) return [{
241
- type: "Feature",
242
- id: this.getCompositeId(),
243
- properties: this.getProps(),
244
- geometry: {
245
- type: "Point",
246
- coordinates: strArrayToFloat([this.latLng.lon, this.latLng.lat])
247
- }
248
- }];
249
- return [];
250
- }
251
- getLatLng() {
252
- return this.latLng;
253
- }
254
- };
255
- }));
256
- //#endregion
257
- //#region src/late-binder.ts
258
- var LateBinder;
259
- var init_late_binder = __esmMin((() => {
260
- LateBinder = class {
261
- constructor(container, valueFunc, ctx, args) {
262
- this.container = container;
263
- this.valueFunc = valueFunc;
264
- this.ctx = ctx;
265
- this.args = args;
266
- }
267
- bind() {
268
- const v = this.valueFunc.apply(this.ctx, this.args);
269
- const idx = this.container.indexOf(this);
270
- if (idx < 0) return;
271
- const args = [idx, 1];
272
- if (v) args.push(v);
273
- Array.prototype.splice.apply(this.container, args);
274
- }
275
- };
276
- }));
277
- //#endregion
278
- //#region src/polytags.json
279
- init_xmlparser();
280
- init_node();
281
- init_late_binder();
282
- var polytags_default = {
283
- building: {},
284
- highway: { "whitelist": [
188
+ }];
189
+ return [];
190
+ }
191
+ getLatLng() {
192
+ return this.latLng;
193
+ }
194
+ };
195
+ // src/output.ts
196
+ /**
197
+ * A pre-built geometry carried by Overpass derived responses (`out geom`,
198
+ * `convert`, `make`, `local`) or by equivalent XML output.
199
+ */
200
+ var Output = class extends OsmObject {
201
+ constructor(type, id, refElems) {
202
+ super(type, id, refElems);
203
+ }
204
+ setGeometry(geometry) {
205
+ this.geometry = geometry;
206
+ }
207
+ toFeatureArray() {
208
+ if (!this.geometry) return [];
209
+ return [{
210
+ type: "Feature",
211
+ id: this.getCompositeId(),
212
+ properties: this.getProps(),
213
+ geometry: this.geometry
214
+ }];
215
+ }
216
+ };
217
+ // src/late-binder.ts
218
+ var LateBinder = class {
219
+ constructor(container, valueFunc, ctx, args) {
220
+ this.container = container;
221
+ this.valueFunc = valueFunc;
222
+ this.ctx = ctx;
223
+ this.args = args;
224
+ }
225
+ resolve() {
226
+ return this.valueFunc.apply(this.ctx, this.args);
227
+ }
228
+ getContainer() {
229
+ return this.container;
230
+ }
231
+ };
232
+ // src/way.ts
233
+ const defaultPolygonRules = {
234
+ building: { "ignore": ["no"] },
235
+ highway: { "include": [
285
236
  "services",
286
237
  "rest_area",
287
238
  "escape",
288
239
  "elevator"
289
240
  ] },
290
- natural: { "blacklist": [
241
+ natural: { "exclude": [
291
242
  "coastline",
292
243
  "cliff",
293
244
  "ridge",
@@ -295,7 +246,7 @@ var polytags_default = {
295
246
  "tree_row"
296
247
  ] },
297
248
  landuse: {},
298
- waterway: { "whitelist": [
249
+ waterway: { "include": [
299
250
  "riverbank",
300
251
  "dock",
301
252
  "boatyard",
@@ -303,7 +254,7 @@ var polytags_default = {
303
254
  ] },
304
255
  amenity: {},
305
256
  leisure: {},
306
- barrier: { "whitelist": [
257
+ barrier: { "include": [
307
258
  "city_wall",
308
259
  "ditch",
309
260
  "hedge",
@@ -311,481 +262,1124 @@ var polytags_default = {
311
262
  "wall",
312
263
  "spikes"
313
264
  ] },
314
- railway: { "whitelist": [
265
+ railway: { "include": [
315
266
  "station",
316
267
  "turntable",
317
268
  "roundhouse",
318
269
  "platform"
319
270
  ] },
320
- area: {},
321
271
  boundary: {},
322
- man_made: { "blacklist": [
272
+ man_made: { "exclude": [
323
273
  "cutline",
324
274
  "embankment",
325
275
  "pipeline"
326
276
  ] },
327
- power: { "whitelist": [
277
+ power: { "include": [
328
278
  "plant",
329
279
  "substation",
330
280
  "generator",
331
281
  "transformer"
332
282
  ] },
283
+ "piste:type": { "include": ["downhill"] },
333
284
  place: {},
334
285
  shop: {},
335
- aeroway: { "blacklist": ["taxiway"] },
286
+ aeroway: { "exclude": ["taxiway"] },
336
287
  tourism: {},
337
288
  historic: {},
338
289
  public_transport: {},
339
290
  office: {},
340
- "building:part": {},
291
+ "building:part": { "ignore": ["no"] },
341
292
  military: {},
342
293
  ruins: {},
343
294
  "area:highway": {},
344
295
  craft: {},
345
296
  golf: {},
346
- indoor: {}
297
+ indoor: { "ignore": ["yes", "no"] },
298
+ area: { "exclude": ["no"] }
347
299
  };
348
- //#endregion
349
- //#region src/way.ts
350
- var Way;
351
- var init_way = __esmMin((() => {
352
- init_osm_object();
353
- init_utils();
354
- Way = class extends OsmObject {
355
- constructor(id, refElems) {
356
- super("way", id, refElems);
357
- this.latLngArray = [];
358
- this.isPolygon = false;
359
- }
360
- addLatLng(latLng) {
361
- this.latLngArray.push(latLng);
362
- }
363
- setLatLngArray(latLngArray) {
364
- this.latLngArray = latLngArray;
365
- }
366
- addNodeRef(ref) {
367
- const binder = new LateBinder(this.latLngArray, (id) => {
368
- const node = this.refElems.get(`node/${id}`);
369
- if (node) {
370
- node.refCount++;
371
- return node.getLatLng();
300
+ var Way = class extends OsmObject {
301
+ constructor(id, refElems, register = true) {
302
+ super("way", id, refElems, register);
303
+ this.latLngArray = [];
304
+ this.center = null;
305
+ this.bounds = null;
306
+ }
307
+ addLatLng(latLng) {
308
+ this.latLngArray.push(latLng);
309
+ }
310
+ setLatLngArray(latLngArray) {
311
+ this.latLngArray = latLngArray;
312
+ }
313
+ setCenter(center) {
314
+ this.center = center;
315
+ }
316
+ setBounds(bounds) {
317
+ this.bounds = bounds;
318
+ }
319
+ getBounds() {
320
+ return this.bounds;
321
+ }
322
+ addNodeRef(ref) {
323
+ const binder = new LateBinder(this.latLngArray, (id) => {
324
+ const node = this.refElems.get(`node/${id}`);
325
+ if (node) {
326
+ node.refCount++;
327
+ return node.getLatLng();
328
+ }
329
+ this.tainted = true;
330
+ }, this, [ref]);
331
+ this.latLngArray.push(binder);
332
+ this.refElems.addBinder(binder);
333
+ }
334
+ /**
335
+ * Coordinates are normalised to numbers in a single pass here. Previously
336
+ * the strings were joined for map keys and parsed again during output, so
337
+ * every coordinate was converted twice and several intermediate arrays
338
+ * were allocated.
339
+ */
340
+ toCoordsArray() {
341
+ const source = this.latLngArray;
342
+ const coords = new Array(source.length);
343
+ for (let i = 0; i < source.length; i++) {
344
+ const latLng = source[i];
345
+ coords[i] = [+latLng.lon, +latLng.lat];
346
+ }
347
+ return coords;
348
+ }
349
+ toFeatureArray() {
350
+ const coordsArray = this.toCoordsArray();
351
+ if (coordsArray.length > 1) {
352
+ const feature = {
353
+ type: "Feature",
354
+ id: this.getCompositeId(),
355
+ properties: this.getProps(),
356
+ geometry: {
357
+ type: "LineString",
358
+ coordinates: coordsArray
372
359
  }
373
- }, this, [ref]);
374
- this.latLngArray.push(binder);
375
- this.refElems.addBinder(binder);
376
- }
377
- addTags(tags) {
378
- super.addTags(tags);
379
- for (const [k, v] of Object.entries(tags)) this.analyzeTag(k, v);
380
- }
381
- addTag(k, v) {
382
- super.addTag(k, v);
383
- this.analyzeTag(k, v);
384
- }
385
- toCoordsArray() {
386
- return this.latLngArray.map((latLng) => [latLng.lon, latLng.lat]);
387
- }
388
- toFeatureArray() {
389
- let coordsArrayString = this.toCoordsArray();
390
- if (coordsArrayString.length > 1) {
391
- const coordsArray = strArrayArrayToFloat(coordsArrayString);
392
- const feature = {
393
- type: "Feature",
394
- id: this.getCompositeId(),
395
- properties: this.getProps(),
396
- geometry: {
397
- type: "LineString",
398
- coordinates: coordsArray
399
- }
360
+ };
361
+ if (isRing(coordsArray) && this.isPolygon()) {
362
+ if (ringDirection(coordsArray) !== "counterclockwise") coordsArray.reverse();
363
+ feature.geometry = {
364
+ type: "Polygon",
365
+ coordinates: [coordsArray]
400
366
  };
401
- if (this.isPolygon && isRing(coordsArray)) {
402
- if (ringDirection(coordsArray) !== "counterclockwise") coordsArray.reverse();
403
- feature.geometry = {
404
- type: "Polygon",
405
- coordinates: [coordsArray]
406
- };
407
- return [feature];
408
- }
409
- return [feature];
410
- }
411
- return [];
412
- }
413
- analyzeTag(k, v) {
414
- const o = polytags_default[k];
415
- if (o) {
416
- this.isPolygon = true;
417
- if (o.whitelist) this.isPolygon = o.whitelist.indexOf(v) >= 0 ? true : false;
418
- else if (o.blacklist) this.isPolygon = o.blacklist.indexOf(v) >= 0 ? false : true;
419
367
  }
368
+ return [feature];
420
369
  }
421
- };
422
- }));
423
- //#endregion
424
- //#region src/way-collection.ts
425
- var WayCollection;
426
- var init_way_collection = __esmMin((() => {
427
- init_utils();
428
- WayCollection = class extends Array {
429
- constructor() {
430
- super();
431
- this.firstMap = {};
432
- this.lastMap = {};
433
- }
434
- addWay(way) {
435
- const w = way.toCoordsArray();
436
- if (w.length > 0) {
437
- this.push(w);
438
- addToMap(this.firstMap, coordsToKey(first(w)), w);
439
- addToMap(this.lastMap, coordsToKey(last(w)), w);
370
+ if (this.bounds) return [{
371
+ type: "Feature",
372
+ id: this.getCompositeId(),
373
+ properties: this.getProps(),
374
+ geometry: {
375
+ type: "Polygon",
376
+ coordinates: [boundsToRing(this.bounds)]
440
377
  }
441
- }
442
- mergeWays() {
443
- const strings = [];
444
- let way = this.shift();
445
- while (way) {
446
- removeFromMap(this.firstMap, coordsToKey(first(way)), way);
447
- removeFromMap(this.lastMap, coordsToKey(last(way)), way);
448
- let current = way;
449
- let next;
450
- do {
451
- let nextWay = this.getNextWay(current);
452
- next = nextWay.next;
453
- let mergeType = nextWay.mergeType;
454
- if (!next) continue;
455
- this.splice(this.indexOf(next), 1);
456
- removeFromMap(this.firstMap, coordsToKey(first(next)), next);
457
- removeFromMap(this.lastMap, coordsToKey(last(next)), next);
458
- switch (mergeType) {
459
- case 0:
460
- current = current.concat(next.slice(1));
461
- break;
462
- case 1:
463
- next.reverse();
464
- current = current.concat(next.slice(1));
465
- break;
466
- case 2:
467
- current.reverse();
468
- current = current.concat(next.slice(1));
469
- break;
470
- case 3:
471
- current = next.concat(current.slice(1));
472
- current.reverse();
473
- break;
474
- }
475
- } while (next);
476
- strings.push(strArrayArrayToFloat(current));
477
- way = this.shift();
378
+ }];
379
+ if (this.center) return [{
380
+ type: "Feature",
381
+ id: this.getCompositeId(),
382
+ properties: this.getProps(),
383
+ geometry: {
384
+ type: "Point",
385
+ coordinates: [+this.center.lon, +this.center.lat]
478
386
  }
479
- return strings;
480
- }
481
- /**
482
- * Try to find the next way to add to the current way.
483
- * It first tries the next way in the array, and if this doesn't work, try any other way.
484
- */
485
- getNextWay(current) {
486
- const lastKey = coordsToKey(last(current));
487
- const firstKey = coordsToKey(first(current));
488
- let next = this.length > 0 ? this[0] : null;
489
- if (next) {
490
- const nextFirstKey = coordsToKey(first(next));
491
- const nextLastKey = coordsToKey(last(next));
492
- if (lastKey === nextFirstKey) return {
493
- next,
494
- mergeType: 0
495
- };
496
- if (lastKey === nextLastKey) return {
497
- next,
498
- mergeType: 1
499
- };
500
- if (firstKey === nextFirstKey) return {
501
- next,
502
- mergeType: 2
503
- };
504
- if (firstKey === nextLastKey) return {
505
- next,
506
- mergeType: 3
507
- };
387
+ }];
388
+ return [];
389
+ }
390
+ /**
391
+ * Polygon detection is order-independent: a closed way is an area when at
392
+ * least one polygon tag applies, while tags such as `building=no` or
393
+ * `area=no` explicitly opt out.
394
+ */
395
+ isPolygon() {
396
+ const polygonRules = this.refElems && this.refElems.polygonTags || defaultPolygonRules;
397
+ let area = false;
398
+ for (const key in polygonRules) {
399
+ const value = this.tags[key];
400
+ if (!value) continue;
401
+ const rule = polygonRules[key];
402
+ if (rule.ignore && rule.ignore.indexOf(value) >= 0) continue;
403
+ area = true;
404
+ if (rule.include) area = rule.include.indexOf(value) >= 0;
405
+ else if (rule.exclude) area = rule.exclude.indexOf(value) < 0;
406
+ else if (rule.whitelist) area = rule.whitelist.indexOf(value) >= 0;
407
+ else if (rule.blacklist) area = rule.blacklist.indexOf(value) < 0;
408
+ }
409
+ return area;
410
+ }
411
+ };
412
+ // src/way-collection.ts
413
+ /**
414
+ * A growable point sequence with O(1) reverse and amortised O(1) append.
415
+ *
416
+ * `left` is stored in reverse order, so the logical sequence is
417
+ * `reverse(left) + right`. Reversing therefore only swaps the two arrays and
418
+ * appending always targets `right`. This lets the line/ring stitcher grow a
419
+ * path without repeatedly copying the accumulated coordinates.
420
+ */
421
+ var PointDeque = class {
422
+ constructor(first, firstKey, lastKey) {
423
+ this.left = [];
424
+ this.right = first.slice();
425
+ this.headKey = firstKey;
426
+ this.tailKey = lastKey;
427
+ }
428
+ reverse() {
429
+ const tmp = this.left;
430
+ this.left = this.right;
431
+ this.right = tmp;
432
+ const key = this.headKey;
433
+ this.headKey = this.tailKey;
434
+ this.tailKey = key;
435
+ }
436
+ /** Append a segment, dropping its first point (the shared joint). */
437
+ append(segment, segmentLastKey) {
438
+ for (let i = 1; i < segment.length; i++) this.right.push(segment[i]);
439
+ this.tailKey = segmentLastKey;
440
+ }
441
+ flatten() {
442
+ const result = new Array(this.left.length + this.right.length);
443
+ let at = 0;
444
+ for (let i = this.left.length - 1; i >= 0; i--) result[at++] = this.left[i];
445
+ for (let i = 0; i < this.right.length; i++) result[at++] = this.right[i];
446
+ return result;
447
+ }
448
+ };
449
+ /**
450
+ * A collection of way coordinate arrays that can be stitched back into lines
451
+ * or rings.
452
+ *
453
+ * The previous implementation extended `Array` and relied on `shift()` plus
454
+ * `splice(indexOf(...))` while merging, which is O(n^2) for relations made of
455
+ * many ways. This version keeps a cursor over a fixed array and marks consumed
456
+ * entries, so merging is linear apart from the small per-coordinate-end maps.
457
+ */
458
+ var WayCollection = class {
459
+ constructor() {
460
+ this.ways = [];
461
+ this.firstKeys = [];
462
+ this.lastKeys = [];
463
+ this.firstMap = {};
464
+ this.lastMap = {};
465
+ this.used = [];
466
+ this.indexByWay = /* @__PURE__ */ new Map();
467
+ this.head = 0;
468
+ }
469
+ addWay(way) {
470
+ let w = way.toCoordsArray();
471
+ if (w.length === 0) {
472
+ const bounds = way.getBounds();
473
+ if (bounds) w = boundsToRing(bounds);
474
+ }
475
+ if (w.length > 0) {
476
+ const index = this.ways.length;
477
+ const firstKey = coordsToKey(first(w));
478
+ const lastKey = coordsToKey(last(w));
479
+ this.ways.push(w);
480
+ this.firstKeys.push(firstKey);
481
+ this.lastKeys.push(lastKey);
482
+ this.used.push(false);
483
+ this.indexByWay.set(w, index);
484
+ addToMap(this.firstMap, firstKey, w);
485
+ addToMap(this.lastMap, lastKey, w);
486
+ }
487
+ }
488
+ mergeWays() {
489
+ const strings = [];
490
+ let index = this.firstUnused();
491
+ while (index >= 0) {
492
+ const way = this.ways[index];
493
+ this.consume(index);
494
+ const deque = new PointDeque(way, this.firstKeys[index], this.lastKeys[index]);
495
+ for (;;) {
496
+ const found = this.getNextWay(deque.headKey, deque.tailKey);
497
+ const next = found.next;
498
+ if (!next) break;
499
+ const nextIndex = this.indexByWay.get(next);
500
+ if (nextIndex === void 0) break;
501
+ this.consume(nextIndex);
502
+ switch (found.mergeType) {
503
+ case 0:
504
+ deque.append(next, this.lastKeys[nextIndex]);
505
+ break;
506
+ case 1:
507
+ next.reverse();
508
+ deque.append(next, this.firstKeys[nextIndex]);
509
+ break;
510
+ case 2:
511
+ deque.reverse();
512
+ deque.append(next, this.lastKeys[nextIndex]);
513
+ break;
514
+ case 3:
515
+ next.reverse();
516
+ deque.reverse();
517
+ deque.append(next, this.firstKeys[nextIndex]);
518
+ }
508
519
  }
509
- next = getFirstFromMap(this.firstMap, lastKey);
510
- if (next) return {
520
+ strings.push(deque.flatten());
521
+ index = this.firstUnused();
522
+ }
523
+ return strings;
524
+ }
525
+ firstUnused() {
526
+ while (this.head < this.ways.length && this.used[this.head]) this.head++;
527
+ return this.head < this.ways.length ? this.head : -1;
528
+ }
529
+ consume(index) {
530
+ this.used[index] = true;
531
+ const way = this.ways[index];
532
+ removeFromMap(this.firstMap, this.firstKeys[index], way);
533
+ removeFromMap(this.lastMap, this.lastKeys[index], way);
534
+ }
535
+ /**
536
+ * Try to find the next way to add to the current way.
537
+ * It first tries the next unused way in the array, and if this doesn't work,
538
+ * tries any other way through the endpoint maps.
539
+ */
540
+ getNextWay(headKey, tailKey) {
541
+ const candidateIndex = this.firstUnused();
542
+ const next = candidateIndex >= 0 ? this.ways[candidateIndex] : null;
543
+ if (next) {
544
+ const nextFirstKey = this.firstKeys[candidateIndex];
545
+ const nextLastKey = this.lastKeys[candidateIndex];
546
+ if (tailKey === nextFirstKey) return {
511
547
  next,
512
548
  mergeType: 0
513
549
  };
514
- next = getFirstFromMap(this.lastMap, lastKey);
515
- return {
550
+ if (tailKey === nextLastKey) return {
516
551
  next,
517
552
  mergeType: 1
518
553
  };
554
+ if (headKey === nextFirstKey) return {
555
+ next,
556
+ mergeType: 2
557
+ };
558
+ if (headKey === nextLastKey) return {
559
+ next,
560
+ mergeType: 3
561
+ };
519
562
  }
520
- toRings(direction) {
521
- const strings = this.mergeWays();
522
- const rings = [];
523
- let str = strings.shift();
524
- while (str) {
525
- if (isRing(str)) {
526
- if (ringDirection(str) !== direction) str.reverse();
527
- rings.push(str);
528
- }
529
- str = strings.shift();
563
+ const fromFirst = getFirstFromMap(this.firstMap, tailKey);
564
+ if (fromFirst) return {
565
+ next: fromFirst,
566
+ mergeType: 0
567
+ };
568
+ return {
569
+ next: getFirstFromMap(this.lastMap, tailKey),
570
+ mergeType: 1
571
+ };
572
+ }
573
+ toRings(direction) {
574
+ const strings = this.mergeWays();
575
+ const rings = [];
576
+ for (let i = 0; i < strings.length; i++) {
577
+ const str = strings[i];
578
+ if (isRing(str)) {
579
+ if (ringDirection(str) !== direction) str.reverse();
580
+ rings.push(str);
530
581
  }
531
- return rings;
532
582
  }
533
- };
534
- }));
535
- //#endregion
536
- //#region src/relation.ts
537
- var Relation;
538
- var init_relation = __esmMin((() => {
539
- init_osm_object();
540
- init_way();
541
- init_way_collection();
542
- init_late_binder();
543
- init_utils();
544
- Relation = class extends OsmObject {
545
- constructor(id, refElems) {
546
- super("relation", id, refElems);
547
- this.relations = [];
548
- this.nodes = [];
549
- this.bounds = void 0;
550
- this.ways = [];
551
- this.roles = [];
552
- }
553
- setBounds(bounds) {
554
- this.bounds = bounds;
555
- }
556
- addMember(member) {
557
- switch (member.type) {
558
- case "relation":
559
- let binder = new LateBinder(this.relations, (id) => {
560
- const relation = this.refElems.get(`relation/${id}`);
561
- if (relation) {
562
- relation.refCount++;
563
- return relation;
583
+ return rings;
584
+ }
585
+ };
586
+ // src/relation.ts
587
+ var Relation = class Relation extends OsmObject {
588
+ constructor(id, refElems, register = true) {
589
+ super("relation", id, refElems, register);
590
+ this.relations = [];
591
+ this.nodes = [];
592
+ this.bounds = void 0;
593
+ this.center = null;
594
+ this.ways = [];
595
+ this.roles = [];
596
+ }
597
+ setBounds(bounds) {
598
+ this.bounds = bounds;
599
+ }
600
+ setCenter(center) {
601
+ this.center = center;
602
+ }
603
+ addMember(member) {
604
+ switch (member.type) {
605
+ case "relation":
606
+ let binder = new LateBinder(this.relations, (id) => {
607
+ const relation = this.refElems.get(`relation/${id}`);
608
+ if (relation) {
609
+ relation.refCount++;
610
+ return relation;
611
+ }
612
+ this.tainted = true;
613
+ return new Relation(id, this.refElems, false);
614
+ }, this, [member.ref]);
615
+ this.relations.push(binder);
616
+ this.refElems.addBinder(binder);
617
+ break;
618
+ case "way":
619
+ if (!member.role) member.role = "";
620
+ if (member.geometry) {
621
+ const way = new Way(member.ref, this.refElems, false);
622
+ way.setLatLngArray(member.geometry);
623
+ way.refCount++;
624
+ this.ways.push(way);
625
+ this.roles.push(member.role);
626
+ } else if (member.nodes) {
627
+ const way = new Way(member.ref, this.refElems, false);
628
+ for (const nid of member.nodes) way.addNodeRef(nid);
629
+ way.refCount++;
630
+ this.ways.push(way);
631
+ this.roles.push(member.role);
632
+ } else {
633
+ let binder = new LateBinder(this.ways, (nid) => {
634
+ const way = this.refElems.get(`way/${nid}`);
635
+ if (way) {
636
+ way.refCount++;
637
+ return way;
564
638
  }
639
+ this.tainted = true;
640
+ return new Way(nid, this.refElems, false);
565
641
  }, this, [member.ref]);
566
- this.relations.push(binder);
642
+ this.ways.push(binder);
643
+ this.roles.push(member.role);
567
644
  this.refElems.addBinder(binder);
568
- break;
569
- case "way":
570
- if (!member.role) member.role = "";
571
- if (member.geometry) {
572
- const way = new Way(member.ref, this.refElems);
573
- way.setLatLngArray(member.geometry);
574
- way.refCount++;
575
- this.ways.push(way);
576
- this.roles.push(member.role);
577
- } else if (member.nodes) {
578
- const way = new Way(member.ref, this.refElems);
579
- for (const nid of member.nodes) way.addNodeRef(nid);
580
- way.refCount++;
581
- this.ways.push(way);
582
- this.roles.push(member.role);
583
- } else {
584
- let binder = new LateBinder(this.ways, (nid) => {
585
- const way = this.refElems.get(`way/${nid}`);
586
- if (way) {
587
- way.refCount++;
588
- return way;
589
- }
590
- }, this, [member.ref]);
591
- this.ways.push(binder);
592
- this.roles.push(member.role);
593
- this.refElems.addBinder(binder);
594
- }
595
- break;
596
- case "node":
597
- let node = null;
598
- if (member.lat && member.lon) {
599
- node = new Node(member.ref, this.refElems);
600
- node.setLatLng({
601
- lon: member.lon,
602
- lat: member.lat
603
- });
604
- if (member.tags) node.addTags(member.tags);
605
- for (const [k, v] of Object.entries(member)) if ([
606
- "id",
607
- "type",
608
- "lat",
609
- "lon"
610
- ].indexOf(k) < 0) node.addProp(k, v);
611
- node.refCount++;
612
- this.nodes.push(node);
613
- } else {
614
- let binder = new LateBinder(this.nodes, (id) => {
615
- const nn = this.refElems.get(`node/${id}`);
616
- if (nn) {
617
- nn.refCount++;
618
- return nn;
619
- }
620
- }, this, [member.ref]);
621
- this.nodes.push(binder);
622
- this.refElems.addBinder(binder);
623
- }
624
- break;
625
- }
626
- }
627
- constructStringGeometry(ws) {
628
- const strings = ws ? ws.mergeWays() : [];
629
- if (strings.length === 0) return null;
630
- return {
631
- type: "MultiLineString",
632
- coordinates: strings
633
- };
645
+ }
646
+ break;
647
+ case "node":
648
+ let node = null;
649
+ if (member.lat !== void 0 && member.lon !== void 0) {
650
+ node = new Node(member.ref, this.refElems, false);
651
+ node.setLatLng({
652
+ lon: member.lon,
653
+ lat: member.lat
654
+ });
655
+ if (member.tags) node.addTags(member.tags);
656
+ for (const [k, v] of Object.entries(member)) if ([
657
+ "id",
658
+ "type",
659
+ "lat",
660
+ "lon"
661
+ ].indexOf(k) < 0) node.addProp(k, v);
662
+ node.refCount++;
663
+ this.nodes.push(node);
664
+ } else {
665
+ let binder = new LateBinder(this.nodes, (id) => {
666
+ const nn = this.refElems.get(`node/${id}`);
667
+ if (nn) {
668
+ nn.refCount++;
669
+ return nn;
670
+ }
671
+ this.tainted = true;
672
+ }, this, [member.ref]);
673
+ this.nodes.push(binder);
674
+ this.refElems.addBinder(binder);
675
+ }
634
676
  }
635
- constructPolygonGeometry(ows, iws) {
636
- const outerRings = ows ? ows.toRings("counterclockwise") : [];
637
- const innerRings = iws ? iws.toRings("clockwise") : [];
638
- if (outerRings.length > 0) {
639
- const compositPolyons = [];
640
- let ring;
641
- for (ring of outerRings) compositPolyons.push([ring]);
642
- ring = innerRings.shift();
643
- while (ring) {
644
- for (const idx in outerRings) if (pointInsidePolygon(first(ring), outerRings[idx])) {
677
+ }
678
+ constructStringGeometry(ws) {
679
+ const strings = ws ? ws.mergeWays() : [];
680
+ if (strings.length === 0) return null;
681
+ return {
682
+ type: "MultiLineString",
683
+ coordinates: strings
684
+ };
685
+ }
686
+ constructPolygonGeometry(ows, iws) {
687
+ const outerRings = ows ? ows.toRings("counterclockwise") : [];
688
+ const innerRings = iws ? iws.toRings("clockwise") : [];
689
+ if (outerRings.length > 0) {
690
+ const compositPolyons = [];
691
+ const outerBounds = [];
692
+ for (const ring of outerRings) compositPolyons.push([ring]);
693
+ if (innerRings.length > 0) for (const ring of outerRings) outerBounds.push(ringBounds(ring));
694
+ for (let i = 0; i < innerRings.length; i++) {
695
+ const ring = innerRings[i];
696
+ const pt = first(ring);
697
+ for (let idx = 0; idx < outerRings.length; idx++) {
698
+ const b = outerBounds[idx];
699
+ if (pt[0] < b.minX || pt[0] > b.maxX || pt[1] < b.minY || pt[1] > b.maxY) continue;
700
+ if (pointInsidePolygon(pt, outerRings[idx])) {
645
701
  compositPolyons[idx].push(ring);
646
702
  break;
647
703
  }
648
- ring = innerRings.shift();
649
704
  }
650
- if (compositPolyons.length === 1) return {
651
- type: "Polygon",
652
- coordinates: compositPolyons[0]
653
- };
654
- return {
655
- type: "MultiPolygon",
656
- coordinates: compositPolyons
657
- };
658
705
  }
659
- return null;
660
- }
661
- collectAllWaysForRelation(relation, relationToWaysMap) {
662
- const ways = [...relation.ways];
663
- const roles = [...relation.roles];
664
- if (relation.relations.length === 0) {
665
- relationToWaysMap.set(relation.id, {
666
- ways,
667
- roles
706
+ if (compositPolyons.length === 1) return {
707
+ type: "Polygon",
708
+ coordinates: compositPolyons[0]
709
+ };
710
+ return {
711
+ type: "MultiPolygon",
712
+ coordinates: compositPolyons
713
+ };
714
+ }
715
+ return null;
716
+ }
717
+ /**
718
+ * Flatten a relation and its sub-relations into a single way/role list.
719
+ * Duplicate way references (a common cause of duplicated rings) are removed
720
+ * while preserving the first occurrence and its role.
721
+ */
722
+ collectAllWaysForRelation(relation, relationToWaysMap) {
723
+ const cached = relationToWaysMap.get(relation.id);
724
+ if (cached) return cached;
725
+ const ways = [];
726
+ const roles = [];
727
+ const seen = /* @__PURE__ */ new Set();
728
+ const addDirect = (way, role) => {
729
+ if (!way) {
730
+ relation.tainted = true;
731
+ return;
732
+ }
733
+ const key = way.getCompositeId() + "\n" + role;
734
+ if (seen.has(key)) return;
735
+ seen.add(key);
736
+ ways.push(way);
737
+ roles.push(role);
738
+ };
739
+ for (let i = 0; i < relation.ways.length; i++) addDirect(relation.ways[i], relation.roles[i]);
740
+ const seenRelations = /* @__PURE__ */ new Set();
741
+ for (const subRelation of relation.relations) {
742
+ if (!subRelation) {
743
+ relation.tainted = true;
744
+ continue;
745
+ }
746
+ const id = subRelation.getCompositeId();
747
+ if (seenRelations.has(id)) continue;
748
+ seenRelations.add(id);
749
+ const entry = this.collectAllWaysForRelation(subRelation, relationToWaysMap);
750
+ for (let i = 0; i < entry.ways.length; i++) {
751
+ ways.push(entry.ways[i]);
752
+ roles.push(entry.roles[i]);
753
+ }
754
+ }
755
+ const result = {
756
+ ways,
757
+ roles
758
+ };
759
+ relationToWaysMap.set(relation.id, result);
760
+ return result;
761
+ }
762
+ toFeatureArray() {
763
+ const polygonFeatures = [];
764
+ const stringFeatures = [];
765
+ let pointFeatures = [];
766
+ const relationToWaysMap = /* @__PURE__ */ new Map();
767
+ const waysAndRoles = this.collectAllWaysForRelation(this, relationToWaysMap);
768
+ let templateFeature = {
769
+ type: "Feature",
770
+ id: this.getCompositeId(),
771
+ bbox: this.bounds,
772
+ properties: this.getProps(),
773
+ geometry: null
774
+ };
775
+ if (!this.bounds) delete templateFeature.bbox;
776
+ let hasGeometry = false;
777
+ if (waysAndRoles.roles.some((r) => r === "outer")) {
778
+ const outerWayCollection = new WayCollection();
779
+ const innerWayCollection = new WayCollection();
780
+ for (let i = 0; i < waysAndRoles.ways.length; i++) {
781
+ const way = waysAndRoles.ways[i];
782
+ const role = waysAndRoles.roles[i];
783
+ if (role === "outer") outerWayCollection.addWay(way);
784
+ else if (role === "inner") innerWayCollection.addWay(way);
785
+ }
786
+ const geometry = this.constructPolygonGeometry(outerWayCollection, innerWayCollection);
787
+ if (geometry) {
788
+ const feature = Object.assign({}, templateFeature);
789
+ feature.geometry = geometry;
790
+ polygonFeatures.push(feature);
791
+ hasGeometry = true;
792
+ }
793
+ } else {
794
+ const wayCollection = new WayCollection();
795
+ for (const way of waysAndRoles.ways) wayCollection.addWay(way);
796
+ const geometry = this.constructStringGeometry(wayCollection);
797
+ if (geometry) {
798
+ const feature = Object.assign({}, templateFeature);
799
+ feature.geometry = geometry;
800
+ stringFeatures.push(feature);
801
+ hasGeometry = true;
802
+ }
803
+ }
804
+ if (!hasGeometry && this.bounds) {
805
+ const feature = Object.assign({}, templateFeature);
806
+ feature.geometry = {
807
+ type: "Polygon",
808
+ coordinates: [boundsToRing(this.bounds)]
809
+ };
810
+ polygonFeatures.push(feature);
811
+ hasGeometry = true;
812
+ }
813
+ if (!hasGeometry && this.center) {
814
+ const feature = Object.assign({}, templateFeature);
815
+ feature.geometry = {
816
+ type: "Point",
817
+ coordinates: [+this.center.lon, +this.center.lat]
818
+ };
819
+ pointFeatures.push(feature);
820
+ }
821
+ for (const node of this.nodes) pointFeatures = pointFeatures.concat(node.toFeatureArray());
822
+ return [
823
+ ...polygonFeatures,
824
+ ...stringFeatures,
825
+ ...pointFeatures
826
+ ];
827
+ }
828
+ };
829
+ // src/xmlparser.ts
830
+ const XML_ENTITY = /&(#x[0-9a-fA-F]+|#[0-9]+|amp|lt|gt|quot|apos);/g;
831
+ /**
832
+ * Decode the XML entities that occur in OSM data. The cheap `indexOf` check
833
+ * keeps the common case (no entities) allocation-free.
834
+ */
835
+ function decodeEntities(value) {
836
+ if (value.indexOf("&") < 0) return value;
837
+ return value.replace(XML_ENTITY, (match, entity) => {
838
+ switch (entity) {
839
+ case "amp": return "&";
840
+ case "lt": return "<";
841
+ case "gt": return ">";
842
+ case "quot": return "\"";
843
+ case "apos": return "'";
844
+ }
845
+ if (entity.charCodeAt(0) === 35) {
846
+ const code = entity.charCodeAt(1) === 120 || entity.charCodeAt(1) === 88 ? parseInt(entity.slice(2), 16) : parseInt(entity.slice(1), 10);
847
+ if (Number.isFinite(code) && code >= 0 && code <= 1114111) return String.fromCodePoint(code);
848
+ }
849
+ return match;
850
+ });
851
+ }
852
+ // src/osm-xml.ts
853
+ const GT = 62;
854
+ const SLASH = 47;
855
+ const BANG = 33;
856
+ const QUESTION = 63;
857
+ const EQUALS = 61;
858
+ const DQUOTE = 34;
859
+ const SQUOTE = 39;
860
+ const SPACE = 32;
861
+ const TAB = 9;
862
+ const LF = 10;
863
+ const CR = 13;
864
+ function isSpace(c) {
865
+ return c === SPACE || c === TAB || c === LF || c === CR;
866
+ }
867
+ function newDerived() {
868
+ return {
869
+ lineCoords: [],
870
+ geometries: []
871
+ };
872
+ }
873
+ function buildDerivedGeometry(derived) {
874
+ if (derived.lineCoords.length > 0) return {
875
+ type: "LineString",
876
+ coordinates: derived.lineCoords
877
+ };
878
+ if (derived.geometries.length > 0) return {
879
+ type: "GeometryCollection",
880
+ geometries: derived.geometries
881
+ };
882
+ return derived.geometry;
883
+ }
884
+ function addNodeProps(node, attrs) {
885
+ for (const k in attrs) if (k !== "id" && k !== "lon" && k !== "lat") node.addProp(k, attrs[k]);
886
+ }
887
+ function addWayProps(way, attrs) {
888
+ for (const k in attrs) if (k !== "id") way.addProp(k, attrs[k]);
889
+ }
890
+ function addDerivedProps(output, attrs) {
891
+ for (const k in attrs) if (k !== "id" && k !== "type") output.addProp(k, attrs[k]);
892
+ }
893
+ function isRoot(kind) {
894
+ return kind === 0 || kind === 7;
895
+ }
896
+ function finish(frame, parent, refElements) {
897
+ switch (frame.kind) {
898
+ case 1: {
899
+ const attrs = frame.attrs;
900
+ if (frame.hasDerived) {
901
+ const output = new Output("node", frame.id, refElements);
902
+ addDerivedProps(output, attrs);
903
+ if (frame.tags) output.addTags(frame.tags);
904
+ const geometry = buildDerivedGeometry(frame.derived);
905
+ if (geometry) output.setGeometry(geometry);
906
+ } else {
907
+ const node = new Node(frame.id, refElements);
908
+ addNodeProps(node, attrs);
909
+ node.setLatLng({
910
+ lat: attrs.lat,
911
+ lon: attrs.lon
668
912
  });
669
- return {
670
- ways,
671
- roles
672
- };
913
+ if (frame.tags) node.addTags(frame.tags);
673
914
  }
674
- for (const subRelation of relation.relations) {
675
- if (!subRelation) continue;
676
- if (!relationToWaysMap.has(subRelation.id)) this.collectAllWaysForRelation(subRelation, relationToWaysMap);
677
- const entry = relationToWaysMap.get(subRelation.id);
678
- for (let i = 0; i < entry.ways.length; i++) {
679
- ways.push(entry.ways[i]);
680
- roles.push(entry.roles[i]);
681
- }
915
+ return;
916
+ }
917
+ case 2: {
918
+ const way = frame.way;
919
+ if (frame.hasDerived) {
920
+ const output = new Output("way", frame.id, refElements);
921
+ addDerivedProps(output, frame.attrs);
922
+ if (frame.tags) output.addTags(frame.tags);
923
+ const geometry = buildDerivedGeometry(frame.derived);
924
+ if (geometry) output.setGeometry(geometry);
925
+ } else {
926
+ refElements.add(way.getCompositeId(), way);
927
+ addWayProps(way, frame.attrs);
928
+ if (frame.tags) way.addTags(frame.tags);
929
+ if (frame.center) way.setCenter(frame.center);
930
+ if (frame.bounds && !frame.hasNd) way.setBounds(frame.bounds);
682
931
  }
683
- relationToWaysMap.set(relation.id, {
684
- ways,
685
- roles
686
- });
687
- return {
688
- ways,
689
- roles
932
+ return;
933
+ }
934
+ case 3: {
935
+ const relation = frame.relation;
936
+ if (frame.hasDerived) {
937
+ const output = new Output("relation", frame.id, refElements);
938
+ addDerivedProps(output, frame.attrs);
939
+ if (frame.tags) output.addTags(frame.tags);
940
+ const geometry = buildDerivedGeometry(frame.derived);
941
+ if (geometry) output.setGeometry(geometry);
942
+ } else {
943
+ if (frame.tags) relation.addTags(frame.tags);
944
+ if (frame.center) relation.setCenter(frame.center);
945
+ }
946
+ return;
947
+ }
948
+ case 4: {
949
+ const attrs = frame.attrs;
950
+ const member = {
951
+ type: attrs.type,
952
+ role: attrs.role ? attrs.role : "",
953
+ ref: attrs.ref
690
954
  };
955
+ if (attrs.lat && attrs.lon) {
956
+ member.lat = attrs.lat;
957
+ member.lon = attrs.lon;
958
+ member.tags = {};
959
+ for (const k in attrs) if (k !== "type" && k !== "lat" && k !== "lon") member[k] = attrs[k];
960
+ }
961
+ if (frame.member.geometry.length > 0) member.geometry = frame.member.geometry;
962
+ else if (frame.member.nodes.length > 0) member.nodes = frame.member.nodes;
963
+ if (parent && parent.kind === 3 && parent.relation) parent.relation.addMember(member);
964
+ return;
691
965
  }
692
- toFeatureArray() {
693
- const polygonFeatures = [];
694
- const stringFeatures = [];
695
- let pointFeatures = [];
696
- const relationToWaysMap = /* @__PURE__ */ new Map();
697
- const waysAndRoles = this.collectAllWaysForRelation(this, relationToWaysMap);
698
- let templateFeature = {
699
- type: "Feature",
700
- id: this.getCompositeId(),
701
- bbox: this.bounds,
702
- properties: this.getProps(),
703
- geometry: null
966
+ case 5:
967
+ if (parent && (parent.kind === 1 || parent.kind === 2 || parent.kind === 3)) parent.derived.geometry = {
968
+ type: "Polygon",
969
+ coordinates: [frame.lineCoords]
704
970
  };
705
- if (!this.bounds) delete templateFeature.bbox;
706
- if (this.roles.some((r) => r === "outer")) {
707
- const outerWayCollection = new WayCollection();
708
- const innerWayCollection = new WayCollection();
709
- for (let i = 0; i < waysAndRoles.ways.length; i++) {
710
- const way = waysAndRoles.ways[i];
711
- const role = waysAndRoles.roles[i];
712
- if (role === "outer") outerWayCollection.addWay(way);
713
- else if (role === "inner") innerWayCollection.addWay(way);
971
+ return;
972
+ case 6:
973
+ if (parent && (parent.kind === 1 || parent.kind === 2 || parent.kind === 3)) {
974
+ const derived = parent.derived;
975
+ const points = frame.groupPoints;
976
+ for (let i = 0; i < points.length; i++) derived.geometries.push(points[i]);
977
+ const coords = frame.groupCoords;
978
+ if (coords.length > 0) derived.geometries.push({
979
+ type: "LineString",
980
+ coordinates: coords
981
+ });
982
+ }
983
+ return;
984
+ }
985
+ }
986
+ /**
987
+ * Parse OSM/Overpass XML directly into `RefElements`.
988
+ */
989
+ function parseOsmXml(xml, refElements) {
990
+ const len = xml.length;
991
+ const stack = [];
992
+ let i = 0;
993
+ while (i < len) {
994
+ const lt = xml.indexOf("<", i);
995
+ if (lt < 0) break;
996
+ i = lt + 1;
997
+ const c = xml.charCodeAt(i);
998
+ if (c === SLASH) {
999
+ const gt = xml.indexOf(">", i);
1000
+ if (gt < 0) break;
1001
+ i = gt + 1;
1002
+ const frame = stack.pop();
1003
+ if (frame) finish(frame, stack.length ? stack[stack.length - 1] : void 0, refElements);
1004
+ continue;
1005
+ }
1006
+ if (c === QUESTION) {
1007
+ const gt = xml.indexOf(">", i);
1008
+ i = gt < 0 ? len : gt + 1;
1009
+ continue;
1010
+ }
1011
+ if (c === BANG) {
1012
+ if (xml.charCodeAt(i + 1) === 45 && xml.charCodeAt(i + 2) === 45) {
1013
+ const end = xml.indexOf("-->", i + 3);
1014
+ i = end < 0 ? len : end + 3;
1015
+ } else {
1016
+ const gt = xml.indexOf(">", i);
1017
+ i = gt < 0 ? len : gt + 1;
1018
+ }
1019
+ continue;
1020
+ }
1021
+ let j = i;
1022
+ while (j < len) {
1023
+ const ch = xml.charCodeAt(j);
1024
+ if (isSpace(ch) || ch === SLASH || ch === GT) break;
1025
+ j++;
1026
+ }
1027
+ const tag = xml.slice(i, j);
1028
+ const topKind = stack.length ? stack[stack.length - 1].kind : 0;
1029
+ let kind = 7;
1030
+ let attrMode = 0;
1031
+ switch (tag) {
1032
+ case "node":
1033
+ if (isRoot(topKind)) {
1034
+ kind = 1;
1035
+ attrMode = 1;
714
1036
  }
715
- let feature = Object.assign({}, templateFeature);
716
- let geometry = this.constructPolygonGeometry(outerWayCollection, innerWayCollection);
717
- if (geometry) {
718
- feature.geometry = geometry;
719
- polygonFeatures.push(feature);
1037
+ break;
1038
+ case "way":
1039
+ if (isRoot(topKind)) {
1040
+ kind = 2;
1041
+ attrMode = 1;
720
1042
  }
721
- } else {
722
- const wayCollection = new WayCollection();
723
- for (let way of waysAndRoles.ways) wayCollection.addWay(way);
724
- let geometry = this.constructStringGeometry(wayCollection);
725
- if (geometry) {
726
- let feature = Object.assign({}, templateFeature);
727
- feature.geometry = geometry;
728
- stringFeatures.push(feature);
1043
+ break;
1044
+ case "relation":
1045
+ if (isRoot(topKind)) {
1046
+ kind = 3;
1047
+ attrMode = 1;
1048
+ }
1049
+ break;
1050
+ case "osm":
1051
+ kind = 0;
1052
+ break;
1053
+ case "member":
1054
+ if (topKind === 3) {
1055
+ kind = 4;
1056
+ attrMode = 4;
1057
+ }
1058
+ break;
1059
+ case "nd":
1060
+ if (topKind === 2 || topKind === 4) attrMode = 2;
1061
+ break;
1062
+ case "tag":
1063
+ if (topKind === 1 || topKind === 2 || topKind === 3) attrMode = 3;
1064
+ break;
1065
+ case "bounds":
1066
+ if (topKind === 3 || topKind === 2) attrMode = 5;
1067
+ break;
1068
+ case "center":
1069
+ if (topKind === 2 || topKind === 3) attrMode = 6;
1070
+ break;
1071
+ case "point":
1072
+ if (topKind === 1 || topKind === 2 || topKind === 3 || topKind === 6) attrMode = 7;
1073
+ break;
1074
+ case "vertex":
1075
+ if (topKind === 2 || topKind === 3 || topKind === 6 || topKind === 5) attrMode = 7;
1076
+ break;
1077
+ case "linestring":
1078
+ if (topKind === 1 || topKind === 2 || topKind === 3) kind = 5;
1079
+ break;
1080
+ case "group": if (topKind === 1 || topKind === 2 || topKind === 3) kind = 6;
1081
+ }
1082
+ const attrs = attrMode === 1 || attrMode === 4 ? {} : void 0;
1083
+ let ref = "", lat = "", lon = "", k = "", v = "";
1084
+ let minlat = "", minlon = "", maxlat = "", maxlon = "";
1085
+ let selfClose = false;
1086
+ while (j < len) {
1087
+ let ch = xml.charCodeAt(j);
1088
+ while (isSpace(ch)) ch = xml.charCodeAt(++j);
1089
+ if (ch === GT) {
1090
+ j++;
1091
+ break;
1092
+ }
1093
+ if (ch === SLASH) {
1094
+ selfClose = true;
1095
+ j++;
1096
+ continue;
1097
+ }
1098
+ const nameStart = j;
1099
+ while (j < len) {
1100
+ ch = xml.charCodeAt(j);
1101
+ if (ch === EQUALS || isSpace(ch) || ch === GT || ch === SLASH) break;
1102
+ j++;
1103
+ }
1104
+ const attrName = xml.slice(nameStart, j);
1105
+ while (isSpace(xml.charCodeAt(j))) j++;
1106
+ let value = "";
1107
+ if (xml.charCodeAt(j) === EQUALS) {
1108
+ j++;
1109
+ while (isSpace(xml.charCodeAt(j))) j++;
1110
+ const q = xml.charCodeAt(j);
1111
+ if (q === DQUOTE || q === SQUOTE) {
1112
+ j++;
1113
+ let valEnd = xml.indexOf(q === DQUOTE ? "\"" : "'", j);
1114
+ if (valEnd < 0) valEnd = len;
1115
+ value = xml.slice(j, valEnd);
1116
+ if (value.indexOf("&") >= 0) value = decodeEntities(value);
1117
+ j = valEnd + 1;
729
1118
  }
730
1119
  }
731
- for (let node of this.nodes) pointFeatures = pointFeatures.concat(node.toFeatureArray());
732
- return [
733
- ...polygonFeatures,
734
- ...stringFeatures,
735
- ...pointFeatures
736
- ];
1120
+ switch (attrMode) {
1121
+ case 1:
1122
+ case 4:
1123
+ attrs[attrName] = value;
1124
+ break;
1125
+ case 2:
1126
+ if (attrName === "ref") ref = value;
1127
+ else if (attrName === "lat") lat = value;
1128
+ else if (attrName === "lon") lon = value;
1129
+ break;
1130
+ case 3:
1131
+ if (attrName === "k") k = value;
1132
+ else if (attrName === "v") v = value;
1133
+ break;
1134
+ case 5:
1135
+ if (attrName === "minlat") minlat = value;
1136
+ else if (attrName === "minlon") minlon = value;
1137
+ else if (attrName === "maxlat") maxlat = value;
1138
+ else if (attrName === "maxlon") maxlon = value;
1139
+ break;
1140
+ case 6:
1141
+ case 7: if (attrName === "lat") lat = value;
1142
+ else if (attrName === "lon") lon = value;
1143
+ }
737
1144
  }
738
- };
739
- }));
740
- //#endregion
741
- //#region src/ref-elements.ts
742
- var RefElements;
743
- var init_ref_elements = __esmMin((() => {
744
- RefElements = class extends Map {
745
- constructor() {
746
- super();
747
- this.binders = [];
1145
+ i = j;
1146
+ const parent = stack.length ? stack[stack.length - 1] : void 0;
1147
+ if (attrMode === 2) {
1148
+ if (parent) {
1149
+ if (parent.kind === 2 && parent.way) {
1150
+ parent.hasNd = true;
1151
+ if (lat && lon) parent.way.addLatLng({
1152
+ lat,
1153
+ lon
1154
+ });
1155
+ else if (ref) parent.way.addNodeRef(ref);
1156
+ } else if (parent.kind === 4 && parent.member) {
1157
+ if (lat && lon) parent.member.geometry.push({
1158
+ lat,
1159
+ lon
1160
+ });
1161
+ else if (ref) parent.member.nodes.push(ref);
1162
+ }
1163
+ }
1164
+ if (!selfClose) stack.push({ kind: 7 });
1165
+ continue;
748
1166
  }
749
- add(k, v) {
750
- this.set(k, v);
1167
+ if (attrMode === 3) {
1168
+ if (parent && k) {
1169
+ const tags = parent.tags || (parent.tags = {});
1170
+ tags[k] = v;
1171
+ }
1172
+ if (!selfClose) stack.push({ kind: 7 });
1173
+ continue;
751
1174
  }
752
- addBinder(binder) {
753
- this.binders.push(binder);
1175
+ if (attrMode === 5) {
1176
+ if (parent) {
1177
+ const bounds = [
1178
+ parseFloat(minlon),
1179
+ parseFloat(minlat),
1180
+ parseFloat(maxlon),
1181
+ parseFloat(maxlat)
1182
+ ];
1183
+ if (parent.kind === 3 && parent.relation) parent.relation.setBounds(bounds);
1184
+ else if (parent.kind === 2) parent.bounds = bounds;
1185
+ }
1186
+ if (!selfClose) stack.push({ kind: 7 });
1187
+ continue;
754
1188
  }
755
- bindAll() {
756
- this.binders.forEach((binder) => binder.bind());
1189
+ if (attrMode === 6) {
1190
+ if (parent) parent.center = {
1191
+ lat,
1192
+ lon
1193
+ };
1194
+ if (!selfClose) stack.push({ kind: 7 });
1195
+ continue;
757
1196
  }
758
- };
759
- }));
760
- //#endregion
761
- //#region src/index.ts
762
- var require_src = /* @__PURE__ */ __commonJSMin(((exports, module) => {
763
- init_utils();
764
- init_node();
765
- init_way();
766
- init_relation();
767
- init_ref_elements();
768
- function parseOptions(options) {
769
- if (!options) return {
770
- completeFeature: false,
771
- renderTagged: false,
772
- excludeWay: true
773
- };
774
- let excludeWay = options.excludeWay === void 0 || options.excludeWay;
775
- return {
776
- completeFeature: options.completeFeature ? true : false,
777
- renderTagged: options.renderTagged ? true : false,
778
- excludeWay
779
- };
1197
+ if (attrMode === 7) {
1198
+ if (parent) {
1199
+ const x = +lon;
1200
+ const y = +lat;
1201
+ switch (parent.kind) {
1202
+ case 1:
1203
+ case 2:
1204
+ case 3:
1205
+ parent.hasDerived = true;
1206
+ if (tag === "point") parent.derived.geometry = {
1207
+ type: "Point",
1208
+ coordinates: [x, y]
1209
+ };
1210
+ else parent.derived.lineCoords.push([x, y]);
1211
+ break;
1212
+ case 6:
1213
+ if (tag === "point") parent.groupPoints.push({
1214
+ type: "Point",
1215
+ coordinates: [x, y]
1216
+ });
1217
+ else parent.groupCoords.push([x, y]);
1218
+ break;
1219
+ case 5: parent.lineCoords.push([x, y]);
1220
+ }
1221
+ }
1222
+ if (!selfClose) stack.push({ kind: 7 });
1223
+ continue;
1224
+ }
1225
+ let frame;
1226
+ switch (kind) {
1227
+ case 1:
1228
+ frame = {
1229
+ kind,
1230
+ id: attrs.id,
1231
+ attrs,
1232
+ derived: newDerived(),
1233
+ hasDerived: false
1234
+ };
1235
+ break;
1236
+ case 2:
1237
+ frame = {
1238
+ kind,
1239
+ id: attrs.id,
1240
+ attrs,
1241
+ way: new Way(attrs.id, refElements, false),
1242
+ derived: newDerived(),
1243
+ hasDerived: false
1244
+ };
1245
+ break;
1246
+ case 3:
1247
+ frame = {
1248
+ kind,
1249
+ id: attrs.id,
1250
+ attrs,
1251
+ relation: new Relation(attrs.id, refElements),
1252
+ derived: newDerived(),
1253
+ hasDerived: false
1254
+ };
1255
+ break;
1256
+ case 4:
1257
+ frame = {
1258
+ kind,
1259
+ attrs,
1260
+ member: {
1261
+ geometry: [],
1262
+ nodes: []
1263
+ }
1264
+ };
1265
+ break;
1266
+ case 5:
1267
+ if (parent && (parent.kind === 1 || parent.kind === 2 || parent.kind === 3)) parent.hasDerived = true;
1268
+ frame = {
1269
+ kind,
1270
+ lineCoords: []
1271
+ };
1272
+ break;
1273
+ case 6:
1274
+ if (parent && (parent.kind === 1 || parent.kind === 2 || parent.kind === 3)) parent.hasDerived = true;
1275
+ frame = {
1276
+ kind,
1277
+ groupPoints: [],
1278
+ groupCoords: []
1279
+ };
1280
+ break;
1281
+ case 0:
1282
+ case 7: frame = { kind };
1283
+ }
1284
+ if (selfClose) {
1285
+ if (frame) finish(frame, parent, refElements);
1286
+ } else if (frame) stack.push(frame);
780
1287
  }
781
- function detectFormat(o) {
782
- if (o.elements) return "json";
1288
+ }
1289
+ // src/ref-elements.ts
1290
+ var RefElements = class extends Map {
1291
+ constructor() {
1292
+ super();
1293
+ this.binders = [];
1294
+ this.reportTainted = false;
1295
+ this.polygonTags = void 0;
1296
+ }
1297
+ add(k, v) {
1298
+ this.set(k, v);
1299
+ }
1300
+ addBinder(binder) {
1301
+ this.binders.push(binder);
1302
+ }
1303
+ /**
1304
+ * Resolve every deferred reference in one pass per container.
1305
+ *
1306
+ * The previous implementation removed each binder with `indexOf` + `splice`,
1307
+ * which is O(n^2) for containers such as a way's node list. Grouping by
1308
+ * container and compacting once keeps insertion order and makes binding
1309
+ * linear in the number of binders.
1310
+ */
1311
+ bindAll() {
1312
+ if (this.binders.length === 0) return;
1313
+ const groups = /* @__PURE__ */ new Map();
1314
+ for (const binder of this.binders) {
1315
+ const container = binder.getContainer();
1316
+ const list = groups.get(container);
1317
+ if (list) list.push(binder);
1318
+ else groups.set(container, [binder]);
1319
+ }
1320
+ for (const [container, binders] of groups) {
1321
+ const bindersInContainer = new Set(binders);
1322
+ const resolved = /* @__PURE__ */ new Map();
1323
+ for (const binder of binders) resolved.set(binder, binder.resolve());
1324
+ let write = 0;
1325
+ for (let read = 0; read < container.length; read++) {
1326
+ const item = container[read];
1327
+ if (bindersInContainer.has(item)) {
1328
+ const value = resolved.get(item);
1329
+ if (value !== void 0) container[write++] = value;
1330
+ } else container[write++] = item;
1331
+ }
1332
+ container.length = write;
1333
+ }
1334
+ this.binders = [];
1335
+ }
1336
+ };
1337
+ // src/index.ts
1338
+ function parseOptions(options) {
1339
+ if (!options) return {
1340
+ completeFeature: false,
1341
+ renderTagged: false,
1342
+ excludeWay: true,
1343
+ elementId: void 0,
1344
+ tainted: false,
1345
+ polygonTags: void 0
1346
+ };
1347
+ let excludeWay = options.excludeWay === void 0 || options.excludeWay;
1348
+ let completeFeature = options.completeFeature ? true : false;
1349
+ let renderTagged = options.renderTagged ? true : false;
1350
+ let tainted = options.tainted ? true : false;
1351
+ return {
1352
+ completeFeature,
1353
+ renderTagged,
1354
+ excludeWay,
1355
+ elementId: options.elementId,
1356
+ tainted,
1357
+ polygonTags: options.polygonTags
1358
+ };
1359
+ }
1360
+ function detectFormat(o) {
1361
+ if (o && typeof o === "object" && o.elements) return "json";
1362
+ if (typeof o === "string") {
783
1363
  if (o.indexOf("<osm") >= 0) return "xml";
784
1364
  if (o.trim().startsWith("{")) return "json-raw";
785
- return "invalid";
786
1365
  }
787
- function analyzeFeaturesFromJson(osm, refElements) {
788
- for (const elem of osm.elements) switch (elem.type) {
1366
+ return "invalid";
1367
+ }
1368
+ function analyzeFeaturesFromJson(osm, refElements) {
1369
+ for (const elem of osm.elements) {
1370
+ if (elem.geometry && typeof elem.geometry === "object" && elem.geometry.type) {
1371
+ const output = new Output(elem.type, elem.id, refElements);
1372
+ if (elem.tags) output.addTags(elem.tags);
1373
+ output.addProps(purgeProps(elem, [
1374
+ "id",
1375
+ "type",
1376
+ "tags",
1377
+ "geometry"
1378
+ ]));
1379
+ output.setGeometry(elem.geometry);
1380
+ continue;
1381
+ }
1382
+ switch (elem.type) {
789
1383
  case "node":
790
1384
  const node = new Node(elem.id, refElements);
791
1385
  if (elem.tags) node.addTags(elem.tags);
@@ -809,7 +1403,14 @@ var require_src = /* @__PURE__ */ __commonJSMin(((exports, module) => {
809
1403
  "geometry"
810
1404
  ]));
811
1405
  if (elem.geometry) way.setLatLngArray(elem.geometry);
1406
+ else if (elem.center) way.setCenter(elem.center);
812
1407
  else if (elem.nodes) for (const n of elem.nodes) way.addNodeRef(n);
1408
+ else if (elem.bounds) way.setBounds([
1409
+ parseFloat(elem.bounds.minlon),
1410
+ parseFloat(elem.bounds.minlat),
1411
+ parseFloat(elem.bounds.maxlon),
1412
+ parseFloat(elem.bounds.maxlat)
1413
+ ]);
813
1414
  break;
814
1415
  case "relation":
815
1416
  const relation = new Relation(elem.id, refElements);
@@ -820,6 +1421,7 @@ var require_src = /* @__PURE__ */ __commonJSMin(((exports, module) => {
820
1421
  parseFloat(elem.bounds.maxlat)
821
1422
  ]);
822
1423
  if (elem.tags) relation.addTags(elem.tags);
1424
+ if (elem.center) relation.setCenter(elem.center);
823
1425
  relation.addProps(purgeProps(elem, [
824
1426
  "id",
825
1427
  "type",
@@ -828,97 +1430,40 @@ var require_src = /* @__PURE__ */ __commonJSMin(((exports, module) => {
828
1430
  "members"
829
1431
  ]));
830
1432
  if (elem.members) for (const member of elem.members) relation.addMember(member);
831
- default: break;
832
1433
  }
833
1434
  }
834
- function analyzeFeaturesFromXml(osm, refElements) {
835
- const xmlParser = new XmlParser({ progressive: true });
836
- xmlParser.on("</osm.node>", (node) => {
837
- const nd = new Node(node.id, refElements);
838
- for (const [k, v] of Object.entries(node)) if (!k.startsWith("$") && [
839
- "id",
840
- "lon",
841
- "lat"
842
- ].indexOf(k) < 0) nd.addProp(k, v);
843
- nd.setLatLng(node);
844
- if (node.$innerNodes) {
845
- for (const ind of node.$innerNodes) if (ind.$tag === "tag") nd.addTag(ind.k, ind.v);
846
- }
847
- });
848
- xmlParser.on("</osm.way>", (node) => {
849
- const way = new Way(node.id, refElements);
850
- for (const [k, v] of Object.entries(node)) if (!k.startsWith("$") && ["id"].indexOf(k) < 0) way.addProp(k, v);
851
- if (node.$innerNodes) {
852
- for (const ind of node.$innerNodes) if (ind.$tag === "nd") {
853
- if (ind.lon && ind.lat) way.addLatLng(ind);
854
- else if (ind.ref) way.addNodeRef(ind.ref);
855
- } else if (ind.$tag === "tag") way.addTag(ind.k, ind.v);
856
- }
857
- });
858
- xmlParser.on("<osm.relation>", (node) => new Relation(node.id, refElements));
859
- xmlParser.on("</osm.relation.member>", (node, parent) => {
860
- const relation = refElements.get(`relation/${parent?.id}`);
861
- const member = {
862
- type: node.type,
863
- role: node.role ? node.role : "",
864
- ref: node.ref
865
- };
866
- if (node.lat && node.lon) {
867
- member.lat = node.lat, member.lon = node.lon, member.tags = {};
868
- for (const [k, v] of Object.entries(node)) if (!k.startsWith("$") && [
869
- "type",
870
- "lat",
871
- "lon"
872
- ].indexOf(k) < 0) member[k] = v;
873
- }
874
- if (node.$innerNodes) {
875
- const geometry = [];
876
- const nodes = [];
877
- for (const ind of node.$innerNodes) if (ind.lat && ind.lon) geometry.push(ind);
878
- else if (ind.ref) nodes.push(ind.ref);
879
- if (geometry.length > 0) member.geometry = geometry;
880
- else if (nodes.length > 0) member.nodes = nodes;
881
- }
882
- relation.addMember(member);
883
- });
884
- xmlParser.on("</osm.relation.bounds>", (node, parent) => {
885
- refElements.get(`relation/${parent?.id}`).setBounds([
886
- parseFloat(node.minlon),
887
- parseFloat(node.minlat),
888
- parseFloat(node.maxlon),
889
- parseFloat(node.maxlat)
890
- ]);
891
- });
892
- xmlParser.on("</osm.relation.tag>", (node, parent) => {
893
- refElements.get(`relation/${parent?.id}`).addTag(node.k, node.v);
894
- });
895
- xmlParser.parse(osm);
896
- }
897
- function osm2geojson(osm, opts) {
898
- let { completeFeature, renderTagged, excludeWay } = parseOptions(opts);
899
- let format = detectFormat(osm);
900
- const refElements = new RefElements();
901
- let featureArray = [];
902
- if (format === "json-raw") {
903
- osm = JSON.parse(osm);
904
- if (osm.elements) format = "json";
905
- else format = "invalid";
906
- }
907
- if (format === "json") analyzeFeaturesFromJson(osm, refElements);
908
- else if (format === "xml") analyzeFeaturesFromXml(osm, refElements);
909
- refElements.bindAll();
910
- for (const v of refElements.values()) {
911
- if (v.refCount > 0 && (!v.hasTag || !renderTagged || v instanceof Way && excludeWay)) continue;
912
- const features = v.toFeatureArray();
913
- if (v instanceof Relation && !completeFeature && features.length > 0) return features[0].geometry;
914
- featureArray = featureArray.concat(features);
915
- }
916
- return {
917
- type: "FeatureCollection",
918
- features: featureArray
919
- };
1435
+ }
1436
+ function analyzeFeaturesFromXml(osm, refElements) {
1437
+ parseOsmXml(osm, refElements);
1438
+ }
1439
+ function osm2geojson(osm, opts) {
1440
+ let { completeFeature, renderTagged, excludeWay, elementId, tainted, polygonTags } = parseOptions(opts);
1441
+ let format = detectFormat(osm);
1442
+ const refElements = new RefElements();
1443
+ refElements.reportTainted = tainted;
1444
+ refElements.polygonTags = polygonTags;
1445
+ let featureArray = [];
1446
+ if (format === "json-raw") {
1447
+ osm = JSON.parse(osm);
1448
+ if (osm.elements) format = "json";
1449
+ else format = "invalid";
920
1450
  }
921
- module.exports = osm2geojson;
922
- }));
923
- //#endregion
924
- export default require_src();
1451
+ if (format === "json") analyzeFeaturesFromJson(osm, refElements);
1452
+ else if (format === "xml") analyzeFeaturesFromXml(osm, refElements);
1453
+ refElements.bindAll();
1454
+ if (elementId) {
1455
+ const element = refElements.get(elementId);
1456
+ return element ? element.toFeatureArray()[0] : void 0;
1457
+ }
1458
+ for (const v of refElements.values()) {
1459
+ if (v.refCount > 0 && (!v.hasTag || !renderTagged || v instanceof Way && excludeWay)) continue;
1460
+ const features = v.toFeatureArray();
1461
+ if (v instanceof Relation && !completeFeature && features.length > 0) return features[0].geometry;
1462
+ for (const feature of features) featureArray.push(feature);
1463
+ }
1464
+ return {
1465
+ type: "FeatureCollection",
1466
+ features: featureArray
1467
+ };
1468
+ }
1469
+ export { osm2geojson as default };