landxml 0.5.2 → 0.6.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/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # landxml
2
2
 
3
+ ## 0.6.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 6c547e2: deploy options fix
8
+
9
+ ## 0.6.0
10
+
11
+ ### Minor Changes
12
+
13
+ - 330e3e8: Introduced web workers for some of the processing heavy workflows
14
+
3
15
  ## 0.5.2
4
16
 
5
17
  ### Patch Changes
package/dist/index.js CHANGED
@@ -145,47 +145,109 @@ var downloadGlb = (glbData, fileName) => {
145
145
  var download_glb_default = downloadGlb;
146
146
 
147
147
  // src/private/parse-xml.ts
148
+ var import_easy_web_worker = require("easy-web-worker");
148
149
  var import_xml_js = __toESM(require("xml-js"));
149
- var parseXML = (xmlString) => __async(void 0, null, function* () {
150
- const { LandXML } = import_xml_js.default.xml2js(xmlString, {
151
- compact: true,
152
- attributesKey: "attr",
153
- textKey: "content"
154
- });
155
- const landXML_surfaces = Array.isArray(LandXML.Surfaces.Surface) ? LandXML.Surfaces.Surface : [LandXML.Surfaces.Surface];
156
- return landXML_surfaces.map((surface) => {
157
- var _a, _b;
158
- let points = [];
159
- let faces = [];
160
- let pointIdMap = {};
161
- surface.Definition.Pnts.P.forEach((pt) => {
162
- const { attr, content } = pt;
163
- const [y, x, z] = content.split(" ").map((v) => parseFloat(v));
164
- points.push([x, y, z]);
165
- pointIdMap[attr.id] = points.length - 1;
166
- }, []);
167
- faces = surface.Definition.Faces.F.reduce((faceList, face) => {
168
- const { attr, content } = face;
169
- if ((attr == null ? void 0 : attr.i) === "1")
170
- return faceList;
171
- const [a, b, c] = content.split(" ").map((v) => pointIdMap[v]);
172
- if ([a, b, c].filter((v) => typeof v === "undefined").length > 0) {
173
- throw `Invalid LandXML. A face is referencing a point that doesn't exist. Face is referencing points: ${content}`;
174
- }
175
- return faceList.concat([[a, b, c]]);
176
- }, []);
177
- return {
178
- sourceFile: LandXML.Project.attr.name,
179
- timeStamp: LandXML.Application.attr.timeStamp,
180
- name: surface.attr.name,
181
- description: surface.attr.desc,
182
- wktString: (_b = (_a = LandXML.CoordinateSystem) == null ? void 0 : _a.attr) == null ? void 0 : _b.ogcWktCode,
183
- surfaceDefinition: {
184
- points,
185
- faces
150
+ var surfaceDefWorker = (0, import_easy_web_worker.createEasyWebWorker)(
151
+ ({ onMessage }) => {
152
+ onMessage((message) => {
153
+ const { isPoint, arr, idMap } = message.payload;
154
+ if (isPoint) {
155
+ message.resolve(
156
+ arr.map((pt) => [pt.attr.id, pt.content.split(" ").map(Number)]).map((v) => [v[0], [v[1][1], v[1][0], v[1][2]]])
157
+ );
158
+ } else {
159
+ message.resolve(
160
+ arr.flatMap((f) => {
161
+ var _a;
162
+ if (typeof f === "string")
163
+ return [f.split(" ").map((id) => idMap == null ? void 0 : idMap.indexOf(id))];
164
+ if (((_a = f == null ? void 0 : f.attr) == null ? void 0 : _a.i) === "1")
165
+ return [];
166
+ return [f.content.split(" ").map((id) => idMap == null ? void 0 : idMap.indexOf(id))];
167
+ })
168
+ );
186
169
  }
187
- };
188
- });
170
+ });
171
+ },
172
+ { maxWorkers: 10 }
173
+ );
174
+ var parseXML = (xmlString) => __async(void 0, null, function* () {
175
+ return new Promise((resolve, reject) => __async(void 0, null, function* () {
176
+ var _a, _b, _c, _d, _e;
177
+ const parsed = import_xml_js.default.xml2js(xmlString, {
178
+ compact: true,
179
+ attributesKey: "attr",
180
+ textKey: "content"
181
+ });
182
+ if (typeof ((_b = (_a = parsed.LandXML) == null ? void 0 : _a.Surfaces) == null ? void 0 : _b.Surface) === "undefined") {
183
+ throw new Error("LandXML doesn't contain any surfaces");
184
+ return;
185
+ }
186
+ if (!Array.isArray(parsed.LandXML.Surfaces.Surface)) {
187
+ parsed.LandXML.Surfaces.Surface = [parsed.LandXML.Surfaces.Surface];
188
+ }
189
+ let sourceFile = parsed.LandXML.Project.attr.name || "Undefined source";
190
+ let timeStamp = parsed.LandXML.Application.attr.timeStamp || "";
191
+ let wktString = ((_e = (_d = (_c = parsed.LandXML) == null ? void 0 : _c.CoordinateSystem) == null ? void 0 : _d.attr) == null ? void 0 : _e.ogcWktCode) || void 0;
192
+ const surfaces = parsed.LandXML.Surfaces.Surface.map(
193
+ (surface) => __async(void 0, null, function* () {
194
+ return new Promise((resolve2, reject2) => __async(void 0, null, function* () {
195
+ const { name, desc } = surface.attr;
196
+ const Pnts = surface.Definition.Pnts.P;
197
+ const Faces = surface.Definition.Faces.F;
198
+ let ptsIdArray = [];
199
+ let faces = [];
200
+ if (Pnts.length > 1e4) {
201
+ const sliceIndexes = [...Array(20).keys()].map((i) => Math.round(Pnts.length / 20 * i));
202
+ ptsIdArray = (yield Promise.all(
203
+ sliceIndexes.map(
204
+ (v, i, a) => new Promise((resolve3, reject3) => __async(void 0, null, function* () {
205
+ const pts = yield surfaceDefWorker.send({
206
+ isPoint: true,
207
+ arr: Pnts.slice(a[i], a[i + 1] || Pnts.length)
208
+ });
209
+ resolve3(pts);
210
+ }))
211
+ )
212
+ )).reduce((prev, curr) => [...prev, ...curr], []);
213
+ } else {
214
+ ptsIdArray = yield surfaceDefWorker.send({ arr: Pnts, isPoint: true });
215
+ }
216
+ const points = ptsIdArray.map((v) => v[1]);
217
+ const pointsIdMap = ptsIdArray.map((v) => v[0]);
218
+ if (Faces.length > 1e4) {
219
+ const sliceIndexes = [...Array(20).keys()].map((i) => Math.round(Faces.length / 20 * i));
220
+ faces = (yield Promise.all(
221
+ sliceIndexes.map(
222
+ (v, i, a) => new Promise((resolve3, reject3) => __async(void 0, null, function* () {
223
+ const fcs = yield surfaceDefWorker.send({
224
+ isPoint: false,
225
+ arr: Faces.slice(a[i], a[i + 1] || Faces.length),
226
+ idMap: pointsIdMap
227
+ });
228
+ resolve3(fcs);
229
+ }))
230
+ )
231
+ )).reduce((prev, curr) => [...prev, ...curr], []);
232
+ } else {
233
+ faces = yield surfaceDefWorker.send({ arr: Faces, isPoint: false, idMap: pointsIdMap });
234
+ }
235
+ resolve2({
236
+ sourceFile,
237
+ timeStamp: timeStamp || "",
238
+ name,
239
+ description: desc || "",
240
+ wktString,
241
+ surfaceDefinition: {
242
+ points,
243
+ faces
244
+ }
245
+ });
246
+ }));
247
+ })
248
+ );
249
+ resolve(yield Promise.all(surfaces));
250
+ }));
189
251
  });
190
252
  var parse_xml_default = parseXML;
191
253
 
@@ -217,32 +279,104 @@ var toGlb = (landXmlString, center = "auto", surfaceId = -1) => __async(void 0,
217
279
  var to_glb_default = toGlb;
218
280
 
219
281
  // src/private/get-contours.ts
220
- var contourLineOnFace = (face, z) => {
221
- let vertsAtElevation = 0;
222
- let line = [];
223
- for (let i = 0; i < face.length; i++) {
224
- let vertex1 = face[i];
225
- let vertex2 = face[(i + 1) % face.length];
226
- if (vertex1[2] === z)
227
- vertsAtElevation++;
228
- if ((vertex1[2] <= z && vertex2[2] >= z || vertex1[2] >= z && vertex2[2] <= z) && !Number.isNaN((z - vertex1[2]) / (vertex2[2] - vertex1[2]))) {
229
- let t = (z - vertex1[2]) / (vertex2[2] - vertex1[2]);
230
- line.push([vertex1[0] + t * (vertex2[0] - vertex1[0]), vertex1[1] + t * (vertex2[1] - vertex1[1])]);
231
- }
232
- }
233
- if (vertsAtElevation >= 2 && face.map((f) => f[2]).reduce((a, b) => a + b) > z * face.length)
234
- return void 0;
235
- if (line.length === 2 && line[0][0] === line[1][0] && line[0][1] === line[1][1])
236
- return void 0;
237
- if (line.length > 2) {
238
- line = [...new Set(line.map((v) => JSON.stringify(v)))].map((s) => JSON.parse(s));
239
- }
240
- return line.length > 0 ? line : void 0;
241
- };
282
+ var import_easy_web_worker2 = require("easy-web-worker");
283
+ var contoursWorker = (0, import_easy_web_worker2.createEasyWebWorker)(
284
+ ({ onMessage }) => {
285
+ const contourLineOnFace = (face, z) => {
286
+ let vertsAtElevation = 0;
287
+ let line = [];
288
+ for (let i = 0; i < face.length; i++) {
289
+ let vertex1 = face[i];
290
+ let vertex2 = face[(i + 1) % face.length];
291
+ if (vertex1[2] === z)
292
+ vertsAtElevation++;
293
+ if ((vertex1[2] <= z && vertex2[2] >= z || vertex1[2] >= z && vertex2[2] <= z) && !Number.isNaN((z - vertex1[2]) / (vertex2[2] - vertex1[2]))) {
294
+ let t = (z - vertex1[2]) / (vertex2[2] - vertex1[2]);
295
+ line.push([vertex1[0] + t * (vertex2[0] - vertex1[0]), vertex1[1] + t * (vertex2[1] - vertex1[1])]);
296
+ }
297
+ }
298
+ if (vertsAtElevation >= 2 && face.map((f) => f[2]).reduce((a, b) => a + b) > z * face.length)
299
+ return void 0;
300
+ if (line.length === 2 && line[0][0] === line[1][0] && line[0][1] === line[1][1])
301
+ return void 0;
302
+ if (line.length > 2) {
303
+ line = [...new Set(line.map((v) => JSON.stringify(v)))].map((s) => JSON.parse(s));
304
+ }
305
+ return line.length > 0 ? line : void 0;
306
+ };
307
+ const linesToPolyLines3 = (lineSegments) => {
308
+ var _a, _b;
309
+ if (!Array.isArray(lineSegments) || (lineSegments == null ? void 0 : lineSegments.length) === 0) {
310
+ return [];
311
+ }
312
+ const segmentsMapIndexes = {};
313
+ const polylines = [];
314
+ const parsedSegmentIndexes = [];
315
+ const lineSegmentStrings = lineSegments.map((v) => v.map((c) => c.join(",")));
316
+ lineSegmentStrings.forEach(([start, end], 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.includes(i))
322
+ continue;
323
+ parsedSegmentIndexes.push(i);
324
+ let [start, end] = lineSegmentStrings[i];
325
+ let polyline = [start, end];
326
+ while (start && segmentsMapIndexes[start]) {
327
+ const nextLineIndex = (_a = segmentsMapIndexes[start]) == null ? void 0 : _a.find(
328
+ (lineIndex) => !parsedSegmentIndexes.includes(lineIndex)
329
+ );
330
+ if (nextLineIndex) {
331
+ parsedSegmentIndexes.push(nextLineIndex);
332
+ const nextLineSegment = lineSegmentStrings[nextLineIndex];
333
+ const nextLineSegmentPointIndex = nextLineSegment[0] === start ? 1 : 0;
334
+ const newPoint = nextLineSegment[nextLineSegmentPointIndex];
335
+ polyline.unshift(newPoint);
336
+ start = newPoint;
337
+ } else {
338
+ start = null;
339
+ }
340
+ }
341
+ while (end && segmentsMapIndexes[end]) {
342
+ const nextLineIndex = (_b = segmentsMapIndexes[end]) == null ? void 0 : _b.find(
343
+ (lineIndex) => !parsedSegmentIndexes.includes(lineIndex)
344
+ );
345
+ if (nextLineIndex) {
346
+ parsedSegmentIndexes.push(nextLineIndex);
347
+ const nextLineSegment = lineSegmentStrings[nextLineIndex];
348
+ const nextLineSegmentPointIndex = nextLineSegment[0] === end ? 1 : 0;
349
+ const newPoint = nextLineSegment[nextLineSegmentPointIndex];
350
+ polyline.push(newPoint);
351
+ end = newPoint;
352
+ } else {
353
+ end = null;
354
+ }
355
+ }
356
+ polylines.push(polyline.map((coord) => coord.split(",").map((v) => parseFloat(v))));
357
+ }
358
+ return polylines;
359
+ };
360
+ onMessage((message) => {
361
+ const { triangles, elevation } = message.payload;
362
+ const linesAtElevationE = triangles.reduce((prev, curr) => {
363
+ const line = contourLineOnFace(curr, elevation);
364
+ if (line)
365
+ prev.push(line);
366
+ return prev;
367
+ }, []);
368
+ message.resolve({
369
+ elevation,
370
+ polylines: linesToPolyLines3(linesAtElevationE)
371
+ });
372
+ });
373
+ },
374
+ { maxWorkers: 10 }
375
+ );
242
376
  var linesToPolyLines = (lineSegments) => {
243
377
  var _a, _b;
244
- if (!Array.isArray(lineSegments) || lineSegments.length === 0) {
245
- throw new Error("Invalid input: Please provide a non-empty array of line segments.");
378
+ if (!Array.isArray(lineSegments) || (lineSegments == null ? void 0 : lineSegments.length) === 0) {
379
+ return [];
246
380
  }
247
381
  const segmentsMapIndexes = {};
248
382
  const polylines = [];
@@ -337,23 +471,7 @@ var getContours = (data, interval = 2) => __async(void 0, null, function* () {
337
471
  [Infinity, -Infinity]
338
472
  );
339
473
  const elevations = contourElevations(minElevation, maxElevation, interval);
340
- const elevationPolylines = elevations.map((e) => {
341
- const linesAtElevationE = triangles.reduce((prev, curr) => {
342
- const line = contourLineOnFace(curr, e);
343
- if (line)
344
- prev.push(line);
345
- return prev;
346
- }, []);
347
- const polylinesAtElevationE = linesToPolyLines(linesAtElevationE);
348
- if (e === 442) {
349
- console.log("linesAtElevationE", JSON.stringify(linesAtElevationE));
350
- console.log("polylinesAtElevationE", JSON.stringify(polylinesAtElevationE));
351
- }
352
- return {
353
- elevation: e,
354
- polylines: polylinesAtElevationE
355
- };
356
- });
474
+ const elevationPolylines = yield Promise.all(elevations.map((elevation) => contoursWorker.send({ triangles, elevation })));
357
475
  return constructGeojson(elevationPolylines);
358
476
  });
359
477
  var get_contours_default = getContours;
@@ -408,7 +526,6 @@ var toGeojsonContours = (landXmlString, contourInterval = 2, generateOutline = t
408
526
  const geojson = yield get_contours_default(surface, contourInterval);
409
527
  if (generateOutline) {
410
528
  const outlineGeojson = get_outline_default(surface);
411
- console.log(outlineGeojson.features);
412
529
  geojson.features = [...geojson.features, ...outlineGeojson.features];
413
530
  }
414
531
  const _a = surface, { surfaceDefinition } = _a, rest = __objRest(_a, ["surfaceDefinition"]);
package/dist/index.mjs CHANGED
@@ -110,47 +110,109 @@ var downloadGlb = (glbData, fileName) => {
110
110
  var download_glb_default = downloadGlb;
111
111
 
112
112
  // src/private/parse-xml.ts
113
+ import { createEasyWebWorker } from "easy-web-worker";
113
114
  import convert from "xml-js";
114
- var parseXML = (xmlString) => __async(void 0, null, function* () {
115
- const { LandXML } = convert.xml2js(xmlString, {
116
- compact: true,
117
- attributesKey: "attr",
118
- textKey: "content"
119
- });
120
- const landXML_surfaces = Array.isArray(LandXML.Surfaces.Surface) ? LandXML.Surfaces.Surface : [LandXML.Surfaces.Surface];
121
- return landXML_surfaces.map((surface) => {
122
- var _a, _b;
123
- let points = [];
124
- let faces = [];
125
- let pointIdMap = {};
126
- surface.Definition.Pnts.P.forEach((pt) => {
127
- const { attr, content } = pt;
128
- const [y, x, z] = content.split(" ").map((v) => parseFloat(v));
129
- points.push([x, y, z]);
130
- pointIdMap[attr.id] = points.length - 1;
131
- }, []);
132
- faces = surface.Definition.Faces.F.reduce((faceList, face) => {
133
- const { attr, content } = face;
134
- if ((attr == null ? void 0 : attr.i) === "1")
135
- return faceList;
136
- const [a, b, c] = content.split(" ").map((v) => pointIdMap[v]);
137
- if ([a, b, c].filter((v) => typeof v === "undefined").length > 0) {
138
- throw `Invalid LandXML. A face is referencing a point that doesn't exist. Face is referencing points: ${content}`;
139
- }
140
- return faceList.concat([[a, b, c]]);
141
- }, []);
142
- return {
143
- sourceFile: LandXML.Project.attr.name,
144
- timeStamp: LandXML.Application.attr.timeStamp,
145
- name: surface.attr.name,
146
- description: surface.attr.desc,
147
- wktString: (_b = (_a = LandXML.CoordinateSystem) == null ? void 0 : _a.attr) == null ? void 0 : _b.ogcWktCode,
148
- surfaceDefinition: {
149
- points,
150
- faces
115
+ var surfaceDefWorker = createEasyWebWorker(
116
+ ({ onMessage }) => {
117
+ onMessage((message) => {
118
+ const { isPoint, arr, idMap } = message.payload;
119
+ if (isPoint) {
120
+ message.resolve(
121
+ arr.map((pt) => [pt.attr.id, pt.content.split(" ").map(Number)]).map((v) => [v[0], [v[1][1], v[1][0], v[1][2]]])
122
+ );
123
+ } else {
124
+ message.resolve(
125
+ arr.flatMap((f) => {
126
+ var _a;
127
+ if (typeof f === "string")
128
+ return [f.split(" ").map((id) => idMap == null ? void 0 : idMap.indexOf(id))];
129
+ if (((_a = f == null ? void 0 : f.attr) == null ? void 0 : _a.i) === "1")
130
+ return [];
131
+ return [f.content.split(" ").map((id) => idMap == null ? void 0 : idMap.indexOf(id))];
132
+ })
133
+ );
151
134
  }
152
- };
153
- });
135
+ });
136
+ },
137
+ { maxWorkers: 10 }
138
+ );
139
+ var parseXML = (xmlString) => __async(void 0, null, function* () {
140
+ return new Promise((resolve, reject) => __async(void 0, null, function* () {
141
+ var _a, _b, _c, _d, _e;
142
+ const parsed = convert.xml2js(xmlString, {
143
+ compact: true,
144
+ attributesKey: "attr",
145
+ textKey: "content"
146
+ });
147
+ if (typeof ((_b = (_a = parsed.LandXML) == null ? void 0 : _a.Surfaces) == null ? void 0 : _b.Surface) === "undefined") {
148
+ throw new Error("LandXML doesn't contain any surfaces");
149
+ return;
150
+ }
151
+ if (!Array.isArray(parsed.LandXML.Surfaces.Surface)) {
152
+ parsed.LandXML.Surfaces.Surface = [parsed.LandXML.Surfaces.Surface];
153
+ }
154
+ let sourceFile = parsed.LandXML.Project.attr.name || "Undefined source";
155
+ let timeStamp = parsed.LandXML.Application.attr.timeStamp || "";
156
+ let wktString = ((_e = (_d = (_c = parsed.LandXML) == null ? void 0 : _c.CoordinateSystem) == null ? void 0 : _d.attr) == null ? void 0 : _e.ogcWktCode) || void 0;
157
+ const surfaces = parsed.LandXML.Surfaces.Surface.map(
158
+ (surface) => __async(void 0, null, function* () {
159
+ return new Promise((resolve2, reject2) => __async(void 0, null, function* () {
160
+ const { name, desc } = surface.attr;
161
+ const Pnts = surface.Definition.Pnts.P;
162
+ const Faces = surface.Definition.Faces.F;
163
+ let ptsIdArray = [];
164
+ let faces = [];
165
+ if (Pnts.length > 1e4) {
166
+ const sliceIndexes = [...Array(20).keys()].map((i) => Math.round(Pnts.length / 20 * i));
167
+ ptsIdArray = (yield Promise.all(
168
+ sliceIndexes.map(
169
+ (v, i, a) => new Promise((resolve3, reject3) => __async(void 0, null, function* () {
170
+ const pts = yield surfaceDefWorker.send({
171
+ isPoint: true,
172
+ arr: Pnts.slice(a[i], a[i + 1] || Pnts.length)
173
+ });
174
+ resolve3(pts);
175
+ }))
176
+ )
177
+ )).reduce((prev, curr) => [...prev, ...curr], []);
178
+ } else {
179
+ ptsIdArray = yield surfaceDefWorker.send({ arr: Pnts, isPoint: true });
180
+ }
181
+ const points = ptsIdArray.map((v) => v[1]);
182
+ const pointsIdMap = ptsIdArray.map((v) => v[0]);
183
+ if (Faces.length > 1e4) {
184
+ const sliceIndexes = [...Array(20).keys()].map((i) => Math.round(Faces.length / 20 * i));
185
+ faces = (yield Promise.all(
186
+ sliceIndexes.map(
187
+ (v, i, a) => new Promise((resolve3, reject3) => __async(void 0, null, function* () {
188
+ const fcs = yield surfaceDefWorker.send({
189
+ isPoint: false,
190
+ arr: Faces.slice(a[i], a[i + 1] || Faces.length),
191
+ idMap: pointsIdMap
192
+ });
193
+ resolve3(fcs);
194
+ }))
195
+ )
196
+ )).reduce((prev, curr) => [...prev, ...curr], []);
197
+ } else {
198
+ faces = yield surfaceDefWorker.send({ arr: Faces, isPoint: false, idMap: pointsIdMap });
199
+ }
200
+ resolve2({
201
+ sourceFile,
202
+ timeStamp: timeStamp || "",
203
+ name,
204
+ description: desc || "",
205
+ wktString,
206
+ surfaceDefinition: {
207
+ points,
208
+ faces
209
+ }
210
+ });
211
+ }));
212
+ })
213
+ );
214
+ resolve(yield Promise.all(surfaces));
215
+ }));
154
216
  });
155
217
  var parse_xml_default = parseXML;
156
218
 
@@ -182,32 +244,104 @@ var toGlb = (landXmlString, center = "auto", surfaceId = -1) => __async(void 0,
182
244
  var to_glb_default = toGlb;
183
245
 
184
246
  // src/private/get-contours.ts
185
- var contourLineOnFace = (face, z) => {
186
- let vertsAtElevation = 0;
187
- let line = [];
188
- for (let i = 0; i < face.length; i++) {
189
- let vertex1 = face[i];
190
- let vertex2 = face[(i + 1) % face.length];
191
- if (vertex1[2] === z)
192
- vertsAtElevation++;
193
- if ((vertex1[2] <= z && vertex2[2] >= z || vertex1[2] >= z && vertex2[2] <= z) && !Number.isNaN((z - vertex1[2]) / (vertex2[2] - vertex1[2]))) {
194
- let t = (z - vertex1[2]) / (vertex2[2] - vertex1[2]);
195
- line.push([vertex1[0] + t * (vertex2[0] - vertex1[0]), vertex1[1] + t * (vertex2[1] - vertex1[1])]);
196
- }
197
- }
198
- if (vertsAtElevation >= 2 && face.map((f) => f[2]).reduce((a, b) => a + b) > z * face.length)
199
- return void 0;
200
- if (line.length === 2 && line[0][0] === line[1][0] && line[0][1] === line[1][1])
201
- return void 0;
202
- if (line.length > 2) {
203
- line = [...new Set(line.map((v) => JSON.stringify(v)))].map((s) => JSON.parse(s));
204
- }
205
- return line.length > 0 ? line : void 0;
206
- };
247
+ import { createEasyWebWorker as createEasyWebWorker2 } from "easy-web-worker";
248
+ var contoursWorker = createEasyWebWorker2(
249
+ ({ onMessage }) => {
250
+ const contourLineOnFace = (face, z) => {
251
+ let vertsAtElevation = 0;
252
+ let line = [];
253
+ for (let i = 0; i < face.length; i++) {
254
+ let vertex1 = face[i];
255
+ let vertex2 = face[(i + 1) % face.length];
256
+ if (vertex1[2] === z)
257
+ vertsAtElevation++;
258
+ if ((vertex1[2] <= z && vertex2[2] >= z || vertex1[2] >= z && vertex2[2] <= z) && !Number.isNaN((z - vertex1[2]) / (vertex2[2] - vertex1[2]))) {
259
+ let t = (z - vertex1[2]) / (vertex2[2] - vertex1[2]);
260
+ line.push([vertex1[0] + t * (vertex2[0] - vertex1[0]), vertex1[1] + t * (vertex2[1] - vertex1[1])]);
261
+ }
262
+ }
263
+ if (vertsAtElevation >= 2 && face.map((f) => f[2]).reduce((a, b) => a + b) > z * face.length)
264
+ return void 0;
265
+ if (line.length === 2 && line[0][0] === line[1][0] && line[0][1] === line[1][1])
266
+ return void 0;
267
+ if (line.length > 2) {
268
+ line = [...new Set(line.map((v) => JSON.stringify(v)))].map((s) => JSON.parse(s));
269
+ }
270
+ return line.length > 0 ? line : void 0;
271
+ };
272
+ const linesToPolyLines3 = (lineSegments) => {
273
+ var _a, _b;
274
+ if (!Array.isArray(lineSegments) || (lineSegments == null ? void 0 : lineSegments.length) === 0) {
275
+ return [];
276
+ }
277
+ const segmentsMapIndexes = {};
278
+ const polylines = [];
279
+ const parsedSegmentIndexes = [];
280
+ const lineSegmentStrings = lineSegments.map((v) => v.map((c) => c.join(",")));
281
+ lineSegmentStrings.forEach(([start, end], i) => {
282
+ segmentsMapIndexes[start] = segmentsMapIndexes[start] ? [...segmentsMapIndexes[start] || [], i] : [i];
283
+ segmentsMapIndexes[end] = segmentsMapIndexes[end] ? [...segmentsMapIndexes[end] || [], i] : [i];
284
+ });
285
+ for (let i = 0; i < lineSegmentStrings.length; i++) {
286
+ if (parsedSegmentIndexes.includes(i))
287
+ continue;
288
+ parsedSegmentIndexes.push(i);
289
+ let [start, end] = lineSegmentStrings[i];
290
+ let polyline = [start, end];
291
+ while (start && segmentsMapIndexes[start]) {
292
+ const nextLineIndex = (_a = segmentsMapIndexes[start]) == null ? void 0 : _a.find(
293
+ (lineIndex) => !parsedSegmentIndexes.includes(lineIndex)
294
+ );
295
+ if (nextLineIndex) {
296
+ parsedSegmentIndexes.push(nextLineIndex);
297
+ const nextLineSegment = lineSegmentStrings[nextLineIndex];
298
+ const nextLineSegmentPointIndex = nextLineSegment[0] === start ? 1 : 0;
299
+ const newPoint = nextLineSegment[nextLineSegmentPointIndex];
300
+ polyline.unshift(newPoint);
301
+ start = newPoint;
302
+ } else {
303
+ start = null;
304
+ }
305
+ }
306
+ while (end && segmentsMapIndexes[end]) {
307
+ const nextLineIndex = (_b = segmentsMapIndexes[end]) == null ? void 0 : _b.find(
308
+ (lineIndex) => !parsedSegmentIndexes.includes(lineIndex)
309
+ );
310
+ if (nextLineIndex) {
311
+ parsedSegmentIndexes.push(nextLineIndex);
312
+ const nextLineSegment = lineSegmentStrings[nextLineIndex];
313
+ const nextLineSegmentPointIndex = nextLineSegment[0] === end ? 1 : 0;
314
+ const newPoint = nextLineSegment[nextLineSegmentPointIndex];
315
+ polyline.push(newPoint);
316
+ end = newPoint;
317
+ } else {
318
+ end = null;
319
+ }
320
+ }
321
+ polylines.push(polyline.map((coord) => coord.split(",").map((v) => parseFloat(v))));
322
+ }
323
+ return polylines;
324
+ };
325
+ onMessage((message) => {
326
+ const { triangles, elevation } = message.payload;
327
+ const linesAtElevationE = triangles.reduce((prev, curr) => {
328
+ const line = contourLineOnFace(curr, elevation);
329
+ if (line)
330
+ prev.push(line);
331
+ return prev;
332
+ }, []);
333
+ message.resolve({
334
+ elevation,
335
+ polylines: linesToPolyLines3(linesAtElevationE)
336
+ });
337
+ });
338
+ },
339
+ { maxWorkers: 10 }
340
+ );
207
341
  var linesToPolyLines = (lineSegments) => {
208
342
  var _a, _b;
209
- if (!Array.isArray(lineSegments) || lineSegments.length === 0) {
210
- throw new Error("Invalid input: Please provide a non-empty array of line segments.");
343
+ if (!Array.isArray(lineSegments) || (lineSegments == null ? void 0 : lineSegments.length) === 0) {
344
+ return [];
211
345
  }
212
346
  const segmentsMapIndexes = {};
213
347
  const polylines = [];
@@ -302,23 +436,7 @@ var getContours = (data, interval = 2) => __async(void 0, null, function* () {
302
436
  [Infinity, -Infinity]
303
437
  );
304
438
  const elevations = contourElevations(minElevation, maxElevation, interval);
305
- const elevationPolylines = elevations.map((e) => {
306
- const linesAtElevationE = triangles.reduce((prev, curr) => {
307
- const line = contourLineOnFace(curr, e);
308
- if (line)
309
- prev.push(line);
310
- return prev;
311
- }, []);
312
- const polylinesAtElevationE = linesToPolyLines(linesAtElevationE);
313
- if (e === 442) {
314
- console.log("linesAtElevationE", JSON.stringify(linesAtElevationE));
315
- console.log("polylinesAtElevationE", JSON.stringify(polylinesAtElevationE));
316
- }
317
- return {
318
- elevation: e,
319
- polylines: polylinesAtElevationE
320
- };
321
- });
439
+ const elevationPolylines = yield Promise.all(elevations.map((elevation) => contoursWorker.send({ triangles, elevation })));
322
440
  return constructGeojson(elevationPolylines);
323
441
  });
324
442
  var get_contours_default = getContours;
@@ -373,7 +491,6 @@ var toGeojsonContours = (landXmlString, contourInterval = 2, generateOutline = t
373
491
  const geojson = yield get_contours_default(surface, contourInterval);
374
492
  if (generateOutline) {
375
493
  const outlineGeojson = get_outline_default(surface);
376
- console.log(outlineGeojson.features);
377
494
  geojson.features = [...geojson.features, ...outlineGeojson.features];
378
495
  }
379
496
  const _a = surface, { surfaceDefinition } = _a, rest = __objRest(_a, ["surfaceDefinition"]);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "landxml",
3
- "version": "0.5.2",
3
+ "version": "0.6.1",
4
4
  "description": "Parse LandXML surfaces on the modern web.",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -12,8 +12,8 @@
12
12
  "url": "https://github.com/abrman/landxml"
13
13
  },
14
14
  "scripts": {
15
- "dev": "vitest",
16
- "test": "vitest run",
15
+ "dev": "vitest --config ./vitest.config.ts --slow-test-threshold=0",
16
+ "test": "vitest --config ./vitest.config.ts run",
17
17
  "build": "tsup src/index.ts --format cjs,esm --dts",
18
18
  "lint": "tsc",
19
19
  "ci": "npm run lint && npm run test && npm run build",
@@ -30,14 +30,20 @@
30
30
  "@changesets/cli": "^2.26.2",
31
31
  "@types/geojson": "^7946.0.13",
32
32
  "@types/proj4": "^2.5.5",
33
+ "@types/sax": "^1.2.7",
33
34
  "@types/xml2json": "^0.11.6",
35
+ "jsdom": "^24.0.0",
34
36
  "tsup": "^8.0.0",
35
37
  "typescript": "^5.2.2",
36
- "vitest": "^0.34.6"
38
+ "vitest": "^1.6.0"
37
39
  },
38
40
  "dependencies": {
39
41
  "@gltf-transform/core": "^3.9.0",
42
+ "@vitest/web-worker": "^1.6.0",
43
+ "easy-web-worker": "^6.2.0",
40
44
  "proj4": "^2.9.2",
41
- "xml-js": "^1.6.11"
45
+ "sax": "^1.3.0",
46
+ "xml-js": "^1.6.11",
47
+ "xml2json": "^0.12.0"
42
48
  }
43
49
  }