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.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,134 +178,99 @@ 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
 
267
251
  // src/public/to-glb.ts
268
252
  var toGlb = (landXmlString, center = "auto", surfaceId = -1) => __async(null, null, function* () {
269
- const requestedCenter = center == "origin" ? [0, 0] : center === "auto" ? void 0 : center;
270
253
  let requestedParsedSurfaces = filter_by_surfaceId_default(yield parse_xml_default(landXmlString), surfaceId);
254
+ let resolvedCenter;
255
+ if (center === "origin") {
256
+ resolvedCenter = [0, 0];
257
+ } else if (center === "auto") {
258
+ const allPoints = requestedParsedSurfaces.flatMap((s) => s.surfaceDefinition.points);
259
+ resolvedCenter = findXYAxisMedians(allPoints);
260
+ } else {
261
+ resolvedCenter = center;
262
+ }
271
263
  const glbs = yield Promise.all(
272
264
  requestedParsedSurfaces.map(
273
265
  (surface) => new Promise((resolve, reject) => __async(null, null, function* () {
274
266
  try {
275
- const { glb, center: center2 } = yield get_glb_default(surface, requestedCenter);
267
+ const { glb } = yield get_glb_default(surface, resolvedCenter);
276
268
  const _a = surface, { surfaceDefinition } = _a, rest = __objRest(_a, ["surfaceDefinition"]);
277
269
  resolve(__spreadProps(__spreadValues({}, rest), {
278
270
  glb,
279
- center: center2,
271
+ center: resolvedCenter,
280
272
  download: () => {
281
- download_glb_default(glb, surface.name.replace(/\.xml$/, `${JSON.stringify(center2)}.glb`));
273
+ download_glb_default(glb, surface.name.replace(/\.xml$/, `${JSON.stringify(resolvedCenter)}.glb`));
282
274
  }
283
275
  }));
284
276
  } catch (e) {
@@ -299,49 +291,45 @@ var contoursWorker = createEasyWebWorker2(
299
291
  let vertsAtElevation = 0;
300
292
  let line = [];
301
293
  for (let i = 0; i < face.length; i++) {
302
- let vertex1 = face[i];
303
- let vertex2 = face[(i + 1) % face.length];
294
+ const vertex1 = face[i];
295
+ const vertex2 = face[(i + 1) % face.length];
304
296
  if (vertex1[2] === z) vertsAtElevation++;
305
297
  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]);
298
+ const t = (z - vertex1[2]) / (vertex2[2] - vertex1[2]);
307
299
  line.push([vertex1[0] + t * (vertex2[0] - vertex1[0]), vertex1[1] + t * (vertex2[1] - vertex1[1])]);
308
300
  }
309
301
  }
310
302
  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;
303
+ if (line.length === 2 && line[0][0] === line[1][0] && line[0][1] === line[1][1]) return void 0;
313
304
  if (line.length > 2) {
314
305
  line = [...new Set(line.map((v) => JSON.stringify(v)))].map((s) => JSON.parse(s));
315
306
  }
316
307
  return line.length > 0 ? line : void 0;
317
308
  };
318
309
  const linesToPolyLines3 = (lineSegments) => {
319
- var _a, _b;
320
- if (!Array.isArray(lineSegments) || (lineSegments == null ? void 0 : lineSegments.length) === 0) {
321
- return [];
322
- }
310
+ if (!Array.isArray(lineSegments) || lineSegments.length === 0) return [];
323
311
  const segmentsMapIndexes = {};
324
312
  const polylines = [];
325
- const parsedSegmentIndexes = [];
313
+ const parsedSegmentIndexes = /* @__PURE__ */ new Set();
326
314
  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
315
  for (let i = 0; i < lineSegmentStrings.length; i++) {
332
- if (parsedSegmentIndexes.includes(i)) continue;
333
- parsedSegmentIndexes.push(i);
316
+ const [start, end] = lineSegmentStrings[i];
317
+ segmentsMapIndexes[start] = segmentsMapIndexes[start] ? [...segmentsMapIndexes[start], i] : [i];
318
+ segmentsMapIndexes[end] = segmentsMapIndexes[end] ? [...segmentsMapIndexes[end], i] : [i];
319
+ }
320
+ for (let i = 0; i < lineSegmentStrings.length; i++) {
321
+ if (parsedSegmentIndexes.has(i)) continue;
322
+ parsedSegmentIndexes.add(i);
334
323
  let [start, end] = lineSegmentStrings[i];
335
- let polyline = [start, end];
324
+ const polyline = [start, end];
336
325
  while (start && segmentsMapIndexes[start]) {
337
- const nextLineIndex = (_a = segmentsMapIndexes[start]) == null ? void 0 : _a.find(
338
- (lineIndex) => !parsedSegmentIndexes.includes(lineIndex)
326
+ const nextLineIndex = segmentsMapIndexes[start].find(
327
+ (li) => !parsedSegmentIndexes.has(li)
339
328
  );
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];
329
+ if (nextLineIndex !== void 0) {
330
+ parsedSegmentIndexes.add(nextLineIndex);
331
+ const [a, b] = lineSegmentStrings[nextLineIndex];
332
+ const newPoint = a === start ? b : a;
345
333
  polyline.unshift(newPoint);
346
334
  start = newPoint;
347
335
  } else {
@@ -349,14 +337,13 @@ var contoursWorker = createEasyWebWorker2(
349
337
  }
350
338
  }
351
339
  while (end && segmentsMapIndexes[end]) {
352
- const nextLineIndex = (_b = segmentsMapIndexes[end]) == null ? void 0 : _b.find(
353
- (lineIndex) => !parsedSegmentIndexes.includes(lineIndex)
340
+ const nextLineIndex = segmentsMapIndexes[end].find(
341
+ (li) => !parsedSegmentIndexes.has(li)
354
342
  );
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];
343
+ if (nextLineIndex !== void 0) {
344
+ parsedSegmentIndexes.add(nextLineIndex);
345
+ const [a, b] = lineSegmentStrings[nextLineIndex];
346
+ const newPoint = a === end ? b : a;
360
347
  polyline.push(newPoint);
361
348
  end = newPoint;
362
349
  } else {
@@ -369,66 +356,51 @@ var contoursWorker = createEasyWebWorker2(
369
356
  };
370
357
  onMessage((message) => {
371
358
  const { triangles, elevation } = message.payload;
372
- const linesAtElevationE = triangles.reduce((prev, curr) => {
359
+ const linesAtElevation = triangles.reduce((prev, curr) => {
373
360
  const line = contourLineOnFace(curr, elevation);
374
361
  if (line) prev.push(line);
375
362
  return prev;
376
363
  }, []);
377
- message.resolve({
378
- elevation,
379
- polylines: linesToPolyLines3(linesAtElevationE)
380
- });
364
+ message.resolve({ elevation, polylines: linesToPolyLines3(linesAtElevation) });
381
365
  });
382
366
  },
383
367
  { maxWorkers: 10 }
384
368
  );
385
369
  var linesToPolyLines = (lineSegments) => {
386
- var _a, _b;
387
- if (!Array.isArray(lineSegments) || (lineSegments == null ? void 0 : lineSegments.length) === 0) {
388
- return [];
389
- }
370
+ if (!Array.isArray(lineSegments) || lineSegments.length === 0) return [];
390
371
  const segmentsMapIndexes = {};
391
372
  const polylines = [];
392
- const parsedSegmentIndexes = [];
373
+ const parsedSegmentIndexes = /* @__PURE__ */ new Set();
393
374
  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
375
  for (let i = 0; i < lineSegmentStrings.length; i++) {
399
- if (parsedSegmentIndexes.includes(i)) continue;
400
- parsedSegmentIndexes.push(i);
376
+ const [start, end] = lineSegmentStrings[i];
377
+ segmentsMapIndexes[start] = segmentsMapIndexes[start] ? [...segmentsMapIndexes[start], i] : [i];
378
+ segmentsMapIndexes[end] = segmentsMapIndexes[end] ? [...segmentsMapIndexes[end], i] : [i];
379
+ }
380
+ for (let i = 0; i < lineSegmentStrings.length; i++) {
381
+ if (parsedSegmentIndexes.has(i)) continue;
382
+ parsedSegmentIndexes.add(i);
401
383
  let [start, end] = lineSegmentStrings[i];
402
- let polyline = [start, end];
384
+ const polyline = [start, end];
403
385
  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];
386
+ const nextLineIndex = segmentsMapIndexes[start].find((li) => !parsedSegmentIndexes.has(li));
387
+ if (nextLineIndex !== void 0) {
388
+ parsedSegmentIndexes.add(nextLineIndex);
389
+ const [a, b] = lineSegmentStrings[nextLineIndex];
390
+ const newPoint = a === start ? b : a;
412
391
  polyline.unshift(newPoint);
413
392
  start = newPoint;
414
- } else {
415
- start = null;
416
- }
393
+ } else start = null;
417
394
  }
418
395
  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];
396
+ const nextLineIndex = segmentsMapIndexes[end].find((li) => !parsedSegmentIndexes.has(li));
397
+ if (nextLineIndex !== void 0) {
398
+ parsedSegmentIndexes.add(nextLineIndex);
399
+ const [a, b] = lineSegmentStrings[nextLineIndex];
400
+ const newPoint = a === end ? b : a;
427
401
  polyline.push(newPoint);
428
402
  end = newPoint;
429
- } else {
430
- end = null;
431
- }
403
+ } else end = null;
432
404
  }
433
405
  polylines.push(polyline.map((coord) => coord.split(",").map((v) => parseFloat(v))));
434
406
  }
@@ -442,10 +414,10 @@ var contourElevations = (minElevation, maxElevation, interval) => {
442
414
  throw new Error(`No contour lines at interval: ${interval} between elevation ${minElevation} and ${maxElevation}`);
443
415
  }
444
416
  const elevations = [];
445
- let elevation = Math.ceil(minElevation / interval) * interval;
446
- while (elevation < maxElevation) {
447
- elevations.push(elevation);
448
- elevation += interval;
417
+ const firstStep = Math.ceil(minElevation / interval);
418
+ const lastStep = Math.ceil(maxElevation / interval) - 1;
419
+ for (let step = firstStep; step <= lastStep; step++) {
420
+ elevations.push(step * interval);
449
421
  }
450
422
  return elevations;
451
423
  };
@@ -455,31 +427,55 @@ var constructGeojson = (elevationData) => {
455
427
  return prev.concat(
456
428
  polylines.map((polyline) => ({
457
429
  type: "Feature",
458
- geometry: {
459
- type: "LineString",
460
- coordinates: polyline
461
- },
462
- properties: {
463
- z: elevation
464
- }
430
+ geometry: { type: "LineString", coordinates: polyline },
431
+ properties: { z: elevation }
465
432
  }))
466
433
  );
467
434
  }, []);
468
- return {
469
- type: "FeatureCollection",
470
- features
471
- };
435
+ return { type: "FeatureCollection", features };
472
436
  };
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
- );
437
+ var precomputeSurfaceData = (data) => {
438
+ const { points, faces } = data.surfaceDefinition;
439
+ let minElevation = Infinity;
440
+ let maxElevation = -Infinity;
441
+ for (const pt of points) {
442
+ if (pt[2] < minElevation) minElevation = pt[2];
443
+ if (pt[2] > maxElevation) maxElevation = pt[2];
444
+ }
445
+ const triangles = faces.map((face) => face.map((vert) => points[vert]));
446
+ return { triangles, minElevation, maxElevation };
447
+ };
448
+ var bucketTrianglesByElevation = (triangles, elevations, interval) => {
449
+ const trianglesByElevation = /* @__PURE__ */ new Map();
450
+ for (const e of elevations) trianglesByElevation.set(e, []);
451
+ for (const tri of triangles) {
452
+ const zMin = Math.min(tri[0][2], tri[1][2], tri[2][2]);
453
+ const zMax = Math.max(tri[0][2], tri[1][2], tri[2][2]);
454
+ const firstStep = Math.ceil(zMin / interval);
455
+ const lastStep = Math.floor(zMax / interval);
456
+ for (let step = firstStep; step <= lastStep; step++) {
457
+ const rounded = step * interval;
458
+ const bucket = trianglesByElevation.get(rounded);
459
+ if (bucket) bucket.push(tri);
460
+ }
461
+ }
462
+ return trianglesByElevation;
463
+ };
464
+ var getContours = (data, interval = 2, precomputed) => __async(null, null, function* () {
465
+ const { triangles, minElevation, maxElevation } = precomputed != null ? precomputed : precomputeSurfaceData(data);
481
466
  const elevations = contourElevations(minElevation, maxElevation, interval);
482
- const elevationPolylines = yield Promise.all(elevations.map((elevation) => contoursWorker.send({ triangles, elevation })));
467
+ const trianglesByElevation = bucketTrianglesByElevation(triangles, elevations, interval);
468
+ const elevationPolylines = yield Promise.all(
469
+ elevations.map(
470
+ (elevation) => {
471
+ var _a;
472
+ return contoursWorker.send({
473
+ triangles: (_a = trianglesByElevation.get(elevation)) != null ? _a : [],
474
+ elevation
475
+ });
476
+ }
477
+ )
478
+ );
483
479
  return constructGeojson(elevationPolylines);
484
480
  });
485
481
  var get_contours_default = getContours;
@@ -562,8 +558,48 @@ var reprojectGeoJson = (geojson, sourceProjection, targetProjection = "WGS84", k
562
558
  return geojson;
563
559
  };
564
560
  var reproject_geojson_default = reprojectGeoJson;
561
+
562
+ // src/public/to-glb-and-contours.ts
563
+ var toGlbAndContours = (landXmlString, contourInterval = 2, generateOutline = true, center = "auto", surfaceId = -1) => __async(null, null, function* () {
564
+ const requestedParsedSurfaces = filter_by_surfaceId_default(yield parse_xml_default(landXmlString), surfaceId);
565
+ let resolvedCenter;
566
+ if (center === "origin") {
567
+ resolvedCenter = [0, 0];
568
+ } else if (center === "auto") {
569
+ const allPoints = requestedParsedSurfaces.flatMap((s) => s.surfaceDefinition.points);
570
+ resolvedCenter = findXYAxisMedians(allPoints);
571
+ } else {
572
+ resolvedCenter = center;
573
+ }
574
+ const results = yield Promise.all(
575
+ requestedParsedSurfaces.map((surface) => __async(null, null, function* () {
576
+ const precomputed = precomputeSurfaceData(surface);
577
+ const [{ glb }, geojson] = yield Promise.all([
578
+ get_glb_default(surface, resolvedCenter),
579
+ get_contours_default(surface, contourInterval, precomputed)
580
+ ]);
581
+ if (generateOutline) {
582
+ const outlineGeojson = get_outline_default(surface);
583
+ geojson.features = [...geojson.features, ...outlineGeojson.features];
584
+ }
585
+ const _a = surface, { surfaceDefinition } = _a, rest = __objRest(_a, ["surfaceDefinition"]);
586
+ return __spreadProps(__spreadValues({}, rest), {
587
+ glb,
588
+ center: resolvedCenter,
589
+ download: () => {
590
+ download_glb_default(glb, surface.name.replace(/\.xml$/, `${JSON.stringify(resolvedCenter)}.glb`));
591
+ },
592
+ geojson
593
+ });
594
+ }))
595
+ );
596
+ return results;
597
+ });
598
+ var to_glb_and_contours_default = toGlbAndContours;
565
599
  export {
600
+ precomputeSurfaceData,
566
601
  reproject_geojson_default as reprojectGeoJson,
567
602
  to_geojson_contours_default as toGeojsonContours,
568
- to_glb_default as toGlb
603
+ to_glb_default as toGlb,
604
+ to_glb_and_contours_default as toGlbAndContours
569
605
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "landxml",
3
- "version": "0.6.6",
3
+ "version": "0.8.0",
4
4
  "description": "Parse LandXML surfaces on the modern web.",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",