landxml 0.6.6 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -79,9 +79,11 @@ var __async = (__this, __arguments, generator) => {
79
79
  // src/index.ts
80
80
  var index_exports = {};
81
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
88
  module.exports = __toCommonJS(index_exports);
87
89
 
@@ -113,18 +115,30 @@ var findXYAxisMedians = (vertices) => {
113
115
  };
114
116
  var getGlb = (data, customCenter) => __async(null, null, function* () {
115
117
  const center = customCenter || findXYAxisMedians(data.surfaceDefinition.points);
116
- const vertices = [...data.surfaceDefinition.points].map((p) => p.slice()).map(([x, y, z]) => {
117
- return [x - center[0], z, -(y - center[1])];
118
- }).reduce((prev, curr) => prev.concat(curr), []);
119
- 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
+ }
120
134
  const doc = new import_core.Document();
121
135
  const buffer = doc.createBuffer();
122
- const position = doc.createAccessor().setType("VEC3").setArray(new Float32Array(vertices)).setBuffer(buffer);
123
- 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);
124
138
  const prim = doc.createPrimitive().setAttribute("POSITION", position).setIndices(indices);
125
139
  const mesh = doc.createMesh().addPrimitive(prim);
126
140
  const node = doc.createNode().setMesh(mesh);
127
- const scene = doc.createScene().addChild(node);
141
+ doc.createScene().addChild(node);
128
142
  const glb = yield new import_core.WebIO().writeBinary(doc);
129
143
  return { glb, center };
130
144
  });
@@ -168,14 +182,29 @@ var surfaceDefWorker = (0, import_easy_web_worker.createEasyWebWorker)(
168
182
  })
169
183
  );
170
184
  } else if (task === "find-neighboring-faces") {
171
- const { faces, range: { start, end } } = message.payload;
172
- const faceNeighbors = [];
173
- for (let i = start; i < end; i++) {
174
- const sourceFace = faces[i];
175
- const neighborA = faces.findIndex((f, j) => f.findIndex((v) => v === sourceFace[0]) >= 0 && f.findIndex((v) => v === sourceFace[1]) >= 0 && j !== i);
176
- const neighborB = faces.findIndex((f, j) => f.findIndex((v) => v === sourceFace[1]) >= 0 && f.findIndex((v) => v === sourceFace[2]) >= 0 && j !== i);
177
- const neighborC = faces.findIndex((f, j) => f.findIndex((v) => v === sourceFace[0]) >= 0 && f.findIndex((v) => v === sourceFace[2]) >= 0 && j !== i);
178
- 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))];
179
208
  }
180
209
  message.resolve(faceNeighbors);
181
210
  }
@@ -186,134 +215,99 @@ var surfaceDefWorker = (0, import_easy_web_worker.createEasyWebWorker)(
186
215
  },
187
216
  { maxWorkers: 16 }
188
217
  );
189
- var parseXML = (xmlString) => __async(null, null, function* () {
190
- return new Promise((resolve, reject) => __async(null, null, function* () {
191
- var _a, _b, _c, _d, _e;
192
- const parsed = import_xml_js.default.xml2js(xmlString, {
193
- compact: true,
194
- attributesKey: "attr",
195
- textKey: "content"
196
- });
197
- if (typeof ((_b = (_a = parsed.LandXML) == null ? void 0 : _a.Surfaces) == null ? void 0 : _b.Surface) === "undefined") {
198
- throw new Error("LandXML doesn't contain any surfaces");
199
- return;
200
- }
201
- if (!Array.isArray(parsed.LandXML.Surfaces.Surface)) {
202
- parsed.LandXML.Surfaces.Surface = [parsed.LandXML.Surfaces.Surface];
203
- }
204
- let sourceFile = parsed.LandXML.Project.attr.name || "Undefined source";
205
- let timeStamp = parsed.LandXML.Application.attr.timeStamp || "";
206
- let wktString = ((_e = (_d = (_c = parsed.LandXML) == null ? void 0 : _c.CoordinateSystem) == null ? void 0 : _d.attr) == null ? void 0 : _e.ogcWktCode) || void 0;
207
- const surfaces = parsed.LandXML.Surfaces.Surface.map(
208
- (surface) => __async(null, null, function* () {
209
- return new Promise((resolve2, reject2) => __async(null, null, function* () {
210
- const { name, desc } = surface.attr;
211
- const Pnts = surface.Definition.Pnts.P;
212
- const Faces = surface.Definition.Faces.F;
213
- let ptsIdArray = [];
214
- let faces = [];
215
- let faceNeighbors = [];
216
- if (Pnts.length > 1e4) {
217
- const sliceIndexes = [...Array(20).keys()].map((i) => Math.round(Pnts.length / 20 * i));
218
- ptsIdArray = (yield Promise.all(
219
- sliceIndexes.map(
220
- (v, i, a) => new Promise((resolve3, reject3) => __async(null, null, function* () {
221
- const pts = yield surfaceDefWorker.send({
222
- task: "parse-surface-points",
223
- points: Pnts.slice(a[i], a[i + 1] || Pnts.length)
224
- });
225
- resolve3(pts);
226
- }))
227
- )
228
- )).reduce((prev, curr) => [...prev, ...curr], []);
229
- } else {
230
- ptsIdArray = yield surfaceDefWorker.send({ task: "parse-surface-points", points: Pnts });
231
- }
232
- const points = ptsIdArray.map((v) => v[1]);
233
- const pointsIdMap = ptsIdArray.map((v) => v[0]);
234
- if (Faces.length > 1e4) {
235
- const sliceIndexes = [...Array(20).keys()].map((i) => Math.round(Faces.length / 20 * i));
236
- faces = (yield Promise.all(
237
- sliceIndexes.map(
238
- (v, i, a) => new Promise((resolve3, reject3) => __async(null, null, function* () {
239
- const fcs = yield surfaceDefWorker.send({
240
- task: "parse-surface-faces",
241
- faces: Faces.slice(a[i], a[i + 1] || Faces.length),
242
- idMap: pointsIdMap
243
- });
244
- resolve3(fcs);
245
- }))
246
- )
247
- )).reduce((prev, curr) => [...prev, ...curr], []);
248
- } else {
249
- faces = yield surfaceDefWorker.send({
250
- task: "parse-surface-faces",
251
- faces: Faces,
252
- idMap: pointsIdMap
253
- });
254
- }
255
- if (Faces.length > 1e4) {
256
- const sliceIndexes = [...Array(20).keys()].map((i) => Math.round((faces.length - 1) / 20 * i));
257
- faceNeighbors = (yield Promise.all(
258
- sliceIndexes.map(
259
- (v, i, a) => new Promise((resolve3, reject3) => __async(null, null, function* () {
260
- const fcs = yield surfaceDefWorker.send({
261
- task: "find-neighboring-faces",
262
- faces,
263
- range: {
264
- start: a[i],
265
- end: a[i + 1] || faces.length
266
- }
267
- });
268
- resolve3(fcs);
269
- }))
270
- )
271
- )).reduce((prev, curr) => [...prev, ...curr], []);
272
- } else {
273
- faceNeighbors = yield surfaceDefWorker.send({
274
- task: "find-neighboring-faces",
275
- faces,
276
- range: {
277
- start: 0,
278
- end: faces.length
279
- }
280
- });
281
- }
282
- resolve2({
283
- sourceFile,
284
- timeStamp: timeStamp || "",
285
- name,
286
- description: desc || "",
287
- wktString,
288
- surfaceDefinition: {
289
- points,
290
- faces,
291
- faceNeighbors
292
- }
293
- });
294
- }));
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));
295
226
  })
296
227
  );
297
- 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
+ };
298
283
  }));
284
+ return Promise.all(surfaces);
299
285
  });
300
286
  var parse_xml_default = parseXML;
301
287
 
302
288
  // src/public/to-glb.ts
303
289
  var toGlb = (landXmlString, center = "auto", surfaceId = -1) => __async(null, null, function* () {
304
- const requestedCenter = center == "origin" ? [0, 0] : center === "auto" ? void 0 : center;
305
290
  let requestedParsedSurfaces = filter_by_surfaceId_default(yield parse_xml_default(landXmlString), surfaceId);
291
+ let resolvedCenter;
292
+ if (center === "origin") {
293
+ resolvedCenter = [0, 0];
294
+ } else if (center === "auto") {
295
+ const allPoints = requestedParsedSurfaces.flatMap((s) => s.surfaceDefinition.points);
296
+ resolvedCenter = findXYAxisMedians(allPoints);
297
+ } else {
298
+ resolvedCenter = center;
299
+ }
306
300
  const glbs = yield Promise.all(
307
301
  requestedParsedSurfaces.map(
308
302
  (surface) => new Promise((resolve, reject) => __async(null, null, function* () {
309
303
  try {
310
- const { glb, center: center2 } = yield get_glb_default(surface, requestedCenter);
304
+ const { glb } = yield get_glb_default(surface, resolvedCenter);
311
305
  const _a = surface, { surfaceDefinition } = _a, rest = __objRest(_a, ["surfaceDefinition"]);
312
306
  resolve(__spreadProps(__spreadValues({}, rest), {
313
307
  glb,
314
- center: center2,
308
+ center: resolvedCenter,
315
309
  download: () => {
316
- download_glb_default(glb, surface.name.replace(/\.xml$/, `${JSON.stringify(center2)}.glb`));
310
+ download_glb_default(glb, surface.name.replace(/\.xml$/, `${JSON.stringify(resolvedCenter)}.glb`));
317
311
  }
318
312
  }));
319
313
  } catch (e) {
@@ -334,49 +328,45 @@ var contoursWorker = (0, import_easy_web_worker2.createEasyWebWorker)(
334
328
  let vertsAtElevation = 0;
335
329
  let line = [];
336
330
  for (let i = 0; i < face.length; i++) {
337
- let vertex1 = face[i];
338
- let vertex2 = face[(i + 1) % face.length];
331
+ const vertex1 = face[i];
332
+ const vertex2 = face[(i + 1) % face.length];
339
333
  if (vertex1[2] === z) vertsAtElevation++;
340
334
  if ((vertex1[2] <= z && vertex2[2] >= z || vertex1[2] >= z && vertex2[2] <= z) && !Number.isNaN((z - vertex1[2]) / (vertex2[2] - vertex1[2]))) {
341
- let t = (z - vertex1[2]) / (vertex2[2] - vertex1[2]);
335
+ const t = (z - vertex1[2]) / (vertex2[2] - vertex1[2]);
342
336
  line.push([vertex1[0] + t * (vertex2[0] - vertex1[0]), vertex1[1] + t * (vertex2[1] - vertex1[1])]);
343
337
  }
344
338
  }
345
339
  if (vertsAtElevation >= 2 && face.map((f) => f[2]).reduce((a, b) => a + b) > z * face.length) return void 0;
346
- if (line.length === 2 && line[0][0] === line[1][0] && line[0][1] === line[1][1])
347
- return void 0;
340
+ if (line.length === 2 && line[0][0] === line[1][0] && line[0][1] === line[1][1]) return void 0;
348
341
  if (line.length > 2) {
349
342
  line = [...new Set(line.map((v) => JSON.stringify(v)))].map((s) => JSON.parse(s));
350
343
  }
351
344
  return line.length > 0 ? line : void 0;
352
345
  };
353
346
  const linesToPolyLines3 = (lineSegments) => {
354
- var _a, _b;
355
- if (!Array.isArray(lineSegments) || (lineSegments == null ? void 0 : lineSegments.length) === 0) {
356
- return [];
357
- }
347
+ if (!Array.isArray(lineSegments) || lineSegments.length === 0) return [];
358
348
  const segmentsMapIndexes = {};
359
349
  const polylines = [];
360
- const parsedSegmentIndexes = [];
350
+ const parsedSegmentIndexes = /* @__PURE__ */ new Set();
361
351
  const lineSegmentStrings = lineSegments.map((v) => v.map((c) => c.join(",")));
362
- lineSegmentStrings.forEach(([start, end], i) => {
363
- segmentsMapIndexes[start] = segmentsMapIndexes[start] ? [...segmentsMapIndexes[start] || [], i] : [i];
364
- segmentsMapIndexes[end] = segmentsMapIndexes[end] ? [...segmentsMapIndexes[end] || [], i] : [i];
365
- });
366
352
  for (let i = 0; i < lineSegmentStrings.length; i++) {
367
- if (parsedSegmentIndexes.includes(i)) continue;
368
- parsedSegmentIndexes.push(i);
353
+ const [start, end] = lineSegmentStrings[i];
354
+ segmentsMapIndexes[start] = segmentsMapIndexes[start] ? [...segmentsMapIndexes[start], i] : [i];
355
+ segmentsMapIndexes[end] = segmentsMapIndexes[end] ? [...segmentsMapIndexes[end], i] : [i];
356
+ }
357
+ for (let i = 0; i < lineSegmentStrings.length; i++) {
358
+ if (parsedSegmentIndexes.has(i)) continue;
359
+ parsedSegmentIndexes.add(i);
369
360
  let [start, end] = lineSegmentStrings[i];
370
- let polyline = [start, end];
361
+ const polyline = [start, end];
371
362
  while (start && segmentsMapIndexes[start]) {
372
- const nextLineIndex = (_a = segmentsMapIndexes[start]) == null ? void 0 : _a.find(
373
- (lineIndex) => !parsedSegmentIndexes.includes(lineIndex)
363
+ const nextLineIndex = segmentsMapIndexes[start].find(
364
+ (li) => !parsedSegmentIndexes.has(li)
374
365
  );
375
- if (nextLineIndex) {
376
- parsedSegmentIndexes.push(nextLineIndex);
377
- const nextLineSegment = lineSegmentStrings[nextLineIndex];
378
- const nextLineSegmentPointIndex = nextLineSegment[0] === start ? 1 : 0;
379
- const newPoint = nextLineSegment[nextLineSegmentPointIndex];
366
+ if (nextLineIndex !== void 0) {
367
+ parsedSegmentIndexes.add(nextLineIndex);
368
+ const [a, b] = lineSegmentStrings[nextLineIndex];
369
+ const newPoint = a === start ? b : a;
380
370
  polyline.unshift(newPoint);
381
371
  start = newPoint;
382
372
  } else {
@@ -384,14 +374,13 @@ var contoursWorker = (0, import_easy_web_worker2.createEasyWebWorker)(
384
374
  }
385
375
  }
386
376
  while (end && segmentsMapIndexes[end]) {
387
- const nextLineIndex = (_b = segmentsMapIndexes[end]) == null ? void 0 : _b.find(
388
- (lineIndex) => !parsedSegmentIndexes.includes(lineIndex)
377
+ const nextLineIndex = segmentsMapIndexes[end].find(
378
+ (li) => !parsedSegmentIndexes.has(li)
389
379
  );
390
- if (nextLineIndex) {
391
- parsedSegmentIndexes.push(nextLineIndex);
392
- const nextLineSegment = lineSegmentStrings[nextLineIndex];
393
- const nextLineSegmentPointIndex = nextLineSegment[0] === end ? 1 : 0;
394
- const newPoint = nextLineSegment[nextLineSegmentPointIndex];
380
+ if (nextLineIndex !== void 0) {
381
+ parsedSegmentIndexes.add(nextLineIndex);
382
+ const [a, b] = lineSegmentStrings[nextLineIndex];
383
+ const newPoint = a === end ? b : a;
395
384
  polyline.push(newPoint);
396
385
  end = newPoint;
397
386
  } else {
@@ -404,66 +393,51 @@ var contoursWorker = (0, import_easy_web_worker2.createEasyWebWorker)(
404
393
  };
405
394
  onMessage((message) => {
406
395
  const { triangles, elevation } = message.payload;
407
- const linesAtElevationE = triangles.reduce((prev, curr) => {
396
+ const linesAtElevation = triangles.reduce((prev, curr) => {
408
397
  const line = contourLineOnFace(curr, elevation);
409
398
  if (line) prev.push(line);
410
399
  return prev;
411
400
  }, []);
412
- message.resolve({
413
- elevation,
414
- polylines: linesToPolyLines3(linesAtElevationE)
415
- });
401
+ message.resolve({ elevation, polylines: linesToPolyLines3(linesAtElevation) });
416
402
  });
417
403
  },
418
404
  { maxWorkers: 10 }
419
405
  );
420
406
  var linesToPolyLines = (lineSegments) => {
421
- var _a, _b;
422
- if (!Array.isArray(lineSegments) || (lineSegments == null ? void 0 : lineSegments.length) === 0) {
423
- return [];
424
- }
407
+ if (!Array.isArray(lineSegments) || lineSegments.length === 0) return [];
425
408
  const segmentsMapIndexes = {};
426
409
  const polylines = [];
427
- const parsedSegmentIndexes = [];
410
+ const parsedSegmentIndexes = /* @__PURE__ */ new Set();
428
411
  const lineSegmentStrings = lineSegments.map((v) => v.map((c) => c.join(",")));
429
- lineSegmentStrings.forEach(([start, end], i) => {
430
- segmentsMapIndexes[start] = segmentsMapIndexes[start] ? [...segmentsMapIndexes[start] || [], i] : [i];
431
- segmentsMapIndexes[end] = segmentsMapIndexes[end] ? [...segmentsMapIndexes[end] || [], i] : [i];
432
- });
433
412
  for (let i = 0; i < lineSegmentStrings.length; i++) {
434
- if (parsedSegmentIndexes.includes(i)) continue;
435
- parsedSegmentIndexes.push(i);
413
+ const [start, end] = lineSegmentStrings[i];
414
+ segmentsMapIndexes[start] = segmentsMapIndexes[start] ? [...segmentsMapIndexes[start], i] : [i];
415
+ segmentsMapIndexes[end] = segmentsMapIndexes[end] ? [...segmentsMapIndexes[end], i] : [i];
416
+ }
417
+ for (let i = 0; i < lineSegmentStrings.length; i++) {
418
+ if (parsedSegmentIndexes.has(i)) continue;
419
+ parsedSegmentIndexes.add(i);
436
420
  let [start, end] = lineSegmentStrings[i];
437
- let polyline = [start, end];
421
+ const polyline = [start, end];
438
422
  while (start && segmentsMapIndexes[start]) {
439
- const nextLineIndex = (_a = segmentsMapIndexes[start]) == null ? void 0 : _a.find(
440
- (lineIndex) => !parsedSegmentIndexes.includes(lineIndex)
441
- );
442
- if (nextLineIndex) {
443
- parsedSegmentIndexes.push(nextLineIndex);
444
- const nextLineSegment = lineSegmentStrings[nextLineIndex];
445
- const nextLineSegmentPointIndex = nextLineSegment[0] === start ? 1 : 0;
446
- const newPoint = nextLineSegment[nextLineSegmentPointIndex];
423
+ const nextLineIndex = segmentsMapIndexes[start].find((li) => !parsedSegmentIndexes.has(li));
424
+ if (nextLineIndex !== void 0) {
425
+ parsedSegmentIndexes.add(nextLineIndex);
426
+ const [a, b] = lineSegmentStrings[nextLineIndex];
427
+ const newPoint = a === start ? b : a;
447
428
  polyline.unshift(newPoint);
448
429
  start = newPoint;
449
- } else {
450
- start = null;
451
- }
430
+ } else start = null;
452
431
  }
453
432
  while (end && segmentsMapIndexes[end]) {
454
- const nextLineIndex = (_b = segmentsMapIndexes[end]) == null ? void 0 : _b.find(
455
- (lineIndex) => !parsedSegmentIndexes.includes(lineIndex)
456
- );
457
- if (nextLineIndex) {
458
- parsedSegmentIndexes.push(nextLineIndex);
459
- const nextLineSegment = lineSegmentStrings[nextLineIndex];
460
- const nextLineSegmentPointIndex = nextLineSegment[0] === end ? 1 : 0;
461
- const newPoint = nextLineSegment[nextLineSegmentPointIndex];
433
+ const nextLineIndex = segmentsMapIndexes[end].find((li) => !parsedSegmentIndexes.has(li));
434
+ if (nextLineIndex !== void 0) {
435
+ parsedSegmentIndexes.add(nextLineIndex);
436
+ const [a, b] = lineSegmentStrings[nextLineIndex];
437
+ const newPoint = a === end ? b : a;
462
438
  polyline.push(newPoint);
463
439
  end = newPoint;
464
- } else {
465
- end = null;
466
- }
440
+ } else end = null;
467
441
  }
468
442
  polylines.push(polyline.map((coord) => coord.split(",").map((v) => parseFloat(v))));
469
443
  }
@@ -477,10 +451,10 @@ var contourElevations = (minElevation, maxElevation, interval) => {
477
451
  throw new Error(`No contour lines at interval: ${interval} between elevation ${minElevation} and ${maxElevation}`);
478
452
  }
479
453
  const elevations = [];
480
- let elevation = Math.ceil(minElevation / interval) * interval;
481
- while (elevation < maxElevation) {
482
- elevations.push(elevation);
483
- elevation += interval;
454
+ const firstStep = Math.ceil(minElevation / interval);
455
+ const lastStep = Math.ceil(maxElevation / interval) - 1;
456
+ for (let step = firstStep; step <= lastStep; step++) {
457
+ elevations.push(step * interval);
484
458
  }
485
459
  return elevations;
486
460
  };
@@ -490,31 +464,55 @@ var constructGeojson = (elevationData) => {
490
464
  return prev.concat(
491
465
  polylines.map((polyline) => ({
492
466
  type: "Feature",
493
- geometry: {
494
- type: "LineString",
495
- coordinates: polyline
496
- },
497
- properties: {
498
- z: elevation
499
- }
467
+ geometry: { type: "LineString", coordinates: polyline },
468
+ properties: { z: elevation }
500
469
  }))
501
470
  );
502
471
  }, []);
503
- return {
504
- type: "FeatureCollection",
505
- features
506
- };
472
+ return { type: "FeatureCollection", features };
507
473
  };
508
- var getContours = (data, interval = 2) => __async(null, null, function* () {
509
- const triangles = data.surfaceDefinition.faces.map(
510
- (face) => face.map((vert) => data.surfaceDefinition.points[vert])
511
- );
512
- const [minElevation, maxElevation] = data.surfaceDefinition.points.reduce(
513
- ([prevMin, prevMax], curr) => [Math.min(prevMin, curr[2]), Math.max(prevMax, curr[2])],
514
- [Infinity, -Infinity]
515
- );
474
+ var precomputeSurfaceData = (data) => {
475
+ const { points, faces } = data.surfaceDefinition;
476
+ let minElevation = Infinity;
477
+ let maxElevation = -Infinity;
478
+ for (const pt of points) {
479
+ if (pt[2] < minElevation) minElevation = pt[2];
480
+ if (pt[2] > maxElevation) maxElevation = pt[2];
481
+ }
482
+ const triangles = faces.map((face) => face.map((vert) => points[vert]));
483
+ return { triangles, minElevation, maxElevation };
484
+ };
485
+ var bucketTrianglesByElevation = (triangles, elevations, interval) => {
486
+ const trianglesByElevation = /* @__PURE__ */ new Map();
487
+ for (const e of elevations) trianglesByElevation.set(e, []);
488
+ for (const tri of triangles) {
489
+ const zMin = Math.min(tri[0][2], tri[1][2], tri[2][2]);
490
+ const zMax = Math.max(tri[0][2], tri[1][2], tri[2][2]);
491
+ const firstStep = Math.ceil(zMin / interval);
492
+ const lastStep = Math.floor(zMax / interval);
493
+ for (let step = firstStep; step <= lastStep; step++) {
494
+ const rounded = step * interval;
495
+ const bucket = trianglesByElevation.get(rounded);
496
+ if (bucket) bucket.push(tri);
497
+ }
498
+ }
499
+ return trianglesByElevation;
500
+ };
501
+ var getContours = (data, interval = 2, precomputed) => __async(null, null, function* () {
502
+ const { triangles, minElevation, maxElevation } = precomputed != null ? precomputed : precomputeSurfaceData(data);
516
503
  const elevations = contourElevations(minElevation, maxElevation, interval);
517
- const elevationPolylines = yield Promise.all(elevations.map((elevation) => contoursWorker.send({ triangles, elevation })));
504
+ const trianglesByElevation = bucketTrianglesByElevation(triangles, elevations, interval);
505
+ const elevationPolylines = yield Promise.all(
506
+ elevations.map(
507
+ (elevation) => {
508
+ var _a;
509
+ return contoursWorker.send({
510
+ triangles: (_a = trianglesByElevation.get(elevation)) != null ? _a : [],
511
+ elevation
512
+ });
513
+ }
514
+ )
515
+ );
518
516
  return constructGeojson(elevationPolylines);
519
517
  });
520
518
  var get_contours_default = getContours;
@@ -597,9 +595,49 @@ var reprojectGeoJson = (geojson, sourceProjection, targetProjection = "WGS84", k
597
595
  return geojson;
598
596
  };
599
597
  var reproject_geojson_default = reprojectGeoJson;
598
+
599
+ // src/public/to-glb-and-contours.ts
600
+ var toGlbAndContours = (landXmlString, contourInterval = 2, generateOutline = true, center = "auto", surfaceId = -1) => __async(null, null, function* () {
601
+ const requestedParsedSurfaces = filter_by_surfaceId_default(yield parse_xml_default(landXmlString), surfaceId);
602
+ let resolvedCenter;
603
+ if (center === "origin") {
604
+ resolvedCenter = [0, 0];
605
+ } else if (center === "auto") {
606
+ const allPoints = requestedParsedSurfaces.flatMap((s) => s.surfaceDefinition.points);
607
+ resolvedCenter = findXYAxisMedians(allPoints);
608
+ } else {
609
+ resolvedCenter = center;
610
+ }
611
+ const results = yield Promise.all(
612
+ requestedParsedSurfaces.map((surface) => __async(null, null, function* () {
613
+ const precomputed = precomputeSurfaceData(surface);
614
+ const [{ glb }, geojson] = yield Promise.all([
615
+ get_glb_default(surface, resolvedCenter),
616
+ get_contours_default(surface, contourInterval, precomputed)
617
+ ]);
618
+ if (generateOutline) {
619
+ const outlineGeojson = get_outline_default(surface);
620
+ geojson.features = [...geojson.features, ...outlineGeojson.features];
621
+ }
622
+ const _a = surface, { surfaceDefinition } = _a, rest = __objRest(_a, ["surfaceDefinition"]);
623
+ return __spreadProps(__spreadValues({}, rest), {
624
+ glb,
625
+ center: resolvedCenter,
626
+ download: () => {
627
+ download_glb_default(glb, surface.name.replace(/\.xml$/, `${JSON.stringify(resolvedCenter)}.glb`));
628
+ },
629
+ geojson
630
+ });
631
+ }))
632
+ );
633
+ return results;
634
+ });
635
+ var to_glb_and_contours_default = toGlbAndContours;
600
636
  // Annotate the CommonJS export names for ESM import in node:
601
637
  0 && (module.exports = {
638
+ precomputeSurfaceData,
602
639
  reprojectGeoJson,
603
640
  toGeojsonContours,
604
- toGlb
641
+ toGlb,
642
+ toGlbAndContours
605
643
  });