@osmix/geojson 0.0.15 → 0.0.16
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/CHANGELOG.md +24 -0
- package/LICENSE +21 -0
- package/README.md +50 -51
- package/dist/entity-to-feature.d.ts +7 -7
- package/dist/entity-to-feature.d.ts.map +1 -1
- package/dist/entity-to-feature.js +27 -11
- package/dist/entity-to-feature.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/osm-from-geojson.d.ts +2 -2
- package/dist/osm-from-geojson.d.ts.map +1 -1
- package/dist/osm-from-geojson.js +6 -6
- package/dist/osm-from-geojson.js.map +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js.map +1 -1
- package/osmix-geojson-0.0.16.tgz +0 -0
- package/package.json +31 -31
- package/src/entity-to-feature.ts +309 -314
- package/src/index.ts +3 -3
- package/src/osm-from-geojson.ts +266 -287
- package/src/types.ts +31 -35
- package/src/utils.ts +25 -27
- package/test/entity-to-feature.test.ts +56 -59
- package/test/osm-from-geojson.test.ts +855 -866
- package/tsconfig.build.json +6 -6
- package/tsconfig.json +7 -7
package/src/index.ts
CHANGED
|
@@ -30,6 +30,6 @@
|
|
|
30
30
|
* @module @osmix/geojson
|
|
31
31
|
*/
|
|
32
32
|
|
|
33
|
-
export * from "./entity-to-feature.ts"
|
|
34
|
-
export * from "./osm-from-geojson.ts"
|
|
35
|
-
export * from "./types.ts"
|
|
33
|
+
export * from "./entity-to-feature.ts";
|
|
34
|
+
export * from "./osm-from-geojson.ts";
|
|
35
|
+
export * from "./types.ts";
|
package/src/osm-from-geojson.ts
CHANGED
|
@@ -7,23 +7,14 @@
|
|
|
7
7
|
* @module
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import { Osm, type OsmOptions } from "@osmix/core"
|
|
11
|
-
import {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
import type {
|
|
17
|
-
import {
|
|
18
|
-
import type {
|
|
19
|
-
FeatureCollection,
|
|
20
|
-
LineString,
|
|
21
|
-
MultiPolygon,
|
|
22
|
-
Point,
|
|
23
|
-
Polygon,
|
|
24
|
-
} from "geojson"
|
|
25
|
-
import type { ReadOsmDataTypes } from "./types.ts"
|
|
26
|
-
import { readDataAsGeoJSON } from "./utils.ts"
|
|
10
|
+
import { Osm, type OsmOptions, type OsmWriter } from "@osmix/core";
|
|
11
|
+
import { logProgress, type ProgressEvent, progressEvent } from "@osmix/shared/progress";
|
|
12
|
+
import type { OsmRelationMember, OsmTags } from "@osmix/types";
|
|
13
|
+
import { rewindFeature } from "@placemarkio/geojson-rewind";
|
|
14
|
+
import type { FeatureCollection, LineString, MultiPolygon, Point, Polygon } from "geojson";
|
|
15
|
+
|
|
16
|
+
import type { ReadOsmDataTypes } from "./types.ts";
|
|
17
|
+
import { readDataAsGeoJSON } from "./utils.ts";
|
|
27
18
|
|
|
28
19
|
/**
|
|
29
20
|
* Create an Osm index from GeoJSON data.
|
|
@@ -56,18 +47,15 @@ import { readDataAsGeoJSON } from "./utils.ts"
|
|
|
56
47
|
* ```
|
|
57
48
|
*/
|
|
58
49
|
export async function fromGeoJSON(
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
50
|
+
data: ReadOsmDataTypes,
|
|
51
|
+
options: Partial<OsmOptions> = {},
|
|
52
|
+
onProgress: (progress: ProgressEvent) => void = logProgress,
|
|
62
53
|
): Promise<Osm> {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
onProgress(update)
|
|
69
|
-
}
|
|
70
|
-
return osm
|
|
54
|
+
const osm = new Osm(options);
|
|
55
|
+
for (const update of startCreateOsmFromGeoJSON(osm, await readDataAsGeoJSON(data))) {
|
|
56
|
+
onProgress(update);
|
|
57
|
+
}
|
|
58
|
+
return osm;
|
|
71
59
|
}
|
|
72
60
|
|
|
73
61
|
/**
|
|
@@ -91,270 +79,261 @@ export async function fromGeoJSON(
|
|
|
91
79
|
* @yields Progress events during conversion.
|
|
92
80
|
*/
|
|
93
81
|
export function* startCreateOsmFromGeoJSON(
|
|
94
|
-
|
|
95
|
-
|
|
82
|
+
osm: OsmWriter,
|
|
83
|
+
geojson: FeatureCollection<Point | LineString | Polygon | MultiPolygon>,
|
|
96
84
|
): Generator<ProgressEvent> {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
"Finished converting GeoJSON to Osmix, building indexes...",
|
|
321
|
-
)
|
|
322
|
-
|
|
323
|
-
// Build indexes
|
|
324
|
-
osm.buildIndexes()
|
|
325
|
-
osm.buildSpatialIndexes()
|
|
85
|
+
yield progressEvent("Converting GeoJSON to Osmix...");
|
|
86
|
+
|
|
87
|
+
// Map to track nodes by coordinate string for reuse when creating ways and relations
|
|
88
|
+
const nodeMap = new Map<string, number>();
|
|
89
|
+
let nextNodeId = -1;
|
|
90
|
+
let nextWayId = -1;
|
|
91
|
+
let nextRelationId = -1;
|
|
92
|
+
|
|
93
|
+
// Helper to get or create a node for a coordinate
|
|
94
|
+
const getOrCreateNode = (lon: number, lat: number): number => {
|
|
95
|
+
const coordKey = `${lon},${lat}`;
|
|
96
|
+
const existingNodeId = nodeMap.get(coordKey);
|
|
97
|
+
if (existingNodeId !== undefined) {
|
|
98
|
+
return existingNodeId;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const nodeId = nextNodeId--;
|
|
102
|
+
nodeMap.set(coordKey, nodeId);
|
|
103
|
+
osm.nodes.addNode({
|
|
104
|
+
id: nodeId,
|
|
105
|
+
lon,
|
|
106
|
+
lat,
|
|
107
|
+
});
|
|
108
|
+
return nodeId;
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
// Process each feature
|
|
112
|
+
let count = 0;
|
|
113
|
+
for (const feature of geojson.features) {
|
|
114
|
+
// Normalize winding order using rewind (outer rings counterclockwise, inner rings clockwise)
|
|
115
|
+
const normalizedFeature = rewindFeature(feature);
|
|
116
|
+
const tags = propertiesToTags(normalizedFeature.properties);
|
|
117
|
+
const featureId = extractFeatureId(normalizedFeature.id);
|
|
118
|
+
|
|
119
|
+
if (normalizedFeature.geometry?.type === "Point") {
|
|
120
|
+
const [lon, lat] = normalizedFeature.geometry.coordinates;
|
|
121
|
+
if (lon === undefined || lat === undefined)
|
|
122
|
+
throw Error("Invalid GeoJSON coordinates in Point.");
|
|
123
|
+
|
|
124
|
+
const nodeId = featureId ?? nextNodeId--;
|
|
125
|
+
osm.nodes.addNode({
|
|
126
|
+
id: nodeId,
|
|
127
|
+
lon,
|
|
128
|
+
lat,
|
|
129
|
+
tags,
|
|
130
|
+
});
|
|
131
|
+
nodeMap.set(`${lon},${lat}`, nodeId);
|
|
132
|
+
} else if (normalizedFeature.geometry?.type === "LineString") {
|
|
133
|
+
const coordinates = normalizedFeature.geometry.coordinates;
|
|
134
|
+
if (coordinates.length < 2) throw Error("Invalid GeoJSON coordinates in LineString.");
|
|
135
|
+
|
|
136
|
+
// Create or get nodes for each coordinate
|
|
137
|
+
const nodeRefs: number[] = [];
|
|
138
|
+
for (const [lon, lat] of coordinates) {
|
|
139
|
+
if (lon === undefined || lat === undefined)
|
|
140
|
+
throw Error("Invalid GeoJSON coordinates in LineString.");
|
|
141
|
+
const nodeId = getOrCreateNode(lon, lat);
|
|
142
|
+
nodeRefs.push(nodeId);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Create the way
|
|
146
|
+
const wayId = featureId ?? nextWayId--;
|
|
147
|
+
osm.ways.addWay({
|
|
148
|
+
id: wayId,
|
|
149
|
+
refs: nodeRefs,
|
|
150
|
+
tags,
|
|
151
|
+
});
|
|
152
|
+
} else if (normalizedFeature.geometry?.type === "Polygon") {
|
|
153
|
+
const coordinates = normalizedFeature.geometry.coordinates;
|
|
154
|
+
if (coordinates.length === 0) continue;
|
|
155
|
+
|
|
156
|
+
// If the polygon contains holes, create a relation
|
|
157
|
+
const createRelation = coordinates.length > 1;
|
|
158
|
+
|
|
159
|
+
// First ring is outer boundary, subsequent rings are holes
|
|
160
|
+
const outerRing = coordinates[0];
|
|
161
|
+
if (!outerRing || outerRing.length < 3) continue; // Need at least 3 points for a polygon
|
|
162
|
+
|
|
163
|
+
// Create nodes for outer ring
|
|
164
|
+
const outerNodeRefs: number[] = [];
|
|
165
|
+
for (const [lon, lat] of outerRing) {
|
|
166
|
+
if (lon === undefined || lat === undefined)
|
|
167
|
+
throw Error("Invalid GeoJSON coordinates in Polygon.");
|
|
168
|
+
const nodeId = getOrCreateNode(lon, lat);
|
|
169
|
+
outerNodeRefs.push(nodeId);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Ensure the outer ring is closed (first and last node are the same)
|
|
173
|
+
if (outerNodeRefs[0] !== outerNodeRefs[outerNodeRefs.length - 1])
|
|
174
|
+
throw Error("Outer ring of Polygon is not closed.");
|
|
175
|
+
|
|
176
|
+
// Create way for outer ring with area tags
|
|
177
|
+
const outerWayId = createRelation ? nextWayId-- : (featureId ?? nextWayId--);
|
|
178
|
+
osm.ways.addWay({
|
|
179
|
+
id: outerWayId,
|
|
180
|
+
refs: outerNodeRefs,
|
|
181
|
+
tags: createRelation ? { area: "yes" } : { area: "yes", ...tags },
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
// Create separate ways for holes
|
|
185
|
+
const holeWayIds: number[] = [];
|
|
186
|
+
for (let i = 1; i < coordinates.length; i++) {
|
|
187
|
+
const holeRing = coordinates[i];
|
|
188
|
+
if (!holeRing || holeRing.length < 3)
|
|
189
|
+
throw Error("Hole ring of Polygon has less than 3 coordinates.");
|
|
190
|
+
|
|
191
|
+
const holeNodeRefs: number[] = [];
|
|
192
|
+
for (const [lon, lat] of holeRing) {
|
|
193
|
+
if (lon === undefined || lat === undefined)
|
|
194
|
+
throw Error("Invalid GeoJSON coordinates in Polygon.");
|
|
195
|
+
const nodeId = getOrCreateNode(lon, lat);
|
|
196
|
+
holeNodeRefs.push(nodeId);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Ensure the ring is closed
|
|
200
|
+
if (holeNodeRefs[0] !== holeNodeRefs[holeNodeRefs.length - 1])
|
|
201
|
+
throw Error("Hole ring of Polygon is not closed.");
|
|
202
|
+
|
|
203
|
+
// Create way for hole
|
|
204
|
+
const holeWayId = nextWayId--;
|
|
205
|
+
osm.ways.addWay({
|
|
206
|
+
id: holeWayId,
|
|
207
|
+
refs: holeNodeRefs,
|
|
208
|
+
tags: { area: "yes" },
|
|
209
|
+
});
|
|
210
|
+
holeWayIds.push(holeWayId);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (createRelation) {
|
|
214
|
+
osm.relations.addRelation({
|
|
215
|
+
id: featureId ?? nextRelationId--,
|
|
216
|
+
members: [
|
|
217
|
+
{ type: "way", ref: outerWayId, role: "outer" },
|
|
218
|
+
...holeWayIds.map(
|
|
219
|
+
(id) => ({ type: "way", ref: id, role: "inner" }) as OsmRelationMember,
|
|
220
|
+
),
|
|
221
|
+
],
|
|
222
|
+
tags: {
|
|
223
|
+
type: "multipolygon",
|
|
224
|
+
...tags,
|
|
225
|
+
},
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
} else if (normalizedFeature.geometry?.type === "MultiPolygon") {
|
|
229
|
+
const coordinates = normalizedFeature.geometry.coordinates;
|
|
230
|
+
if (coordinates.length === 0) continue;
|
|
231
|
+
|
|
232
|
+
// Process each polygon in the MultiPolygon
|
|
233
|
+
const relationMembers: OsmRelationMember[] = [];
|
|
234
|
+
for (const polygon of coordinates) {
|
|
235
|
+
if (polygon.length === 0) continue;
|
|
236
|
+
|
|
237
|
+
const outerRing = polygon[0];
|
|
238
|
+
if (!outerRing || outerRing.length < 3) continue;
|
|
239
|
+
|
|
240
|
+
// Create nodes for outer ring
|
|
241
|
+
const outerNodeRefs: number[] = [];
|
|
242
|
+
for (const [lon, lat] of outerRing) {
|
|
243
|
+
if (lon === undefined || lat === undefined)
|
|
244
|
+
throw Error("Invalid GeoJSON coordinates in Polygon.");
|
|
245
|
+
const nodeId = getOrCreateNode(lon, lat);
|
|
246
|
+
outerNodeRefs.push(nodeId);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Ensure the ring is closed
|
|
250
|
+
if (outerNodeRefs[0] !== outerNodeRefs[outerNodeRefs.length - 1])
|
|
251
|
+
throw Error("Outer ring of Polygon is not closed.");
|
|
252
|
+
|
|
253
|
+
// Create way for outer ring
|
|
254
|
+
const outerWayId = nextWayId--;
|
|
255
|
+
osm.ways.addWay({
|
|
256
|
+
id: outerWayId,
|
|
257
|
+
refs: outerNodeRefs,
|
|
258
|
+
tags: { area: "yes" },
|
|
259
|
+
});
|
|
260
|
+
relationMembers.push({ type: "way", ref: outerWayId, role: "outer" });
|
|
261
|
+
|
|
262
|
+
// Create separate ways for holes in this polygon
|
|
263
|
+
for (let i = 1; i < polygon.length; i++) {
|
|
264
|
+
const holeRing = polygon[i];
|
|
265
|
+
if (!holeRing || holeRing.length < 3)
|
|
266
|
+
throw Error("Hole ring of Polygon has less than 3 coordinates.");
|
|
267
|
+
|
|
268
|
+
const holeNodeRefs: number[] = [];
|
|
269
|
+
for (const [lon, lat] of holeRing) {
|
|
270
|
+
if (lon === undefined || lat === undefined)
|
|
271
|
+
throw Error("Invalid GeoJSON coordinates in Polygon.");
|
|
272
|
+
const nodeId = getOrCreateNode(lon, lat);
|
|
273
|
+
holeNodeRefs.push(nodeId);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Ensure the ring is closed
|
|
277
|
+
if (holeNodeRefs[0] !== holeNodeRefs[holeNodeRefs.length - 1])
|
|
278
|
+
throw Error("Hole ring of Polygon is not closed.");
|
|
279
|
+
|
|
280
|
+
const holeWayId = nextWayId--;
|
|
281
|
+
osm.ways.addWay({
|
|
282
|
+
id: holeWayId,
|
|
283
|
+
refs: holeNodeRefs,
|
|
284
|
+
tags: { area: "yes" },
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
relationMembers.push({ type: "way", ref: holeWayId, role: "inner" });
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if (relationMembers.length > 0) {
|
|
292
|
+
osm.relations.addRelation({
|
|
293
|
+
id: featureId ?? nextRelationId--,
|
|
294
|
+
members: relationMembers,
|
|
295
|
+
tags: { type: "multipolygon", ...tags },
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
yield progressEvent(`Processed ${count++} features...`);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
yield progressEvent("Finished converting GeoJSON to Osmix, building indexes...");
|
|
304
|
+
|
|
305
|
+
// Build indexes
|
|
306
|
+
osm.buildIndexes();
|
|
307
|
+
osm.buildSpatialIndexes();
|
|
326
308
|
}
|
|
327
309
|
|
|
328
310
|
// Helper to convert properties to tags
|
|
329
|
-
function propertiesToTags(
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
return Object.keys(tags).length > 0 ? tags : undefined
|
|
311
|
+
function propertiesToTags(properties: Record<string, unknown> | null): OsmTags | undefined {
|
|
312
|
+
if (!properties || Object.keys(properties).length === 0) {
|
|
313
|
+
return undefined;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
const tags: OsmTags = {};
|
|
317
|
+
for (const [key, value] of Object.entries(properties)) {
|
|
318
|
+
// Convert value to string or number
|
|
319
|
+
if (typeof value === "string" || typeof value === "number") {
|
|
320
|
+
tags[key] = value;
|
|
321
|
+
} else if (typeof value === "boolean") {
|
|
322
|
+
tags[key] = String(value);
|
|
323
|
+
} else if (value != null && typeof value === "object") {
|
|
324
|
+
tags[key] = JSON.stringify(value);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
return Object.keys(tags).length > 0 ? tags : undefined;
|
|
347
328
|
}
|
|
348
329
|
|
|
349
330
|
// Helper to extract numeric ID from feature
|
|
350
|
-
function extractFeatureId(
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
}
|
|
359
|
-
return undefined
|
|
331
|
+
function extractFeatureId(featureId: string | number | undefined): number | undefined {
|
|
332
|
+
if (featureId === undefined) return undefined;
|
|
333
|
+
if (typeof featureId === "number") return featureId;
|
|
334
|
+
if (typeof featureId === "string") {
|
|
335
|
+
const numId = Number.parseInt(featureId, 10);
|
|
336
|
+
if (!Number.isNaN(numId)) return numId;
|
|
337
|
+
}
|
|
338
|
+
return undefined;
|
|
360
339
|
}
|