landxml 0.6.6 → 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
@@ -78,18 +78,30 @@ var findXYAxisMedians = (vertices) => {
78
78
  };
79
79
  var getGlb = (data, customCenter) => __async(null, null, function* () {
80
80
  const center = customCenter || findXYAxisMedians(data.surfaceDefinition.points);
81
- const vertices = [...data.surfaceDefinition.points].map((p) => p.slice()).map(([x, y, z]) => {
82
- return [x - center[0], z, -(y - center[1])];
83
- }).reduce((prev, curr) => prev.concat(curr), []);
84
- 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
+ }
85
97
  const doc = new Document();
86
98
  const buffer = doc.createBuffer();
87
- const position = doc.createAccessor().setType("VEC3").setArray(new Float32Array(vertices)).setBuffer(buffer);
88
- 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);
89
101
  const prim = doc.createPrimitive().setAttribute("POSITION", position).setIndices(indices);
90
102
  const mesh = doc.createMesh().addPrimitive(prim);
91
103
  const node = doc.createNode().setMesh(mesh);
92
- const scene = doc.createScene().addChild(node);
104
+ doc.createScene().addChild(node);
93
105
  const glb = yield new WebIO().writeBinary(doc);
94
106
  return { glb, center };
95
107
  });
@@ -133,14 +145,29 @@ var surfaceDefWorker = createEasyWebWorker(
133
145
  })
134
146
  );
135
147
  } else if (task === "find-neighboring-faces") {
136
- const { faces, range: { start, end } } = message.payload;
137
- const faceNeighbors = [];
138
- for (let i = start; i < end; i++) {
139
- const sourceFace = faces[i];
140
- const neighborA = faces.findIndex((f, j) => f.findIndex((v) => v === sourceFace[0]) >= 0 && f.findIndex((v) => v === sourceFace[1]) >= 0 && j !== i);
141
- const neighborB = faces.findIndex((f, j) => f.findIndex((v) => v === sourceFace[1]) >= 0 && f.findIndex((v) => v === sourceFace[2]) >= 0 && j !== i);
142
- const neighborC = faces.findIndex((f, j) => f.findIndex((v) => v === sourceFace[0]) >= 0 && f.findIndex((v) => v === sourceFace[2]) >= 0 && j !== i);
143
- 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))];
144
171
  }
145
172
  message.resolve(faceNeighbors);
146
173
  }
@@ -151,116 +178,73 @@ var surfaceDefWorker = createEasyWebWorker(
151
178
  },
152
179
  { maxWorkers: 16 }
153
180
  );
154
- var parseXML = (xmlString) => __async(null, null, function* () {
155
- return new Promise((resolve, reject) => __async(null, null, function* () {
156
- var _a, _b, _c, _d, _e;
157
- const parsed = convert.xml2js(xmlString, {
158
- compact: true,
159
- attributesKey: "attr",
160
- textKey: "content"
161
- });
162
- if (typeof ((_b = (_a = parsed.LandXML) == null ? void 0 : _a.Surfaces) == null ? void 0 : _b.Surface) === "undefined") {
163
- throw new Error("LandXML doesn't contain any surfaces");
164
- return;
165
- }
166
- if (!Array.isArray(parsed.LandXML.Surfaces.Surface)) {
167
- parsed.LandXML.Surfaces.Surface = [parsed.LandXML.Surfaces.Surface];
168
- }
169
- let sourceFile = parsed.LandXML.Project.attr.name || "Undefined source";
170
- let timeStamp = parsed.LandXML.Application.attr.timeStamp || "";
171
- let wktString = ((_e = (_d = (_c = parsed.LandXML) == null ? void 0 : _c.CoordinateSystem) == null ? void 0 : _d.attr) == null ? void 0 : _e.ogcWktCode) || void 0;
172
- const surfaces = parsed.LandXML.Surfaces.Surface.map(
173
- (surface) => __async(null, null, function* () {
174
- return new Promise((resolve2, reject2) => __async(null, null, function* () {
175
- const { name, desc } = surface.attr;
176
- const Pnts = surface.Definition.Pnts.P;
177
- const Faces = surface.Definition.Faces.F;
178
- let ptsIdArray = [];
179
- let faces = [];
180
- let faceNeighbors = [];
181
- if (Pnts.length > 1e4) {
182
- const sliceIndexes = [...Array(20).keys()].map((i) => Math.round(Pnts.length / 20 * i));
183
- ptsIdArray = (yield Promise.all(
184
- sliceIndexes.map(
185
- (v, i, a) => new Promise((resolve3, reject3) => __async(null, null, function* () {
186
- const pts = yield surfaceDefWorker.send({
187
- task: "parse-surface-points",
188
- points: Pnts.slice(a[i], a[i + 1] || Pnts.length)
189
- });
190
- resolve3(pts);
191
- }))
192
- )
193
- )).reduce((prev, curr) => [...prev, ...curr], []);
194
- } else {
195
- ptsIdArray = yield surfaceDefWorker.send({ task: "parse-surface-points", points: Pnts });
196
- }
197
- const points = ptsIdArray.map((v) => v[1]);
198
- const pointsIdMap = ptsIdArray.map((v) => v[0]);
199
- if (Faces.length > 1e4) {
200
- const sliceIndexes = [...Array(20).keys()].map((i) => Math.round(Faces.length / 20 * i));
201
- faces = (yield Promise.all(
202
- sliceIndexes.map(
203
- (v, i, a) => new Promise((resolve3, reject3) => __async(null, null, function* () {
204
- const fcs = yield surfaceDefWorker.send({
205
- task: "parse-surface-faces",
206
- faces: Faces.slice(a[i], a[i + 1] || Faces.length),
207
- idMap: pointsIdMap
208
- });
209
- resolve3(fcs);
210
- }))
211
- )
212
- )).reduce((prev, curr) => [...prev, ...curr], []);
213
- } else {
214
- faces = yield surfaceDefWorker.send({
215
- task: "parse-surface-faces",
216
- faces: Faces,
217
- idMap: pointsIdMap
218
- });
219
- }
220
- if (Faces.length > 1e4) {
221
- const sliceIndexes = [...Array(20).keys()].map((i) => Math.round((faces.length - 1) / 20 * i));
222
- faceNeighbors = (yield Promise.all(
223
- sliceIndexes.map(
224
- (v, i, a) => new Promise((resolve3, reject3) => __async(null, null, function* () {
225
- const fcs = yield surfaceDefWorker.send({
226
- task: "find-neighboring-faces",
227
- faces,
228
- range: {
229
- start: a[i],
230
- end: a[i + 1] || faces.length
231
- }
232
- });
233
- resolve3(fcs);
234
- }))
235
- )
236
- )).reduce((prev, curr) => [...prev, ...curr], []);
237
- } else {
238
- faceNeighbors = yield surfaceDefWorker.send({
239
- task: "find-neighboring-faces",
240
- faces,
241
- range: {
242
- start: 0,
243
- end: faces.length
244
- }
245
- });
246
- }
247
- resolve2({
248
- sourceFile,
249
- timeStamp: timeStamp || "",
250
- name,
251
- description: desc || "",
252
- wktString,
253
- surfaceDefinition: {
254
- points,
255
- faces,
256
- faceNeighbors
257
- }
258
- });
259
- }));
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));
260
189
  })
261
190
  );
262
- 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
+ };
263
246
  }));
247
+ return Promise.all(surfaces);
264
248
  });
265
249
  var parse_xml_default = parseXML;
266
250
 
@@ -299,49 +283,45 @@ var contoursWorker = createEasyWebWorker2(
299
283
  let vertsAtElevation = 0;
300
284
  let line = [];
301
285
  for (let i = 0; i < face.length; i++) {
302
- let vertex1 = face[i];
303
- let vertex2 = face[(i + 1) % face.length];
286
+ const vertex1 = face[i];
287
+ const vertex2 = face[(i + 1) % face.length];
304
288
  if (vertex1[2] === z) vertsAtElevation++;
305
289
  if ((vertex1[2] <= z && vertex2[2] >= z || vertex1[2] >= z && vertex2[2] <= z) && !Number.isNaN((z - vertex1[2]) / (vertex2[2] - vertex1[2]))) {
306
- let t = (z - vertex1[2]) / (vertex2[2] - vertex1[2]);
290
+ const t = (z - vertex1[2]) / (vertex2[2] - vertex1[2]);
307
291
  line.push([vertex1[0] + t * (vertex2[0] - vertex1[0]), vertex1[1] + t * (vertex2[1] - vertex1[1])]);
308
292
  }
309
293
  }
310
294
  if (vertsAtElevation >= 2 && face.map((f) => f[2]).reduce((a, b) => a + b) > z * face.length) return void 0;
311
- if (line.length === 2 && line[0][0] === line[1][0] && line[0][1] === line[1][1])
312
- return void 0;
295
+ if (line.length === 2 && line[0][0] === line[1][0] && line[0][1] === line[1][1]) return void 0;
313
296
  if (line.length > 2) {
314
297
  line = [...new Set(line.map((v) => JSON.stringify(v)))].map((s) => JSON.parse(s));
315
298
  }
316
299
  return line.length > 0 ? line : void 0;
317
300
  };
318
301
  const linesToPolyLines3 = (lineSegments) => {
319
- var _a, _b;
320
- if (!Array.isArray(lineSegments) || (lineSegments == null ? void 0 : lineSegments.length) === 0) {
321
- return [];
322
- }
302
+ if (!Array.isArray(lineSegments) || lineSegments.length === 0) return [];
323
303
  const segmentsMapIndexes = {};
324
304
  const polylines = [];
325
- const parsedSegmentIndexes = [];
305
+ const parsedSegmentIndexes = /* @__PURE__ */ new Set();
326
306
  const lineSegmentStrings = lineSegments.map((v) => v.map((c) => c.join(",")));
327
- lineSegmentStrings.forEach(([start, end], i) => {
328
- segmentsMapIndexes[start] = segmentsMapIndexes[start] ? [...segmentsMapIndexes[start] || [], i] : [i];
329
- segmentsMapIndexes[end] = segmentsMapIndexes[end] ? [...segmentsMapIndexes[end] || [], i] : [i];
330
- });
331
307
  for (let i = 0; i < lineSegmentStrings.length; i++) {
332
- if (parsedSegmentIndexes.includes(i)) continue;
333
- 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);
334
315
  let [start, end] = lineSegmentStrings[i];
335
- let polyline = [start, end];
316
+ const polyline = [start, end];
336
317
  while (start && segmentsMapIndexes[start]) {
337
- const nextLineIndex = (_a = segmentsMapIndexes[start]) == null ? void 0 : _a.find(
338
- (lineIndex) => !parsedSegmentIndexes.includes(lineIndex)
318
+ const nextLineIndex = segmentsMapIndexes[start].find(
319
+ (li) => !parsedSegmentIndexes.has(li)
339
320
  );
340
- if (nextLineIndex) {
341
- parsedSegmentIndexes.push(nextLineIndex);
342
- const nextLineSegment = lineSegmentStrings[nextLineIndex];
343
- const nextLineSegmentPointIndex = nextLineSegment[0] === start ? 1 : 0;
344
- 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;
345
325
  polyline.unshift(newPoint);
346
326
  start = newPoint;
347
327
  } else {
@@ -349,14 +329,13 @@ var contoursWorker = createEasyWebWorker2(
349
329
  }
350
330
  }
351
331
  while (end && segmentsMapIndexes[end]) {
352
- const nextLineIndex = (_b = segmentsMapIndexes[end]) == null ? void 0 : _b.find(
353
- (lineIndex) => !parsedSegmentIndexes.includes(lineIndex)
332
+ const nextLineIndex = segmentsMapIndexes[end].find(
333
+ (li) => !parsedSegmentIndexes.has(li)
354
334
  );
355
- if (nextLineIndex) {
356
- parsedSegmentIndexes.push(nextLineIndex);
357
- const nextLineSegment = lineSegmentStrings[nextLineIndex];
358
- const nextLineSegmentPointIndex = nextLineSegment[0] === end ? 1 : 0;
359
- 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;
360
339
  polyline.push(newPoint);
361
340
  end = newPoint;
362
341
  } else {
@@ -369,66 +348,51 @@ var contoursWorker = createEasyWebWorker2(
369
348
  };
370
349
  onMessage((message) => {
371
350
  const { triangles, elevation } = message.payload;
372
- const linesAtElevationE = triangles.reduce((prev, curr) => {
351
+ const linesAtElevation = triangles.reduce((prev, curr) => {
373
352
  const line = contourLineOnFace(curr, elevation);
374
353
  if (line) prev.push(line);
375
354
  return prev;
376
355
  }, []);
377
- message.resolve({
378
- elevation,
379
- polylines: linesToPolyLines3(linesAtElevationE)
380
- });
356
+ message.resolve({ elevation, polylines: linesToPolyLines3(linesAtElevation) });
381
357
  });
382
358
  },
383
359
  { maxWorkers: 10 }
384
360
  );
385
361
  var linesToPolyLines = (lineSegments) => {
386
- var _a, _b;
387
- if (!Array.isArray(lineSegments) || (lineSegments == null ? void 0 : lineSegments.length) === 0) {
388
- return [];
389
- }
362
+ if (!Array.isArray(lineSegments) || lineSegments.length === 0) return [];
390
363
  const segmentsMapIndexes = {};
391
364
  const polylines = [];
392
- const parsedSegmentIndexes = [];
365
+ const parsedSegmentIndexes = /* @__PURE__ */ new Set();
393
366
  const lineSegmentStrings = lineSegments.map((v) => v.map((c) => c.join(",")));
394
- lineSegmentStrings.forEach(([start, end], i) => {
395
- segmentsMapIndexes[start] = segmentsMapIndexes[start] ? [...segmentsMapIndexes[start] || [], i] : [i];
396
- segmentsMapIndexes[end] = segmentsMapIndexes[end] ? [...segmentsMapIndexes[end] || [], i] : [i];
397
- });
398
367
  for (let i = 0; i < lineSegmentStrings.length; i++) {
399
- if (parsedSegmentIndexes.includes(i)) continue;
400
- 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);
401
375
  let [start, end] = lineSegmentStrings[i];
402
- let polyline = [start, end];
376
+ const polyline = [start, end];
403
377
  while (start && segmentsMapIndexes[start]) {
404
- const nextLineIndex = (_a = segmentsMapIndexes[start]) == null ? void 0 : _a.find(
405
- (lineIndex) => !parsedSegmentIndexes.includes(lineIndex)
406
- );
407
- if (nextLineIndex) {
408
- parsedSegmentIndexes.push(nextLineIndex);
409
- const nextLineSegment = lineSegmentStrings[nextLineIndex];
410
- const nextLineSegmentPointIndex = nextLineSegment[0] === start ? 1 : 0;
411
- 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;
412
383
  polyline.unshift(newPoint);
413
384
  start = newPoint;
414
- } else {
415
- start = null;
416
- }
385
+ } else start = null;
417
386
  }
418
387
  while (end && segmentsMapIndexes[end]) {
419
- const nextLineIndex = (_b = segmentsMapIndexes[end]) == null ? void 0 : _b.find(
420
- (lineIndex) => !parsedSegmentIndexes.includes(lineIndex)
421
- );
422
- if (nextLineIndex) {
423
- parsedSegmentIndexes.push(nextLineIndex);
424
- const nextLineSegment = lineSegmentStrings[nextLineIndex];
425
- const nextLineSegmentPointIndex = nextLineSegment[0] === end ? 1 : 0;
426
- 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;
427
393
  polyline.push(newPoint);
428
394
  end = newPoint;
429
- } else {
430
- end = null;
431
- }
395
+ } else end = null;
432
396
  }
433
397
  polylines.push(polyline.map((coord) => coord.split(",").map((v) => parseFloat(v))));
434
398
  }
@@ -442,10 +406,10 @@ var contourElevations = (minElevation, maxElevation, interval) => {
442
406
  throw new Error(`No contour lines at interval: ${interval} between elevation ${minElevation} and ${maxElevation}`);
443
407
  }
444
408
  const elevations = [];
445
- let elevation = Math.ceil(minElevation / interval) * interval;
446
- while (elevation < maxElevation) {
447
- elevations.push(elevation);
448
- 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);
449
413
  }
450
414
  return elevations;
451
415
  };
@@ -455,31 +419,55 @@ var constructGeojson = (elevationData) => {
455
419
  return prev.concat(
456
420
  polylines.map((polyline) => ({
457
421
  type: "Feature",
458
- geometry: {
459
- type: "LineString",
460
- coordinates: polyline
461
- },
462
- properties: {
463
- z: elevation
464
- }
422
+ geometry: { type: "LineString", coordinates: polyline },
423
+ properties: { z: elevation }
465
424
  }))
466
425
  );
467
426
  }, []);
468
- return {
469
- type: "FeatureCollection",
470
- features
471
- };
427
+ return { type: "FeatureCollection", features };
472
428
  };
473
- var getContours = (data, interval = 2) => __async(null, null, function* () {
474
- const triangles = data.surfaceDefinition.faces.map(
475
- (face) => face.map((vert) => data.surfaceDefinition.points[vert])
476
- );
477
- const [minElevation, maxElevation] = data.surfaceDefinition.points.reduce(
478
- ([prevMin, prevMax], curr) => [Math.min(prevMin, curr[2]), Math.max(prevMax, curr[2])],
479
- [Infinity, -Infinity]
480
- );
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);
481
458
  const elevations = contourElevations(minElevation, maxElevation, interval);
482
- 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
+ );
483
471
  return constructGeojson(elevationPolylines);
484
472
  });
485
473
  var get_contours_default = getContours;
@@ -562,8 +550,40 @@ var reprojectGeoJson = (geojson, sourceProjection, targetProjection = "WGS84", k
562
550
  return geojson;
563
551
  };
564
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;
565
583
  export {
584
+ precomputeSurfaceData,
566
585
  reproject_geojson_default as reprojectGeoJson,
567
586
  to_geojson_contours_default as toGeojsonContours,
568
- to_glb_default as toGlb
587
+ to_glb_default as toGlb,
588
+ to_glb_and_contours_default as toGlbAndContours
569
589
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "landxml",
3
- "version": "0.6.6",
3
+ "version": "0.7.1",
4
4
  "description": "Parse LandXML surfaces on the modern web.",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",