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