landxml 0.6.4 → 0.7.1

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.js CHANGED
@@ -77,21 +77,22 @@ var __async = (__this, __arguments, generator) => {
77
77
  };
78
78
 
79
79
  // src/index.ts
80
- var src_exports = {};
81
- __export(src_exports, {
80
+ var index_exports = {};
81
+ __export(index_exports, {
82
+ precomputeSurfaceData: () => precomputeSurfaceData,
82
83
  reprojectGeoJson: () => reproject_geojson_default,
83
84
  toGeojsonContours: () => to_geojson_contours_default,
84
- toGlb: () => to_glb_default
85
+ toGlb: () => to_glb_default,
86
+ toGlbAndContours: () => to_glb_and_contours_default
85
87
  });
86
- module.exports = __toCommonJS(src_exports);
88
+ module.exports = __toCommonJS(index_exports);
87
89
 
88
90
  // src/private/filter-by-surfaceId.ts
89
91
  var filterBySurfaceId = (parsedSurfaces, surfaceId) => {
90
92
  let filtered = [...parsedSurfaces];
91
93
  if (typeof surfaceId === "string") {
92
94
  filtered = filtered.filter((s) => s.name === surfaceId);
93
- if (filtered.length === 0)
94
- throw "Provided SurfaceId doesn't exist within provided LandXML";
95
+ if (filtered.length === 0) throw "Provided SurfaceId doesn't exist within provided LandXML";
95
96
  }
96
97
  if (typeof surfaceId === "number" && surfaceId > 0) {
97
98
  if (!filtered[surfaceId])
@@ -112,20 +113,32 @@ var findXYAxisMedians = (vertices) => {
112
113
  const medianY = (_b = vertices.slice().sort((a, b) => a[1] - b[1])[middleIndex]) == null ? void 0 : _b[1];
113
114
  return [medianX, medianY];
114
115
  };
115
- var getGlb = (data, customCenter) => __async(void 0, null, function* () {
116
+ var getGlb = (data, customCenter) => __async(null, null, function* () {
116
117
  const center = customCenter || findXYAxisMedians(data.surfaceDefinition.points);
117
- const vertices = [...data.surfaceDefinition.points].map((p) => p.slice()).map(([x, y, z]) => {
118
- return [x - center[0], z, -(y - center[1])];
119
- }).reduce((prev, curr) => prev.concat(curr), []);
120
- const triangles = data.surfaceDefinition.faces.reduce((prev, curr) => prev.concat(curr), []);
118
+ const pts = data.surfaceDefinition.points;
119
+ const facesFlat = data.surfaceDefinition.faces;
120
+ const vertexBuffer = new Float32Array(pts.length * 3);
121
+ for (let i = 0; i < pts.length; i++) {
122
+ const [x, y, z] = pts[i];
123
+ vertexBuffer[i * 3] = x - center[0];
124
+ vertexBuffer[i * 3 + 1] = z;
125
+ vertexBuffer[i * 3 + 2] = -(y - center[1]);
126
+ }
127
+ const indexBuffer = new Uint32Array(facesFlat.length * 3);
128
+ for (let i = 0; i < facesFlat.length; i++) {
129
+ const [a, b, c] = facesFlat[i];
130
+ indexBuffer[i * 3] = a;
131
+ indexBuffer[i * 3 + 1] = b;
132
+ indexBuffer[i * 3 + 2] = c;
133
+ }
121
134
  const doc = new import_core.Document();
122
135
  const buffer = doc.createBuffer();
123
- const position = doc.createAccessor().setType("VEC3").setArray(new Float32Array(vertices)).setBuffer(buffer);
124
- const indices = doc.createAccessor().setType("SCALAR").setArray(new Uint32Array(triangles)).setBuffer(buffer);
136
+ const position = doc.createAccessor().setType("VEC3").setArray(vertexBuffer).setBuffer(buffer);
137
+ const indices = doc.createAccessor().setType("SCALAR").setArray(indexBuffer).setBuffer(buffer);
125
138
  const prim = doc.createPrimitive().setAttribute("POSITION", position).setIndices(indices);
126
139
  const mesh = doc.createMesh().addPrimitive(prim);
127
140
  const node = doc.createNode().setMesh(mesh);
128
- const scene = doc.createScene().addChild(node);
141
+ doc.createScene().addChild(node);
129
142
  const glb = yield new import_core.WebIO().writeBinary(doc);
130
143
  return { glb, center };
131
144
  });
@@ -164,20 +177,34 @@ var surfaceDefWorker = (0, import_easy_web_worker.createEasyWebWorker)(
164
177
  var _a;
165
178
  if (typeof f === "string")
166
179
  return [f.split(" ").map((id) => idMap == null ? void 0 : idMap.indexOf(id))];
167
- if (((_a = f == null ? void 0 : f.attr) == null ? void 0 : _a.i) === "1")
168
- return [];
180
+ if (((_a = f == null ? void 0 : f.attr) == null ? void 0 : _a.i) === "1") return [];
169
181
  return [f.content.split(" ").map((id) => idMap == null ? void 0 : idMap.indexOf(id))];
170
182
  })
171
183
  );
172
184
  } else if (task === "find-neighboring-faces") {
173
- const { faces, range: { start, end } } = message.payload;
174
- const faceNeighbors = [];
175
- for (let i = start; i < end; i++) {
176
- const sourceFace = faces[i];
177
- const neighborA = faces.findIndex((f, j) => f.findIndex((v) => v === sourceFace[0]) >= 0 && f.findIndex((v) => v === sourceFace[1]) >= 0 && j !== i);
178
- const neighborB = faces.findIndex((f, j) => f.findIndex((v) => v === sourceFace[1]) >= 0 && f.findIndex((v) => v === sourceFace[2]) >= 0 && j !== i);
179
- const neighborC = faces.findIndex((f, j) => f.findIndex((v) => v === sourceFace[0]) >= 0 && f.findIndex((v) => v === sourceFace[2]) >= 0 && j !== i);
180
- faceNeighbors.push([neighborA, neighborB, neighborC]);
185
+ const { faces } = message.payload;
186
+ const edgeMap = /* @__PURE__ */ new Map();
187
+ const edgeKey = (a, b) => a < b ? `${a},${b}` : `${b},${a}`;
188
+ for (let i = 0; i < faces.length; i++) {
189
+ const [v0, v1, v2] = faces[i];
190
+ for (const key of [edgeKey(v0, v1), edgeKey(v1, v2), edgeKey(v0, v2)]) {
191
+ const existing = edgeMap.get(key);
192
+ if (!existing) {
193
+ edgeMap.set(key, [i]);
194
+ } else if (existing.length === 1) {
195
+ existing.push(i);
196
+ }
197
+ }
198
+ }
199
+ const faceNeighbors = new Array(faces.length);
200
+ for (let i = 0; i < faces.length; i++) {
201
+ const [v0, v1, v2] = faces[i];
202
+ const resolve = (key) => {
203
+ const pair = edgeMap.get(key);
204
+ if (!pair || pair.length < 2) return -1;
205
+ return pair[0] === i ? pair[1] : pair[0];
206
+ };
207
+ faceNeighbors[i] = [resolve(edgeKey(v0, v1)), resolve(edgeKey(v1, v2)), resolve(edgeKey(v0, v2))];
181
208
  }
182
209
  message.resolve(faceNeighbors);
183
210
  }
@@ -188,126 +215,83 @@ var surfaceDefWorker = (0, import_easy_web_worker.createEasyWebWorker)(
188
215
  },
189
216
  { maxWorkers: 16 }
190
217
  );
191
- var parseXML = (xmlString) => __async(void 0, null, function* () {
192
- return new Promise((resolve, reject) => __async(void 0, null, function* () {
193
- var _a, _b, _c, _d, _e;
194
- const parsed = import_xml_js.default.xml2js(xmlString, {
195
- compact: true,
196
- attributesKey: "attr",
197
- textKey: "content"
198
- });
199
- if (typeof ((_b = (_a = parsed.LandXML) == null ? void 0 : _a.Surfaces) == null ? void 0 : _b.Surface) === "undefined") {
200
- throw new Error("LandXML doesn't contain any surfaces");
201
- return;
202
- }
203
- if (!Array.isArray(parsed.LandXML.Surfaces.Surface)) {
204
- parsed.LandXML.Surfaces.Surface = [parsed.LandXML.Surfaces.Surface];
205
- }
206
- let sourceFile = parsed.LandXML.Project.attr.name || "Undefined source";
207
- let timeStamp = parsed.LandXML.Application.attr.timeStamp || "";
208
- let wktString = ((_e = (_d = (_c = parsed.LandXML) == null ? void 0 : _c.CoordinateSystem) == null ? void 0 : _d.attr) == null ? void 0 : _e.ogcWktCode) || void 0;
209
- const surfaces = parsed.LandXML.Surfaces.Surface.map(
210
- (surface) => __async(void 0, null, function* () {
211
- return new Promise((resolve2, reject2) => __async(void 0, null, function* () {
212
- const { name, desc } = surface.attr;
213
- const Pnts = surface.Definition.Pnts.P;
214
- const Faces = surface.Definition.Faces.F;
215
- let ptsIdArray = [];
216
- let faces = [];
217
- let faceNeighbors = [];
218
- if (Pnts.length > 1e4) {
219
- const sliceIndexes = [...Array(20).keys()].map((i) => Math.round(Pnts.length / 20 * i));
220
- ptsIdArray = (yield Promise.all(
221
- sliceIndexes.map(
222
- (v, i, a) => new Promise((resolve3, reject3) => __async(void 0, null, function* () {
223
- const pts = yield surfaceDefWorker.send({
224
- task: "parse-surface-points",
225
- points: Pnts.slice(a[i], a[i + 1] || Pnts.length)
226
- });
227
- resolve3(pts);
228
- }))
229
- )
230
- )).reduce((prev, curr) => [...prev, ...curr], []);
231
- } else {
232
- ptsIdArray = yield surfaceDefWorker.send({ task: "parse-surface-points", points: Pnts });
233
- }
234
- const points = ptsIdArray.map((v) => v[1]);
235
- const pointsIdMap = ptsIdArray.map((v) => v[0]);
236
- if (Faces.length > 1e4) {
237
- const sliceIndexes = [...Array(20).keys()].map((i) => Math.round(Faces.length / 20 * i));
238
- faces = (yield Promise.all(
239
- sliceIndexes.map(
240
- (v, i, a) => new Promise((resolve3, reject3) => __async(void 0, null, function* () {
241
- const fcs = yield surfaceDefWorker.send({
242
- task: "parse-surface-faces",
243
- faces: Faces.slice(a[i], a[i + 1] || Faces.length),
244
- idMap: pointsIdMap
245
- });
246
- resolve3(fcs);
247
- }))
248
- )
249
- )).reduce((prev, curr) => [...prev, ...curr], []);
250
- } else {
251
- faces = yield surfaceDefWorker.send({
252
- task: "parse-surface-faces",
253
- faces: Faces,
254
- idMap: pointsIdMap
255
- });
256
- }
257
- if (Faces.length > 1e4) {
258
- const sliceIndexes = [...Array(20).keys()].map((i) => Math.round((faces.length - 1) / 20 * i));
259
- faceNeighbors = (yield Promise.all(
260
- sliceIndexes.map(
261
- (v, i, a) => new Promise((resolve3, reject3) => __async(void 0, null, function* () {
262
- const fcs = yield surfaceDefWorker.send({
263
- task: "find-neighboring-faces",
264
- faces,
265
- range: {
266
- start: a[i],
267
- end: a[i + 1] || faces.length
268
- }
269
- });
270
- resolve3(fcs);
271
- }))
272
- )
273
- )).reduce((prev, curr) => [...prev, ...curr], []);
274
- } else {
275
- faceNeighbors = yield surfaceDefWorker.send({
276
- task: "find-neighboring-faces",
277
- faces,
278
- range: {
279
- start: 0,
280
- end: faces.length
281
- }
282
- });
283
- }
284
- resolve2({
285
- sourceFile,
286
- timeStamp: timeStamp || "",
287
- name,
288
- description: desc || "",
289
- wktString,
290
- surfaceDefinition: {
291
- points,
292
- faces,
293
- faceNeighbors
294
- }
295
- });
296
- }));
218
+ function parallelChunks(items, count, makePayload) {
219
+ return __async(this, null, function* () {
220
+ const sliceIndexes = [...Array(count).keys()].map((i) => Math.round(items.length / count * i));
221
+ const chunks = yield Promise.all(
222
+ sliceIndexes.map((start, i, arr) => {
223
+ var _a;
224
+ const end = (_a = arr[i + 1]) != null ? _a : items.length;
225
+ return surfaceDefWorker.send(makePayload(items.slice(start, end), start, end));
297
226
  })
298
227
  );
299
- resolve(yield Promise.all(surfaces));
228
+ return chunks.reduce((acc, c) => acc.concat(c), []);
229
+ });
230
+ }
231
+ var CHUNK_THRESHOLD = 1e4;
232
+ var CHUNK_COUNT = 20;
233
+ var parseXML = (xmlString) => __async(null, null, function* () {
234
+ var _a, _b, _c, _d, _e;
235
+ const parsed = import_xml_js.default.xml2js(xmlString, {
236
+ compact: true,
237
+ attributesKey: "attr",
238
+ textKey: "content"
239
+ });
240
+ if (typeof ((_b = (_a = parsed.LandXML) == null ? void 0 : _a.Surfaces) == null ? void 0 : _b.Surface) === "undefined") {
241
+ throw new Error("LandXML doesn't contain any surfaces");
242
+ }
243
+ if (!Array.isArray(parsed.LandXML.Surfaces.Surface)) {
244
+ parsed.LandXML.Surfaces.Surface = [parsed.LandXML.Surfaces.Surface];
245
+ }
246
+ const sourceFile = parsed.LandXML.Project.attr.name || "Undefined source";
247
+ const timeStamp = parsed.LandXML.Application.attr.timeStamp || "";
248
+ const wktString = ((_e = (_d = (_c = parsed.LandXML) == null ? void 0 : _c.CoordinateSystem) == null ? void 0 : _d.attr) == null ? void 0 : _e.ogcWktCode) || void 0;
249
+ const surfaces = parsed.LandXML.Surfaces.Surface.map((surface) => __async(null, null, function* () {
250
+ const { name, desc } = surface.attr;
251
+ const Pnts = surface.Definition.Pnts.P;
252
+ const Faces = surface.Definition.Faces.F;
253
+ const ptsIdArray = Pnts.length > CHUNK_THRESHOLD ? yield parallelChunks(Pnts, CHUNK_COUNT, (chunk) => ({
254
+ task: "parse-surface-points",
255
+ points: chunk
256
+ })) : yield surfaceDefWorker.send({
257
+ task: "parse-surface-points",
258
+ points: Pnts
259
+ });
260
+ const points = ptsIdArray.map((v) => v[1]);
261
+ const pointsIdMap = ptsIdArray.map((v) => v[0]);
262
+ const faces = Faces.length > CHUNK_THRESHOLD ? yield parallelChunks(Faces, CHUNK_COUNT, (chunk) => ({
263
+ task: "parse-surface-faces",
264
+ faces: chunk,
265
+ idMap: pointsIdMap
266
+ })) : yield surfaceDefWorker.send({
267
+ task: "parse-surface-faces",
268
+ faces: Faces,
269
+ idMap: pointsIdMap
270
+ });
271
+ const faceNeighbors = yield surfaceDefWorker.send({
272
+ task: "find-neighboring-faces",
273
+ faces
274
+ });
275
+ return {
276
+ sourceFile,
277
+ timeStamp,
278
+ name,
279
+ description: desc || "",
280
+ wktString,
281
+ surfaceDefinition: { points, faces, faceNeighbors }
282
+ };
300
283
  }));
284
+ return Promise.all(surfaces);
301
285
  });
302
286
  var parse_xml_default = parseXML;
303
287
 
304
288
  // src/public/to-glb.ts
305
- var toGlb = (landXmlString, center = "auto", surfaceId = -1) => __async(void 0, null, function* () {
289
+ var toGlb = (landXmlString, center = "auto", surfaceId = -1) => __async(null, null, function* () {
306
290
  const requestedCenter = center == "origin" ? [0, 0] : center === "auto" ? void 0 : center;
307
291
  let requestedParsedSurfaces = filter_by_surfaceId_default(yield parse_xml_default(landXmlString), surfaceId);
308
292
  const glbs = yield Promise.all(
309
293
  requestedParsedSurfaces.map(
310
- (surface) => new Promise((resolve, reject) => __async(void 0, null, function* () {
294
+ (surface) => new Promise((resolve, reject) => __async(null, null, function* () {
311
295
  try {
312
296
  const { glb, center: center2 } = yield get_glb_default(surface, requestedCenter);
313
297
  const _a = surface, { surfaceDefinition } = _a, rest = __objRest(_a, ["surfaceDefinition"]);
@@ -336,52 +320,45 @@ var contoursWorker = (0, import_easy_web_worker2.createEasyWebWorker)(
336
320
  let vertsAtElevation = 0;
337
321
  let line = [];
338
322
  for (let i = 0; i < face.length; i++) {
339
- let vertex1 = face[i];
340
- let vertex2 = face[(i + 1) % face.length];
341
- if (vertex1[2] === z)
342
- vertsAtElevation++;
323
+ const vertex1 = face[i];
324
+ const vertex2 = face[(i + 1) % face.length];
325
+ if (vertex1[2] === z) vertsAtElevation++;
343
326
  if ((vertex1[2] <= z && vertex2[2] >= z || vertex1[2] >= z && vertex2[2] <= z) && !Number.isNaN((z - vertex1[2]) / (vertex2[2] - vertex1[2]))) {
344
- let t = (z - vertex1[2]) / (vertex2[2] - vertex1[2]);
327
+ const t = (z - vertex1[2]) / (vertex2[2] - vertex1[2]);
345
328
  line.push([vertex1[0] + t * (vertex2[0] - vertex1[0]), vertex1[1] + t * (vertex2[1] - vertex1[1])]);
346
329
  }
347
330
  }
348
- if (vertsAtElevation >= 2 && face.map((f) => f[2]).reduce((a, b) => a + b) > z * face.length)
349
- return void 0;
350
- if (line.length === 2 && line[0][0] === line[1][0] && line[0][1] === line[1][1])
351
- return void 0;
331
+ if (vertsAtElevation >= 2 && face.map((f) => f[2]).reduce((a, b) => a + b) > z * face.length) return void 0;
332
+ if (line.length === 2 && line[0][0] === line[1][0] && line[0][1] === line[1][1]) return void 0;
352
333
  if (line.length > 2) {
353
334
  line = [...new Set(line.map((v) => JSON.stringify(v)))].map((s) => JSON.parse(s));
354
335
  }
355
336
  return line.length > 0 ? line : void 0;
356
337
  };
357
338
  const linesToPolyLines3 = (lineSegments) => {
358
- var _a, _b;
359
- if (!Array.isArray(lineSegments) || (lineSegments == null ? void 0 : lineSegments.length) === 0) {
360
- return [];
361
- }
339
+ if (!Array.isArray(lineSegments) || lineSegments.length === 0) return [];
362
340
  const segmentsMapIndexes = {};
363
341
  const polylines = [];
364
- const parsedSegmentIndexes = [];
342
+ const parsedSegmentIndexes = /* @__PURE__ */ new Set();
365
343
  const lineSegmentStrings = lineSegments.map((v) => v.map((c) => c.join(",")));
366
- lineSegmentStrings.forEach(([start, end], i) => {
367
- segmentsMapIndexes[start] = segmentsMapIndexes[start] ? [...segmentsMapIndexes[start] || [], i] : [i];
368
- segmentsMapIndexes[end] = segmentsMapIndexes[end] ? [...segmentsMapIndexes[end] || [], i] : [i];
369
- });
370
344
  for (let i = 0; i < lineSegmentStrings.length; i++) {
371
- if (parsedSegmentIndexes.includes(i))
372
- continue;
373
- parsedSegmentIndexes.push(i);
345
+ const [start, end] = lineSegmentStrings[i];
346
+ segmentsMapIndexes[start] = segmentsMapIndexes[start] ? [...segmentsMapIndexes[start], i] : [i];
347
+ segmentsMapIndexes[end] = segmentsMapIndexes[end] ? [...segmentsMapIndexes[end], i] : [i];
348
+ }
349
+ for (let i = 0; i < lineSegmentStrings.length; i++) {
350
+ if (parsedSegmentIndexes.has(i)) continue;
351
+ parsedSegmentIndexes.add(i);
374
352
  let [start, end] = lineSegmentStrings[i];
375
- let polyline = [start, end];
353
+ const polyline = [start, end];
376
354
  while (start && segmentsMapIndexes[start]) {
377
- const nextLineIndex = (_a = segmentsMapIndexes[start]) == null ? void 0 : _a.find(
378
- (lineIndex) => !parsedSegmentIndexes.includes(lineIndex)
355
+ const nextLineIndex = segmentsMapIndexes[start].find(
356
+ (li) => !parsedSegmentIndexes.has(li)
379
357
  );
380
- if (nextLineIndex) {
381
- parsedSegmentIndexes.push(nextLineIndex);
382
- const nextLineSegment = lineSegmentStrings[nextLineIndex];
383
- const nextLineSegmentPointIndex = nextLineSegment[0] === start ? 1 : 0;
384
- const newPoint = nextLineSegment[nextLineSegmentPointIndex];
358
+ if (nextLineIndex !== void 0) {
359
+ parsedSegmentIndexes.add(nextLineIndex);
360
+ const [a, b] = lineSegmentStrings[nextLineIndex];
361
+ const newPoint = a === start ? b : a;
385
362
  polyline.unshift(newPoint);
386
363
  start = newPoint;
387
364
  } else {
@@ -389,14 +366,13 @@ var contoursWorker = (0, import_easy_web_worker2.createEasyWebWorker)(
389
366
  }
390
367
  }
391
368
  while (end && segmentsMapIndexes[end]) {
392
- const nextLineIndex = (_b = segmentsMapIndexes[end]) == null ? void 0 : _b.find(
393
- (lineIndex) => !parsedSegmentIndexes.includes(lineIndex)
369
+ const nextLineIndex = segmentsMapIndexes[end].find(
370
+ (li) => !parsedSegmentIndexes.has(li)
394
371
  );
395
- if (nextLineIndex) {
396
- parsedSegmentIndexes.push(nextLineIndex);
397
- const nextLineSegment = lineSegmentStrings[nextLineIndex];
398
- const nextLineSegmentPointIndex = nextLineSegment[0] === end ? 1 : 0;
399
- const newPoint = nextLineSegment[nextLineSegmentPointIndex];
372
+ if (nextLineIndex !== void 0) {
373
+ parsedSegmentIndexes.add(nextLineIndex);
374
+ const [a, b] = lineSegmentStrings[nextLineIndex];
375
+ const newPoint = a === end ? b : a;
400
376
  polyline.push(newPoint);
401
377
  end = newPoint;
402
378
  } else {
@@ -409,68 +385,51 @@ var contoursWorker = (0, import_easy_web_worker2.createEasyWebWorker)(
409
385
  };
410
386
  onMessage((message) => {
411
387
  const { triangles, elevation } = message.payload;
412
- const linesAtElevationE = triangles.reduce((prev, curr) => {
388
+ const linesAtElevation = triangles.reduce((prev, curr) => {
413
389
  const line = contourLineOnFace(curr, elevation);
414
- if (line)
415
- prev.push(line);
390
+ if (line) prev.push(line);
416
391
  return prev;
417
392
  }, []);
418
- message.resolve({
419
- elevation,
420
- polylines: linesToPolyLines3(linesAtElevationE)
421
- });
393
+ message.resolve({ elevation, polylines: linesToPolyLines3(linesAtElevation) });
422
394
  });
423
395
  },
424
396
  { maxWorkers: 10 }
425
397
  );
426
398
  var linesToPolyLines = (lineSegments) => {
427
- var _a, _b;
428
- if (!Array.isArray(lineSegments) || (lineSegments == null ? void 0 : lineSegments.length) === 0) {
429
- return [];
430
- }
399
+ if (!Array.isArray(lineSegments) || lineSegments.length === 0) return [];
431
400
  const segmentsMapIndexes = {};
432
401
  const polylines = [];
433
- const parsedSegmentIndexes = [];
402
+ const parsedSegmentIndexes = /* @__PURE__ */ new Set();
434
403
  const lineSegmentStrings = lineSegments.map((v) => v.map((c) => c.join(",")));
435
- lineSegmentStrings.forEach(([start, end], i) => {
436
- segmentsMapIndexes[start] = segmentsMapIndexes[start] ? [...segmentsMapIndexes[start] || [], i] : [i];
437
- segmentsMapIndexes[end] = segmentsMapIndexes[end] ? [...segmentsMapIndexes[end] || [], i] : [i];
438
- });
439
404
  for (let i = 0; i < lineSegmentStrings.length; i++) {
440
- if (parsedSegmentIndexes.includes(i))
441
- continue;
442
- parsedSegmentIndexes.push(i);
405
+ const [start, end] = lineSegmentStrings[i];
406
+ segmentsMapIndexes[start] = segmentsMapIndexes[start] ? [...segmentsMapIndexes[start], i] : [i];
407
+ segmentsMapIndexes[end] = segmentsMapIndexes[end] ? [...segmentsMapIndexes[end], i] : [i];
408
+ }
409
+ for (let i = 0; i < lineSegmentStrings.length; i++) {
410
+ if (parsedSegmentIndexes.has(i)) continue;
411
+ parsedSegmentIndexes.add(i);
443
412
  let [start, end] = lineSegmentStrings[i];
444
- let polyline = [start, end];
413
+ const polyline = [start, end];
445
414
  while (start && segmentsMapIndexes[start]) {
446
- const nextLineIndex = (_a = segmentsMapIndexes[start]) == null ? void 0 : _a.find(
447
- (lineIndex) => !parsedSegmentIndexes.includes(lineIndex)
448
- );
449
- if (nextLineIndex) {
450
- parsedSegmentIndexes.push(nextLineIndex);
451
- const nextLineSegment = lineSegmentStrings[nextLineIndex];
452
- const nextLineSegmentPointIndex = nextLineSegment[0] === start ? 1 : 0;
453
- const newPoint = nextLineSegment[nextLineSegmentPointIndex];
415
+ const nextLineIndex = segmentsMapIndexes[start].find((li) => !parsedSegmentIndexes.has(li));
416
+ if (nextLineIndex !== void 0) {
417
+ parsedSegmentIndexes.add(nextLineIndex);
418
+ const [a, b] = lineSegmentStrings[nextLineIndex];
419
+ const newPoint = a === start ? b : a;
454
420
  polyline.unshift(newPoint);
455
421
  start = newPoint;
456
- } else {
457
- start = null;
458
- }
422
+ } else start = null;
459
423
  }
460
424
  while (end && segmentsMapIndexes[end]) {
461
- const nextLineIndex = (_b = segmentsMapIndexes[end]) == null ? void 0 : _b.find(
462
- (lineIndex) => !parsedSegmentIndexes.includes(lineIndex)
463
- );
464
- if (nextLineIndex) {
465
- parsedSegmentIndexes.push(nextLineIndex);
466
- const nextLineSegment = lineSegmentStrings[nextLineIndex];
467
- const nextLineSegmentPointIndex = nextLineSegment[0] === end ? 1 : 0;
468
- const newPoint = nextLineSegment[nextLineSegmentPointIndex];
425
+ const nextLineIndex = segmentsMapIndexes[end].find((li) => !parsedSegmentIndexes.has(li));
426
+ if (nextLineIndex !== void 0) {
427
+ parsedSegmentIndexes.add(nextLineIndex);
428
+ const [a, b] = lineSegmentStrings[nextLineIndex];
429
+ const newPoint = a === end ? b : a;
469
430
  polyline.push(newPoint);
470
431
  end = newPoint;
471
- } else {
472
- end = null;
473
- }
432
+ } else end = null;
474
433
  }
475
434
  polylines.push(polyline.map((coord) => coord.split(",").map((v) => parseFloat(v))));
476
435
  }
@@ -484,10 +443,10 @@ var contourElevations = (minElevation, maxElevation, interval) => {
484
443
  throw new Error(`No contour lines at interval: ${interval} between elevation ${minElevation} and ${maxElevation}`);
485
444
  }
486
445
  const elevations = [];
487
- let elevation = Math.ceil(minElevation / interval) * interval;
488
- while (elevation < maxElevation) {
489
- elevations.push(elevation);
490
- elevation += interval;
446
+ const firstStep = Math.ceil(minElevation / interval);
447
+ const lastStep = Math.ceil(maxElevation / interval) - 1;
448
+ for (let step = firstStep; step <= lastStep; step++) {
449
+ elevations.push(step * interval);
491
450
  }
492
451
  return elevations;
493
452
  };
@@ -497,31 +456,55 @@ var constructGeojson = (elevationData) => {
497
456
  return prev.concat(
498
457
  polylines.map((polyline) => ({
499
458
  type: "Feature",
500
- geometry: {
501
- type: "LineString",
502
- coordinates: polyline
503
- },
504
- properties: {
505
- z: elevation
506
- }
459
+ geometry: { type: "LineString", coordinates: polyline },
460
+ properties: { z: elevation }
507
461
  }))
508
462
  );
509
463
  }, []);
510
- return {
511
- type: "FeatureCollection",
512
- features
513
- };
464
+ return { type: "FeatureCollection", features };
514
465
  };
515
- var getContours = (data, interval = 2) => __async(void 0, null, function* () {
516
- const triangles = data.surfaceDefinition.faces.map(
517
- (face) => face.map((vert) => data.surfaceDefinition.points[vert])
518
- );
519
- const [minElevation, maxElevation] = data.surfaceDefinition.points.reduce(
520
- ([prevMin, prevMax], curr) => [Math.min(prevMin, curr[2]), Math.max(prevMax, curr[2])],
521
- [Infinity, -Infinity]
522
- );
466
+ var precomputeSurfaceData = (data) => {
467
+ const { points, faces } = data.surfaceDefinition;
468
+ let minElevation = Infinity;
469
+ let maxElevation = -Infinity;
470
+ for (const pt of points) {
471
+ if (pt[2] < minElevation) minElevation = pt[2];
472
+ if (pt[2] > maxElevation) maxElevation = pt[2];
473
+ }
474
+ const triangles = faces.map((face) => face.map((vert) => points[vert]));
475
+ return { triangles, minElevation, maxElevation };
476
+ };
477
+ var bucketTrianglesByElevation = (triangles, elevations, interval) => {
478
+ const trianglesByElevation = /* @__PURE__ */ new Map();
479
+ for (const e of elevations) trianglesByElevation.set(e, []);
480
+ for (const tri of triangles) {
481
+ const zMin = Math.min(tri[0][2], tri[1][2], tri[2][2]);
482
+ const zMax = Math.max(tri[0][2], tri[1][2], tri[2][2]);
483
+ const firstStep = Math.ceil(zMin / interval);
484
+ const lastStep = Math.floor(zMax / interval);
485
+ for (let step = firstStep; step <= lastStep; step++) {
486
+ const rounded = step * interval;
487
+ const bucket = trianglesByElevation.get(rounded);
488
+ if (bucket) bucket.push(tri);
489
+ }
490
+ }
491
+ return trianglesByElevation;
492
+ };
493
+ var getContours = (data, interval = 2, precomputed) => __async(null, null, function* () {
494
+ const { triangles, minElevation, maxElevation } = precomputed != null ? precomputed : precomputeSurfaceData(data);
523
495
  const elevations = contourElevations(minElevation, maxElevation, interval);
524
- const elevationPolylines = yield Promise.all(elevations.map((elevation) => contoursWorker.send({ triangles, elevation })));
496
+ const trianglesByElevation = bucketTrianglesByElevation(triangles, elevations, interval);
497
+ const elevationPolylines = yield Promise.all(
498
+ elevations.map(
499
+ (elevation) => {
500
+ var _a;
501
+ return contoursWorker.send({
502
+ triangles: (_a = trianglesByElevation.get(elevation)) != null ? _a : [],
503
+ elevation
504
+ });
505
+ }
506
+ )
507
+ );
525
508
  return constructGeojson(elevationPolylines);
526
509
  });
527
510
  var get_contours_default = getContours;
@@ -531,12 +514,9 @@ var getOutline = (surface) => {
531
514
  const vertexIndexPairEdges = [];
532
515
  surface.surfaceDefinition.faces.forEach((f, i) => {
533
516
  const neighbors = surface.surfaceDefinition.faceNeighbors[i];
534
- if (neighbors[0] === -1)
535
- vertexIndexPairEdges.push([f[0], f[1]]);
536
- if (neighbors[1] === -1)
537
- vertexIndexPairEdges.push([f[1], f[2]]);
538
- if (neighbors[2] === -1)
539
- vertexIndexPairEdges.push([f[0], f[2]]);
517
+ if (neighbors[0] === -1) vertexIndexPairEdges.push([f[0], f[1]]);
518
+ if (neighbors[1] === -1) vertexIndexPairEdges.push([f[1], f[2]]);
519
+ if (neighbors[2] === -1) vertexIndexPairEdges.push([f[0], f[2]]);
540
520
  });
541
521
  const edges = [];
542
522
  vertexIndexPairEdges.map((pair) => {
@@ -549,11 +529,11 @@ var getOutline = (surface) => {
549
529
  var get_outline_default = getOutline;
550
530
 
551
531
  // src/public/to-geojson-contours.ts
552
- var toGeojsonContours = (landXmlString, contourInterval = 2, generateOutline = true, surfaceId = -1) => __async(void 0, null, function* () {
532
+ var toGeojsonContours = (landXmlString, contourInterval = 2, generateOutline = true, surfaceId = -1) => __async(null, null, function* () {
553
533
  let requestedParsedSurfaces = filter_by_surfaceId_default(yield parse_xml_default(landXmlString), surfaceId);
554
534
  const contours = yield Promise.all(
555
535
  requestedParsedSurfaces.map(
556
- (surface) => new Promise((resolve, reject) => __async(void 0, null, function* () {
536
+ (surface) => new Promise((resolve, reject) => __async(null, null, function* () {
557
537
  try {
558
538
  const geojson = yield get_contours_default(surface, contourInterval);
559
539
  if (generateOutline) {
@@ -591,8 +571,7 @@ var reprojectGeoJson = (geojson, sourceProjection, targetProjection = "WGS84", k
591
571
  throw new Error("Invalid GeoJSON or source projection.");
592
572
  }
593
573
  geojson.features.forEach((feature) => {
594
- if (keepOriginalGeometryAsFeatureProperty)
595
- feature.properties = feature.properties || {};
574
+ if (keepOriginalGeometryAsFeatureProperty) feature.properties = feature.properties || {};
596
575
  if (feature.geometry) {
597
576
  if (keepOriginalGeometryAsFeatureProperty && feature.properties)
598
577
  feature.properties._rawGeometry = __spreadValues({}, feature.geometry);
@@ -608,9 +587,41 @@ var reprojectGeoJson = (geojson, sourceProjection, targetProjection = "WGS84", k
608
587
  return geojson;
609
588
  };
610
589
  var reproject_geojson_default = reprojectGeoJson;
590
+
591
+ // src/public/to-glb-and-contours.ts
592
+ var toGlbAndContours = (landXmlString, contourInterval = 2, generateOutline = true, center = "auto", surfaceId = -1) => __async(null, null, function* () {
593
+ const requestedCenter = center === "origin" ? [0, 0] : center === "auto" ? void 0 : center;
594
+ const requestedParsedSurfaces = filter_by_surfaceId_default(yield parse_xml_default(landXmlString), surfaceId);
595
+ const results = yield Promise.all(
596
+ requestedParsedSurfaces.map((surface) => __async(null, null, function* () {
597
+ const precomputed = precomputeSurfaceData(surface);
598
+ const [{ glb, center: resolvedCenter }, geojson] = yield Promise.all([
599
+ get_glb_default(surface, requestedCenter),
600
+ get_contours_default(surface, contourInterval, precomputed)
601
+ ]);
602
+ if (generateOutline) {
603
+ const outlineGeojson = get_outline_default(surface);
604
+ geojson.features = [...geojson.features, ...outlineGeojson.features];
605
+ }
606
+ const _a = surface, { surfaceDefinition } = _a, rest = __objRest(_a, ["surfaceDefinition"]);
607
+ return __spreadProps(__spreadValues({}, rest), {
608
+ glb,
609
+ center: resolvedCenter,
610
+ download: () => {
611
+ download_glb_default(glb, surface.name.replace(/\.xml$/, `${JSON.stringify(resolvedCenter)}.glb`));
612
+ },
613
+ geojson
614
+ });
615
+ }))
616
+ );
617
+ return results;
618
+ });
619
+ var to_glb_and_contours_default = toGlbAndContours;
611
620
  // Annotate the CommonJS export names for ESM import in node:
612
621
  0 && (module.exports = {
622
+ precomputeSurfaceData,
613
623
  reprojectGeoJson,
614
624
  toGeojsonContours,
615
- toGlb
625
+ toGlb,
626
+ toGlbAndContours
616
627
  });