landxml 0.4.1 → 0.5.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,22 @@
1
1
  # landxml
2
2
 
3
+ ## 0.5.0
4
+
5
+ ### Minor Changes
6
+
7
+ - df95e64: Added ability to generate surface outline geojson
8
+
9
+ ### Patch Changes
10
+
11
+ - df95e64: LandXML Face with with "i" attribute equal to "1" will now be correctly ignored
12
+ - df95e64: Improved contour generation algorithm
13
+
14
+ ## 0.4.2
15
+
16
+ ### Patch Changes
17
+
18
+ - fbd9682: Contour elevations from surface min/max elevations and increment are now calculated correctly (bugfix).
19
+
3
20
  ## 0.4.1
4
21
 
5
22
  ### Patch Changes
package/dist/index.d.mts CHANGED
@@ -39,7 +39,7 @@ declare const toGlb: (landXmlString: string, center?: "auto" | "origin" | [
39
39
  * @returns {string} surfaceContours[].wktString - WKT string of LandXML coordinate system projection
40
40
  * @returns {Object} surfaceContours[].geojson - Geojson feature collection of contour lines
41
41
  */
42
- declare const toGeojsonContours: (landXmlString: string, contourInterval?: number, surfaceId?: string | number) => Promise<{
42
+ declare const toGeojsonContours: (landXmlString: string, contourInterval?: number, generateOutline?: boolean, surfaceId?: string | number) => Promise<{
43
43
  name: string;
44
44
  description: string;
45
45
  sourceFile: string;
package/dist/index.d.ts CHANGED
@@ -39,7 +39,7 @@ declare const toGlb: (landXmlString: string, center?: "auto" | "origin" | [
39
39
  * @returns {string} surfaceContours[].wktString - WKT string of LandXML coordinate system projection
40
40
  * @returns {Object} surfaceContours[].geojson - Geojson feature collection of contour lines
41
41
  */
42
- declare const toGeojsonContours: (landXmlString: string, contourInterval?: number, surfaceId?: string | number) => Promise<{
42
+ declare const toGeojsonContours: (landXmlString: string, contourInterval?: number, generateOutline?: boolean, surfaceId?: string | number) => Promise<{
43
43
  name: string;
44
44
  description: string;
45
45
  sourceFile: string;
package/dist/index.js CHANGED
@@ -164,14 +164,16 @@ var parseXML = (xmlString) => __async(void 0, null, function* () {
164
164
  points.push([x, y, z]);
165
165
  pointIdMap[attr.id] = points.length - 1;
166
166
  }, []);
167
- faces = surface.Definition.Faces.F.map((face) => {
168
- const { content } = face;
167
+ faces = surface.Definition.Faces.F.reduce((faceList, face) => {
168
+ const { attr, content } = face;
169
+ if (attr.i === "1")
170
+ return faceList;
169
171
  const [a, b, c] = content.split(" ").map((v) => pointIdMap[v]);
170
172
  if ([a, b, c].filter((v) => typeof v === "undefined").length > 0) {
171
173
  throw `Invalid LandXML. A face is referencing a point that doesn't exist. Face is referencing points: ${content}`;
172
174
  }
173
- return [a, b, c];
174
- });
175
+ return faceList.concat([[a, b, c]]);
176
+ }, []);
175
177
  return {
176
178
  sourceFile: LandXML.Project.attr.name,
177
179
  timeStamp: LandXML.Application.attr.timeStamp,
@@ -220,11 +222,14 @@ var contourLineOnFace = (face, z) => {
220
222
  for (let i = 0; i < face.length; i++) {
221
223
  let vertex1 = face[i];
222
224
  let vertex2 = face[(i + 1) % face.length];
223
- if (vertex1[2] <= z && vertex2[2] >= z || vertex1[2] >= z && vertex2[2] <= z) {
225
+ if ((vertex1[2] <= z && vertex2[2] >= z || vertex1[2] >= z && vertex2[2] <= z) && !Number.isNaN((z - vertex1[2]) / (vertex2[2] - vertex1[2]))) {
224
226
  let t = (z - vertex1[2]) / (vertex2[2] - vertex1[2]);
225
227
  line.push([vertex1[0] + t * (vertex2[0] - vertex1[0]), vertex1[1] + t * (vertex2[1] - vertex1[1])]);
226
228
  }
227
229
  }
230
+ if (line.length > 2) {
231
+ line = [...new Set(line.map((v) => JSON.stringify(v)))].map((s) => JSON.parse(s));
232
+ }
228
233
  return line.length > 0 ? line : void 0;
229
234
  };
230
235
  var linesToPolyLines = (lineSegments) => {
@@ -281,11 +286,14 @@ var linesToPolyLines = (lineSegments) => {
281
286
  return polylines;
282
287
  };
283
288
  var contourElevations = (minElevation, maxElevation, interval) => {
284
- if (maxElevation >= Infinity) {
289
+ if (!Number.isFinite(minElevation) || !Number.isFinite(maxElevation) || !Number.isFinite(interval)) {
285
290
  throw new Error("Contour elevations have to be finite numbers");
286
291
  }
292
+ if (minElevation + interval > maxElevation) {
293
+ throw new Error(`No contour lines at interval: ${interval} between elevation ${minElevation} and ${maxElevation}`);
294
+ }
287
295
  const elevations = [];
288
- let elevation = Math.ceil(minElevation * interval) / interval;
296
+ let elevation = Math.ceil(minElevation / interval) * interval;
289
297
  while (elevation < maxElevation) {
290
298
  elevations.push(elevation);
291
299
  elevation += interval;
@@ -322,29 +330,76 @@ var getContours = (data, interval = 2) => __async(void 0, null, function* () {
322
330
  [Infinity, -Infinity]
323
331
  );
324
332
  const elevations = contourElevations(minElevation, maxElevation, interval);
325
- const elevationPolylines = elevations.map((e) => ({
326
- elevation: e,
327
- polylines: linesToPolyLines(
328
- triangles.reduce((prev, curr) => {
329
- const line = contourLineOnFace(curr, e);
330
- if (line)
331
- prev.push(line);
332
- return prev;
333
- }, [])
334
- )
335
- }));
333
+ const elevationPolylines = elevations.map((e) => {
334
+ const linesAtElevationE = triangles.reduce((prev, curr) => {
335
+ const line = contourLineOnFace(curr, e);
336
+ if (line)
337
+ prev.push(line);
338
+ return prev;
339
+ }, []);
340
+ const polylinesAtElevationE = linesToPolyLines(linesAtElevationE);
341
+ return {
342
+ elevation: e,
343
+ polylines: polylinesAtElevationE
344
+ };
345
+ });
336
346
  return constructGeojson(elevationPolylines);
337
347
  });
338
348
  var get_contours_default = getContours;
339
349
 
350
+ // src/private/get-outline.ts
351
+ var getOutline = (surface) => {
352
+ const triangleVertexIdEdgePairs = [];
353
+ let i = -1;
354
+ let pairs = [];
355
+ surface.surfaceDefinition.faces.forEach((f) => {
356
+ pairs = [];
357
+ [
358
+ [f[0], f[1]],
359
+ [f[1], f[2]],
360
+ [f[2], f[0]]
361
+ ].forEach(([a, b]) => {
362
+ if (a < b) {
363
+ pairs.push(`${a};${b}`);
364
+ } else {
365
+ pairs.push(`${b};${a}`);
366
+ }
367
+ });
368
+ pairs.forEach((pair) => {
369
+ i = triangleVertexIdEdgePairs.indexOf(pair);
370
+ if (i >= 0) {
371
+ triangleVertexIdEdgePairs.splice(i, 1);
372
+ } else {
373
+ triangleVertexIdEdgePairs.push(pair);
374
+ }
375
+ });
376
+ });
377
+ const edges = [];
378
+ triangleVertexIdEdgePairs.map((pair) => {
379
+ const [v1, v2] = pair.split(";").map((v) => {
380
+ var _a;
381
+ return (_a = surface.surfaceDefinition.points[parseInt(v, 10)]) == null ? void 0 : _a.slice(0, 2);
382
+ });
383
+ if (v1 && v2)
384
+ edges.push([v1, v2]);
385
+ });
386
+ return constructGeojson([{ elevation: 0, polylines: linesToPolyLines(edges) }]);
387
+ };
388
+ var get_outline_default = getOutline;
389
+
340
390
  // src/public/to-geojson-contours.ts
341
- var toGeojsonContours = (landXmlString, contourInterval = 2, surfaceId = -1) => __async(void 0, null, function* () {
391
+ var toGeojsonContours = (landXmlString, contourInterval = 2, generateOutline = true, surfaceId = -1) => __async(void 0, null, function* () {
342
392
  let requestedParsedSurfaces = filter_by_surfaceId_default(yield parse_xml_default(landXmlString), surfaceId);
343
393
  const contours = yield Promise.all(
344
394
  requestedParsedSurfaces.map(
345
395
  (surface) => new Promise((resolve, reject) => __async(void 0, null, function* () {
346
396
  try {
347
397
  const geojson = yield get_contours_default(surface, contourInterval);
398
+ if (generateOutline) {
399
+ const outlineGeojson = get_outline_default(surface);
400
+ console.log(outlineGeojson.features);
401
+ geojson.features = [...geojson.features, ...outlineGeojson.features];
402
+ }
348
403
  const _a = surface, { surfaceDefinition } = _a, rest = __objRest(_a, ["surfaceDefinition"]);
349
404
  resolve(__spreadProps(__spreadValues({}, rest), {
350
405
  geojson
package/dist/index.mjs CHANGED
@@ -129,14 +129,16 @@ var parseXML = (xmlString) => __async(void 0, null, function* () {
129
129
  points.push([x, y, z]);
130
130
  pointIdMap[attr.id] = points.length - 1;
131
131
  }, []);
132
- faces = surface.Definition.Faces.F.map((face) => {
133
- const { content } = face;
132
+ faces = surface.Definition.Faces.F.reduce((faceList, face) => {
133
+ const { attr, content } = face;
134
+ if (attr.i === "1")
135
+ return faceList;
134
136
  const [a, b, c] = content.split(" ").map((v) => pointIdMap[v]);
135
137
  if ([a, b, c].filter((v) => typeof v === "undefined").length > 0) {
136
138
  throw `Invalid LandXML. A face is referencing a point that doesn't exist. Face is referencing points: ${content}`;
137
139
  }
138
- return [a, b, c];
139
- });
140
+ return faceList.concat([[a, b, c]]);
141
+ }, []);
140
142
  return {
141
143
  sourceFile: LandXML.Project.attr.name,
142
144
  timeStamp: LandXML.Application.attr.timeStamp,
@@ -185,11 +187,14 @@ var contourLineOnFace = (face, z) => {
185
187
  for (let i = 0; i < face.length; i++) {
186
188
  let vertex1 = face[i];
187
189
  let vertex2 = face[(i + 1) % face.length];
188
- if (vertex1[2] <= z && vertex2[2] >= z || vertex1[2] >= z && vertex2[2] <= z) {
190
+ if ((vertex1[2] <= z && vertex2[2] >= z || vertex1[2] >= z && vertex2[2] <= z) && !Number.isNaN((z - vertex1[2]) / (vertex2[2] - vertex1[2]))) {
189
191
  let t = (z - vertex1[2]) / (vertex2[2] - vertex1[2]);
190
192
  line.push([vertex1[0] + t * (vertex2[0] - vertex1[0]), vertex1[1] + t * (vertex2[1] - vertex1[1])]);
191
193
  }
192
194
  }
195
+ if (line.length > 2) {
196
+ line = [...new Set(line.map((v) => JSON.stringify(v)))].map((s) => JSON.parse(s));
197
+ }
193
198
  return line.length > 0 ? line : void 0;
194
199
  };
195
200
  var linesToPolyLines = (lineSegments) => {
@@ -246,11 +251,14 @@ var linesToPolyLines = (lineSegments) => {
246
251
  return polylines;
247
252
  };
248
253
  var contourElevations = (minElevation, maxElevation, interval) => {
249
- if (maxElevation >= Infinity) {
254
+ if (!Number.isFinite(minElevation) || !Number.isFinite(maxElevation) || !Number.isFinite(interval)) {
250
255
  throw new Error("Contour elevations have to be finite numbers");
251
256
  }
257
+ if (minElevation + interval > maxElevation) {
258
+ throw new Error(`No contour lines at interval: ${interval} between elevation ${minElevation} and ${maxElevation}`);
259
+ }
252
260
  const elevations = [];
253
- let elevation = Math.ceil(minElevation * interval) / interval;
261
+ let elevation = Math.ceil(minElevation / interval) * interval;
254
262
  while (elevation < maxElevation) {
255
263
  elevations.push(elevation);
256
264
  elevation += interval;
@@ -287,29 +295,76 @@ var getContours = (data, interval = 2) => __async(void 0, null, function* () {
287
295
  [Infinity, -Infinity]
288
296
  );
289
297
  const elevations = contourElevations(minElevation, maxElevation, interval);
290
- const elevationPolylines = elevations.map((e) => ({
291
- elevation: e,
292
- polylines: linesToPolyLines(
293
- triangles.reduce((prev, curr) => {
294
- const line = contourLineOnFace(curr, e);
295
- if (line)
296
- prev.push(line);
297
- return prev;
298
- }, [])
299
- )
300
- }));
298
+ const elevationPolylines = elevations.map((e) => {
299
+ const linesAtElevationE = triangles.reduce((prev, curr) => {
300
+ const line = contourLineOnFace(curr, e);
301
+ if (line)
302
+ prev.push(line);
303
+ return prev;
304
+ }, []);
305
+ const polylinesAtElevationE = linesToPolyLines(linesAtElevationE);
306
+ return {
307
+ elevation: e,
308
+ polylines: polylinesAtElevationE
309
+ };
310
+ });
301
311
  return constructGeojson(elevationPolylines);
302
312
  });
303
313
  var get_contours_default = getContours;
304
314
 
315
+ // src/private/get-outline.ts
316
+ var getOutline = (surface) => {
317
+ const triangleVertexIdEdgePairs = [];
318
+ let i = -1;
319
+ let pairs = [];
320
+ surface.surfaceDefinition.faces.forEach((f) => {
321
+ pairs = [];
322
+ [
323
+ [f[0], f[1]],
324
+ [f[1], f[2]],
325
+ [f[2], f[0]]
326
+ ].forEach(([a, b]) => {
327
+ if (a < b) {
328
+ pairs.push(`${a};${b}`);
329
+ } else {
330
+ pairs.push(`${b};${a}`);
331
+ }
332
+ });
333
+ pairs.forEach((pair) => {
334
+ i = triangleVertexIdEdgePairs.indexOf(pair);
335
+ if (i >= 0) {
336
+ triangleVertexIdEdgePairs.splice(i, 1);
337
+ } else {
338
+ triangleVertexIdEdgePairs.push(pair);
339
+ }
340
+ });
341
+ });
342
+ const edges = [];
343
+ triangleVertexIdEdgePairs.map((pair) => {
344
+ const [v1, v2] = pair.split(";").map((v) => {
345
+ var _a;
346
+ return (_a = surface.surfaceDefinition.points[parseInt(v, 10)]) == null ? void 0 : _a.slice(0, 2);
347
+ });
348
+ if (v1 && v2)
349
+ edges.push([v1, v2]);
350
+ });
351
+ return constructGeojson([{ elevation: 0, polylines: linesToPolyLines(edges) }]);
352
+ };
353
+ var get_outline_default = getOutline;
354
+
305
355
  // src/public/to-geojson-contours.ts
306
- var toGeojsonContours = (landXmlString, contourInterval = 2, surfaceId = -1) => __async(void 0, null, function* () {
356
+ var toGeojsonContours = (landXmlString, contourInterval = 2, generateOutline = true, surfaceId = -1) => __async(void 0, null, function* () {
307
357
  let requestedParsedSurfaces = filter_by_surfaceId_default(yield parse_xml_default(landXmlString), surfaceId);
308
358
  const contours = yield Promise.all(
309
359
  requestedParsedSurfaces.map(
310
360
  (surface) => new Promise((resolve, reject) => __async(void 0, null, function* () {
311
361
  try {
312
362
  const geojson = yield get_contours_default(surface, contourInterval);
363
+ if (generateOutline) {
364
+ const outlineGeojson = get_outline_default(surface);
365
+ console.log(outlineGeojson.features);
366
+ geojson.features = [...geojson.features, ...outlineGeojson.features];
367
+ }
313
368
  const _a = surface, { surfaceDefinition } = _a, rest = __objRest(_a, ["surfaceDefinition"]);
314
369
  resolve(__spreadProps(__spreadValues({}, rest), {
315
370
  geojson
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "landxml",
3
- "version": "0.4.1",
3
+ "version": "0.5.0",
4
4
  "description": "Parse LandXML surfaces on the modern web.",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",