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