landxml 0.5.1 → 0.6.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/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # landxml
2
2
 
3
+ ## 0.6.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 330e3e8: Introduced web workers for some of the processing heavy workflows
8
+
9
+ ## 0.5.2
10
+
11
+ ### Patch Changes
12
+
13
+ - 6cafe85: Bugfix parsing landxml's without extra triangulated faces
14
+
3
15
  ## 0.5.1
4
16
 
5
17
  ### Patch Changes
@@ -1,7 +1,12 @@
1
+ "use strict";
2
+ var __create = Object.create;
1
3
  var __defProp = Object.defineProperty;
2
4
  var __defProps = Object.defineProperties;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
6
  var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
7
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
8
  var __getOwnPropSymbols = Object.getOwnPropertySymbols;
9
+ var __getProtoOf = Object.getPrototypeOf;
5
10
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
11
  var __propIsEnum = Object.prototype.propertyIsEnumerable;
7
12
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
@@ -29,6 +34,27 @@ var __objRest = (source, exclude) => {
29
34
  }
30
35
  return target;
31
36
  };
37
+ var __export = (target, all) => {
38
+ for (var name in all)
39
+ __defProp(target, name, { get: all[name], enumerable: true });
40
+ };
41
+ var __copyProps = (to, from, except, desc) => {
42
+ if (from && typeof from === "object" || typeof from === "function") {
43
+ for (let key of __getOwnPropNames(from))
44
+ if (!__hasOwnProp.call(to, key) && key !== except)
45
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
46
+ }
47
+ return to;
48
+ };
49
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
50
+ // If the importer is in node compatibility mode or this is not an ESM
51
+ // file that has been converted to a CommonJS file using a Babel-
52
+ // compatible transform (i.e. "__esModule" has not been set), then set
53
+ // "default" to the CommonJS "module.exports" for node compatibility.
54
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
55
+ mod
56
+ ));
57
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
32
58
  var __async = (__this, __arguments, generator) => {
33
59
  return new Promise((resolve, reject) => {
34
60
  var fulfilled = (value) => {
@@ -50,6 +76,15 @@ var __async = (__this, __arguments, generator) => {
50
76
  });
51
77
  };
52
78
 
79
+ // src/index.ts
80
+ var src_exports = {};
81
+ __export(src_exports, {
82
+ reprojectGeoJson: () => reproject_geojson_default,
83
+ toGeojsonContours: () => to_geojson_contours_default,
84
+ toGlb: () => to_glb_default
85
+ });
86
+ module.exports = __toCommonJS(src_exports);
87
+
53
88
  // src/private/filter-by-surfaceId.ts
54
89
  var filterBySurfaceId = (parsedSurfaces, surfaceId) => {
55
90
  let filtered = [...parsedSurfaces];
@@ -68,7 +103,7 @@ var filterBySurfaceId = (parsedSurfaces, surfaceId) => {
68
103
  var filter_by_surfaceId_default = filterBySurfaceId;
69
104
 
70
105
  // src/private/get-glb.ts
71
- import { Document, WebIO } from "@gltf-transform/core";
106
+ var import_core = require("@gltf-transform/core");
72
107
  var findXYAxisMedians = (vertices) => {
73
108
  var _a, _b;
74
109
  vertices = vertices.slice().filter(Boolean);
@@ -83,7 +118,7 @@ var getGlb = (data, customCenter) => __async(void 0, null, function* () {
83
118
  return [x - center[0], z, -(y - center[1])];
84
119
  }).reduce((prev, curr) => prev.concat(curr), []);
85
120
  const triangles = data.surfaceDefinition.faces.reduce((prev, curr) => prev.concat(curr), []);
86
- const doc = new Document();
121
+ const doc = new import_core.Document();
87
122
  const buffer = doc.createBuffer();
88
123
  const position = doc.createAccessor().setType("VEC3").setArray(new Float32Array(vertices)).setBuffer(buffer);
89
124
  const indices = doc.createAccessor().setType("SCALAR").setArray(new Uint32Array(triangles)).setBuffer(buffer);
@@ -91,7 +126,7 @@ var getGlb = (data, customCenter) => __async(void 0, null, function* () {
91
126
  const mesh = doc.createMesh().addPrimitive(prim);
92
127
  const node = doc.createNode().setMesh(mesh);
93
128
  const scene = doc.createScene().addChild(node);
94
- const glb = yield new WebIO().writeBinary(doc);
129
+ const glb = yield new import_core.WebIO().writeBinary(doc);
95
130
  return { glb, center };
96
131
  });
97
132
  var get_glb_default = getGlb;
@@ -110,47 +145,109 @@ var downloadGlb = (glbData, fileName) => {
110
145
  var download_glb_default = downloadGlb;
111
146
 
112
147
  // src/private/parse-xml.ts
113
- 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.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
148
+ var import_easy_web_worker = require("easy-web-worker");
149
+ var import_xml_js = __toESM(require("xml-js"), 1);
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
+ );
151
169
  }
152
- };
153
- });
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
+ }));
154
251
  });
155
252
  var parse_xml_default = parseXML;
156
253
 
@@ -182,32 +279,104 @@ var toGlb = (landXmlString, center = "auto", surfaceId = -1) => __async(void 0,
182
279
  var to_glb_default = toGlb;
183
280
 
184
281
  // 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
- };
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
+ );
207
376
  var linesToPolyLines = (lineSegments) => {
208
377
  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.");
378
+ if (!Array.isArray(lineSegments) || (lineSegments == null ? void 0 : lineSegments.length) === 0) {
379
+ return [];
211
380
  }
212
381
  const segmentsMapIndexes = {};
213
382
  const polylines = [];
@@ -302,23 +471,7 @@ var getContours = (data, interval = 2) => __async(void 0, null, function* () {
302
471
  [Infinity, -Infinity]
303
472
  );
304
473
  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
- });
474
+ const elevationPolylines = yield Promise.all(elevations.map((elevation) => contoursWorker.send({ triangles, elevation })));
322
475
  return constructGeojson(elevationPolylines);
323
476
  });
324
477
  var get_contours_default = getContours;
@@ -373,7 +526,6 @@ var toGeojsonContours = (landXmlString, contourInterval = 2, generateOutline = t
373
526
  const geojson = yield get_contours_default(surface, contourInterval);
374
527
  if (generateOutline) {
375
528
  const outlineGeojson = get_outline_default(surface);
376
- console.log(outlineGeojson.features);
377
529
  geojson.features = [...geojson.features, ...outlineGeojson.features];
378
530
  }
379
531
  const _a = surface, { surfaceDefinition } = _a, rest = __objRest(_a, ["surfaceDefinition"]);
@@ -391,7 +543,7 @@ var toGeojsonContours = (landXmlString, contourInterval = 2, generateOutline = t
391
543
  var to_geojson_contours_default = toGeojsonContours;
392
544
 
393
545
  // src/public/reproject-geojson.ts
394
- import proj4 from "proj4";
546
+ var import_proj4 = __toESM(require("proj4"), 1);
395
547
  var reprojectGeoJson = (geojson, sourceProjection, targetProjection = "WGS84", keepOriginalGeometryAsFeatureProperty = true) => {
396
548
  const transformCoordinates = (coordinates, sourceProjection2, targetProjection2) => {
397
549
  if (Array.isArray(coordinates[0])) {
@@ -399,7 +551,7 @@ var reprojectGeoJson = (geojson, sourceProjection, targetProjection = "WGS84", k
399
551
  (subCoordinates) => transformCoordinates(subCoordinates, sourceProjection2, targetProjection2)
400
552
  );
401
553
  } else {
402
- coordinates = proj4(sourceProjection2, targetProjection2, coordinates);
554
+ coordinates = (0, import_proj4.default)(sourceProjection2, targetProjection2, coordinates);
403
555
  }
404
556
  return coordinates;
405
557
  };
@@ -424,8 +576,9 @@ var reprojectGeoJson = (geojson, sourceProjection, targetProjection = "WGS84", k
424
576
  return geojson;
425
577
  };
426
578
  var reproject_geojson_default = reprojectGeoJson;
427
- export {
428
- reproject_geojson_default as reprojectGeoJson,
429
- to_geojson_contours_default as toGeojsonContours,
430
- to_glb_default as toGlb
431
- };
579
+ // Annotate the CommonJS export names for ESM import in node:
580
+ 0 && (module.exports = {
581
+ reprojectGeoJson,
582
+ toGeojsonContours,
583
+ toGlb
584
+ });
package/dist/index.js CHANGED
@@ -1,12 +1,7 @@
1
- "use strict";
2
- var __create = Object.create;
3
1
  var __defProp = Object.defineProperty;
4
2
  var __defProps = Object.defineProperties;
5
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
3
  var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
7
- var __getOwnPropNames = Object.getOwnPropertyNames;
8
4
  var __getOwnPropSymbols = Object.getOwnPropertySymbols;
9
- var __getProtoOf = Object.getPrototypeOf;
10
5
  var __hasOwnProp = Object.prototype.hasOwnProperty;
11
6
  var __propIsEnum = Object.prototype.propertyIsEnumerable;
12
7
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
@@ -34,27 +29,6 @@ var __objRest = (source, exclude) => {
34
29
  }
35
30
  return target;
36
31
  };
37
- var __export = (target, all) => {
38
- for (var name in all)
39
- __defProp(target, name, { get: all[name], enumerable: true });
40
- };
41
- var __copyProps = (to, from, except, desc) => {
42
- if (from && typeof from === "object" || typeof from === "function") {
43
- for (let key of __getOwnPropNames(from))
44
- if (!__hasOwnProp.call(to, key) && key !== except)
45
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
46
- }
47
- return to;
48
- };
49
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
50
- // If the importer is in node compatibility mode or this is not an ESM
51
- // file that has been converted to a CommonJS file using a Babel-
52
- // compatible transform (i.e. "__esModule" has not been set), then set
53
- // "default" to the CommonJS "module.exports" for node compatibility.
54
- isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
55
- mod
56
- ));
57
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
58
32
  var __async = (__this, __arguments, generator) => {
59
33
  return new Promise((resolve, reject) => {
60
34
  var fulfilled = (value) => {
@@ -76,15 +50,6 @@ var __async = (__this, __arguments, generator) => {
76
50
  });
77
51
  };
78
52
 
79
- // src/index.ts
80
- var src_exports = {};
81
- __export(src_exports, {
82
- reprojectGeoJson: () => reproject_geojson_default,
83
- toGeojsonContours: () => to_geojson_contours_default,
84
- toGlb: () => to_glb_default
85
- });
86
- module.exports = __toCommonJS(src_exports);
87
-
88
53
  // src/private/filter-by-surfaceId.ts
89
54
  var filterBySurfaceId = (parsedSurfaces, surfaceId) => {
90
55
  let filtered = [...parsedSurfaces];
@@ -103,7 +68,7 @@ var filterBySurfaceId = (parsedSurfaces, surfaceId) => {
103
68
  var filter_by_surfaceId_default = filterBySurfaceId;
104
69
 
105
70
  // src/private/get-glb.ts
106
- var import_core = require("@gltf-transform/core");
71
+ import { Document, WebIO } from "@gltf-transform/core";
107
72
  var findXYAxisMedians = (vertices) => {
108
73
  var _a, _b;
109
74
  vertices = vertices.slice().filter(Boolean);
@@ -118,7 +83,7 @@ var getGlb = (data, customCenter) => __async(void 0, null, function* () {
118
83
  return [x - center[0], z, -(y - center[1])];
119
84
  }).reduce((prev, curr) => prev.concat(curr), []);
120
85
  const triangles = data.surfaceDefinition.faces.reduce((prev, curr) => prev.concat(curr), []);
121
- const doc = new import_core.Document();
86
+ const doc = new Document();
122
87
  const buffer = doc.createBuffer();
123
88
  const position = doc.createAccessor().setType("VEC3").setArray(new Float32Array(vertices)).setBuffer(buffer);
124
89
  const indices = doc.createAccessor().setType("SCALAR").setArray(new Uint32Array(triangles)).setBuffer(buffer);
@@ -126,7 +91,7 @@ var getGlb = (data, customCenter) => __async(void 0, null, function* () {
126
91
  const mesh = doc.createMesh().addPrimitive(prim);
127
92
  const node = doc.createNode().setMesh(mesh);
128
93
  const scene = doc.createScene().addChild(node);
129
- const glb = yield new import_core.WebIO().writeBinary(doc);
94
+ const glb = yield new WebIO().writeBinary(doc);
130
95
  return { glb, center };
131
96
  });
132
97
  var get_glb_default = getGlb;
@@ -145,47 +110,109 @@ var downloadGlb = (glbData, fileName) => {
145
110
  var download_glb_default = downloadGlb;
146
111
 
147
112
  // src/private/parse-xml.ts
148
- 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.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
113
+ import { createEasyWebWorker } from "easy-web-worker";
114
+ import convert from "xml-js";
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
+ );
186
134
  }
187
- };
188
- });
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
+ }));
189
216
  });
190
217
  var parse_xml_default = parseXML;
191
218
 
@@ -217,32 +244,104 @@ var toGlb = (landXmlString, center = "auto", surfaceId = -1) => __async(void 0,
217
244
  var to_glb_default = toGlb;
218
245
 
219
246
  // 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
- };
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
+ );
242
341
  var linesToPolyLines = (lineSegments) => {
243
342
  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.");
343
+ if (!Array.isArray(lineSegments) || (lineSegments == null ? void 0 : lineSegments.length) === 0) {
344
+ return [];
246
345
  }
247
346
  const segmentsMapIndexes = {};
248
347
  const polylines = [];
@@ -337,23 +436,7 @@ var getContours = (data, interval = 2) => __async(void 0, null, function* () {
337
436
  [Infinity, -Infinity]
338
437
  );
339
438
  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
- });
439
+ const elevationPolylines = yield Promise.all(elevations.map((elevation) => contoursWorker.send({ triangles, elevation })));
357
440
  return constructGeojson(elevationPolylines);
358
441
  });
359
442
  var get_contours_default = getContours;
@@ -408,7 +491,6 @@ var toGeojsonContours = (landXmlString, contourInterval = 2, generateOutline = t
408
491
  const geojson = yield get_contours_default(surface, contourInterval);
409
492
  if (generateOutline) {
410
493
  const outlineGeojson = get_outline_default(surface);
411
- console.log(outlineGeojson.features);
412
494
  geojson.features = [...geojson.features, ...outlineGeojson.features];
413
495
  }
414
496
  const _a = surface, { surfaceDefinition } = _a, rest = __objRest(_a, ["surfaceDefinition"]);
@@ -426,7 +508,7 @@ var toGeojsonContours = (landXmlString, contourInterval = 2, generateOutline = t
426
508
  var to_geojson_contours_default = toGeojsonContours;
427
509
 
428
510
  // src/public/reproject-geojson.ts
429
- var import_proj4 = __toESM(require("proj4"));
511
+ import proj4 from "proj4";
430
512
  var reprojectGeoJson = (geojson, sourceProjection, targetProjection = "WGS84", keepOriginalGeometryAsFeatureProperty = true) => {
431
513
  const transformCoordinates = (coordinates, sourceProjection2, targetProjection2) => {
432
514
  if (Array.isArray(coordinates[0])) {
@@ -434,7 +516,7 @@ var reprojectGeoJson = (geojson, sourceProjection, targetProjection = "WGS84", k
434
516
  (subCoordinates) => transformCoordinates(subCoordinates, sourceProjection2, targetProjection2)
435
517
  );
436
518
  } else {
437
- coordinates = (0, import_proj4.default)(sourceProjection2, targetProjection2, coordinates);
519
+ coordinates = proj4(sourceProjection2, targetProjection2, coordinates);
438
520
  }
439
521
  return coordinates;
440
522
  };
@@ -459,9 +541,8 @@ var reprojectGeoJson = (geojson, sourceProjection, targetProjection = "WGS84", k
459
541
  return geojson;
460
542
  };
461
543
  var reproject_geojson_default = reprojectGeoJson;
462
- // Annotate the CommonJS export names for ESM import in node:
463
- 0 && (module.exports = {
464
- reprojectGeoJson,
465
- toGeojsonContours,
466
- toGlb
467
- });
544
+ export {
545
+ reproject_geojson_default as reprojectGeoJson,
546
+ to_geojson_contours_default as toGeojsonContours,
547
+ to_glb_default as toGlb
548
+ };
package/package.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "landxml",
3
- "version": "0.5.1",
3
+ "type": "module",
4
+ "version": "0.6.0",
4
5
  "description": "Parse LandXML surfaces on the modern web.",
5
6
  "main": "dist/index.js",
6
7
  "module": "dist/index.mjs",
@@ -12,8 +13,8 @@
12
13
  "url": "https://github.com/abrman/landxml"
13
14
  },
14
15
  "scripts": {
15
- "dev": "vitest",
16
- "test": "vitest run",
16
+ "dev": "vitest --config ./vitest.config.ts --slow-test-threshold=0",
17
+ "test": "vitest --config ./vitest.config.ts run",
17
18
  "build": "tsup src/index.ts --format cjs,esm --dts",
18
19
  "lint": "tsc",
19
20
  "ci": "npm run lint && npm run test && npm run build",
@@ -30,14 +31,20 @@
30
31
  "@changesets/cli": "^2.26.2",
31
32
  "@types/geojson": "^7946.0.13",
32
33
  "@types/proj4": "^2.5.5",
34
+ "@types/sax": "^1.2.7",
33
35
  "@types/xml2json": "^0.11.6",
36
+ "jsdom": "^24.0.0",
34
37
  "tsup": "^8.0.0",
35
38
  "typescript": "^5.2.2",
36
- "vitest": "^0.34.6"
39
+ "vitest": "^1.6.0"
37
40
  },
38
41
  "dependencies": {
39
42
  "@gltf-transform/core": "^3.9.0",
43
+ "@vitest/web-worker": "^1.6.0",
44
+ "easy-web-worker": "^6.2.0",
40
45
  "proj4": "^2.9.2",
41
- "xml-js": "^1.6.11"
46
+ "sax": "^1.3.0",
47
+ "xml-js": "^1.6.11",
48
+ "xml2json": "^0.12.0"
42
49
  }
43
50
  }
File without changes