landxml 0.0.1 → 0.1.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 +18 -0
- package/README.md +29 -1
- package/dist/index.d.mts +61 -2
- package/dist/index.d.ts +61 -2
- package/dist/index.js +365 -3
- package/dist/index.mjs +351 -4
- package/package.json +9 -2
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# landxml
|
|
2
|
+
|
|
3
|
+
## 0.1.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- e0fa5a8: Added tests
|
|
8
|
+
- e0fa5a8: Added contour lines geojson export
|
|
9
|
+
- e0fa5a8: Added GLB file export
|
|
10
|
+
- e0fa5a8: Exposed functions for extenal use
|
|
11
|
+
- e0fa5a8: Added README.md
|
|
12
|
+
- e0fa5a8: Added LandXML parser
|
|
13
|
+
|
|
14
|
+
### Patch Changes
|
|
15
|
+
|
|
16
|
+
- 4e2c14f: Fixed test imports
|
|
17
|
+
- 3c53b08: Fixed types on toGeojsonContours to reflect that surface may have a wktString
|
|
18
|
+
- 3c53b08: Updated README GeoJSON Contours code example
|
package/README.md
CHANGED
|
@@ -1 +1,29 @@
|
|
|
1
|
-
|
|
1
|
+
# LandXML Parser for Contour Geojson and 3D Models
|
|
2
|
+
|
|
3
|
+
Easily transform LandXML surfaces into stunning GeoJSON contours or immersive GLB models to explore within threeJS or any popular 3D software.
|
|
4
|
+
|
|
5
|
+
### Features
|
|
6
|
+
|
|
7
|
+
- **toGlb:** Generate GLB models from LandXML surfaces.
|
|
8
|
+
- **toGeojsonContours:** Convert LandXML data into detailed contour GeoJSON representations.
|
|
9
|
+
- **reprojectGeojson:** Effortlessly reproject GeoJSON data to desired projection using `proj4`
|
|
10
|
+
|
|
11
|
+
### Example: Retrieve GeoJSON Contours
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import { toGeojsonContours, reprojectGeoJson } from "landxml";
|
|
15
|
+
|
|
16
|
+
const loadGeojson = async () => {
|
|
17
|
+
const landXmlString = "<?xml version="1.0"?>...</LandXML>";
|
|
18
|
+
const contourInterval = 2;
|
|
19
|
+
const geojsonSurfaces = await toGeojsonContours(landXmlString, contourInterval);
|
|
20
|
+
|
|
21
|
+
// Retrieve geojson with raw LandXML coordinates
|
|
22
|
+
let { geojson, wktString } = geojsonSurfaces[0];
|
|
23
|
+
|
|
24
|
+
// Reproject GeoJSON to a desired coordinate system (e.g., WGS84)
|
|
25
|
+
const targetCoordinateSystem = wktString || "WGS84";
|
|
26
|
+
const keepOriginalGeometryAsFeatureProperties = false;
|
|
27
|
+
return reprojectGeoJson(geojson, wktString, targetCoordinateSystem, keepOriginalGeometryAsFeatureProperties);
|
|
28
|
+
}
|
|
29
|
+
```
|
package/dist/index.d.mts
CHANGED
|
@@ -1,3 +1,62 @@
|
|
|
1
|
-
|
|
1
|
+
import * as geojson from 'geojson';
|
|
2
|
+
import { FeatureCollection, LineString } from 'geojson';
|
|
2
3
|
|
|
3
|
-
|
|
4
|
+
/**
|
|
5
|
+
* @param landXmlString
|
|
6
|
+
* @param center 3D models don't work well when they're far from origin (coordinate `[0,0,0]`), therefore by default your center is moved to the median of x and y axis of your surface
|
|
7
|
+
* @param surfaceId Surface name or index if your LandXML contains multiple surfaces. By default all surfaces are converted and returns an array of glb Uint8Array
|
|
8
|
+
* @returns {Object[]} glbs - Array of processed glbs (will also return an array when just one surface has been processed)
|
|
9
|
+
* @returns {string} glbs[].name - Name of surface as defined in LandXML
|
|
10
|
+
* @returns {string} glbs[].description - Description of surface as defined in LandXML
|
|
11
|
+
* @returns {string} glbs[].sourceFile - Source file of where the LandXML was generated from.
|
|
12
|
+
* @returns {string} glbs[].timeStamp - Timestamp of when the surface was exported into LandXML
|
|
13
|
+
* @returns {Uint8Array} glbs[].glb - Uint8Array binary data of glb
|
|
14
|
+
* @returns {[number, number]} glbs[].center - Offset center of the processed glb
|
|
15
|
+
* @returns {function(): void} glbs[].download - Convenient way to download the GLB, within the filename the new center will be appended.
|
|
16
|
+
*/
|
|
17
|
+
declare const toGlb: (landXmlString: string, center?: "auto" | "origin" | [
|
|
18
|
+
x: number,
|
|
19
|
+
y: number
|
|
20
|
+
], surfaceId?: string | number) => Promise<{
|
|
21
|
+
name: string;
|
|
22
|
+
description: string;
|
|
23
|
+
sourceFile: string;
|
|
24
|
+
timeStamp: string;
|
|
25
|
+
glb: Uint8Array;
|
|
26
|
+
center: [x: number, y: number];
|
|
27
|
+
download: () => void;
|
|
28
|
+
}[]>;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @param landXmlString
|
|
32
|
+
* @param contourInterval Interval at which you would like to generate contour lines
|
|
33
|
+
* @param surfaceId Surface name or index if your LandXML contains multiple surfaces. By default all surfaces are converted and returns an array of glb Uint8Array
|
|
34
|
+
* @returns {Object[]} surfaceContours - Array of processed surface contours (will also return an array when just one surface has been processed)
|
|
35
|
+
* @returns {string} surfaceContours[].name - Name of surface as defined in LandXML
|
|
36
|
+
* @returns {string} surfaceContours[].description - Description of surface as defined in LandXML
|
|
37
|
+
* @returns {string} surfaceContours[].sourceFile - Source file of where the LandXML was generated from.
|
|
38
|
+
* @returns {string} surfaceContours[].timeStamp - Timestamp of when the surface was exported into LandXML
|
|
39
|
+
* @returns {string} surfaceContours[].wktString - WKT string of LandXML coordinate system projection
|
|
40
|
+
* @returns {Object} surfaceContours[].geojson - Geojson feature collection of contour lines
|
|
41
|
+
*/
|
|
42
|
+
declare const toGeojsonContours: (landXmlString: string, contourInterval?: number, surfaceId?: string | number) => Promise<{
|
|
43
|
+
name: string;
|
|
44
|
+
description: string;
|
|
45
|
+
sourceFile: string;
|
|
46
|
+
timeStamp: string;
|
|
47
|
+
wktString?: string;
|
|
48
|
+
geojson: FeatureCollection<LineString, {
|
|
49
|
+
z: number;
|
|
50
|
+
}>;
|
|
51
|
+
}[]>;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* @param geojson
|
|
55
|
+
* @param sourceProjection can be a proj4 string or WKT string, you will likely have a wkt string available with your LandXML if you used Civil 3D exporter and had your drawing geo-referenced
|
|
56
|
+
* @param targetProjection you will most likely want to use WGS84 for online viewing, however any other projection you might need can be used as long as it's valid
|
|
57
|
+
* @param keepOriginalGeometryAsFeatureProperty if you intend to repurpose the original geometry, it can be added to geojson feature properties
|
|
58
|
+
* @returns {FeatureCollection} Geojson FeatureCollection with updated geometry coordinates
|
|
59
|
+
*/
|
|
60
|
+
declare const reprojectGeoJson: (geojson: FeatureCollection, sourceProjection: string, targetProjection?: string, keepOriginalGeometryAsFeatureProperty?: boolean) => FeatureCollection<geojson.Geometry, geojson.GeoJsonProperties>;
|
|
61
|
+
|
|
62
|
+
export { reprojectGeoJson, toGeojsonContours, toGlb };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,62 @@
|
|
|
1
|
-
|
|
1
|
+
import * as geojson from 'geojson';
|
|
2
|
+
import { FeatureCollection, LineString } from 'geojson';
|
|
2
3
|
|
|
3
|
-
|
|
4
|
+
/**
|
|
5
|
+
* @param landXmlString
|
|
6
|
+
* @param center 3D models don't work well when they're far from origin (coordinate `[0,0,0]`), therefore by default your center is moved to the median of x and y axis of your surface
|
|
7
|
+
* @param surfaceId Surface name or index if your LandXML contains multiple surfaces. By default all surfaces are converted and returns an array of glb Uint8Array
|
|
8
|
+
* @returns {Object[]} glbs - Array of processed glbs (will also return an array when just one surface has been processed)
|
|
9
|
+
* @returns {string} glbs[].name - Name of surface as defined in LandXML
|
|
10
|
+
* @returns {string} glbs[].description - Description of surface as defined in LandXML
|
|
11
|
+
* @returns {string} glbs[].sourceFile - Source file of where the LandXML was generated from.
|
|
12
|
+
* @returns {string} glbs[].timeStamp - Timestamp of when the surface was exported into LandXML
|
|
13
|
+
* @returns {Uint8Array} glbs[].glb - Uint8Array binary data of glb
|
|
14
|
+
* @returns {[number, number]} glbs[].center - Offset center of the processed glb
|
|
15
|
+
* @returns {function(): void} glbs[].download - Convenient way to download the GLB, within the filename the new center will be appended.
|
|
16
|
+
*/
|
|
17
|
+
declare const toGlb: (landXmlString: string, center?: "auto" | "origin" | [
|
|
18
|
+
x: number,
|
|
19
|
+
y: number
|
|
20
|
+
], surfaceId?: string | number) => Promise<{
|
|
21
|
+
name: string;
|
|
22
|
+
description: string;
|
|
23
|
+
sourceFile: string;
|
|
24
|
+
timeStamp: string;
|
|
25
|
+
glb: Uint8Array;
|
|
26
|
+
center: [x: number, y: number];
|
|
27
|
+
download: () => void;
|
|
28
|
+
}[]>;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @param landXmlString
|
|
32
|
+
* @param contourInterval Interval at which you would like to generate contour lines
|
|
33
|
+
* @param surfaceId Surface name or index if your LandXML contains multiple surfaces. By default all surfaces are converted and returns an array of glb Uint8Array
|
|
34
|
+
* @returns {Object[]} surfaceContours - Array of processed surface contours (will also return an array when just one surface has been processed)
|
|
35
|
+
* @returns {string} surfaceContours[].name - Name of surface as defined in LandXML
|
|
36
|
+
* @returns {string} surfaceContours[].description - Description of surface as defined in LandXML
|
|
37
|
+
* @returns {string} surfaceContours[].sourceFile - Source file of where the LandXML was generated from.
|
|
38
|
+
* @returns {string} surfaceContours[].timeStamp - Timestamp of when the surface was exported into LandXML
|
|
39
|
+
* @returns {string} surfaceContours[].wktString - WKT string of LandXML coordinate system projection
|
|
40
|
+
* @returns {Object} surfaceContours[].geojson - Geojson feature collection of contour lines
|
|
41
|
+
*/
|
|
42
|
+
declare const toGeojsonContours: (landXmlString: string, contourInterval?: number, surfaceId?: string | number) => Promise<{
|
|
43
|
+
name: string;
|
|
44
|
+
description: string;
|
|
45
|
+
sourceFile: string;
|
|
46
|
+
timeStamp: string;
|
|
47
|
+
wktString?: string;
|
|
48
|
+
geojson: FeatureCollection<LineString, {
|
|
49
|
+
z: number;
|
|
50
|
+
}>;
|
|
51
|
+
}[]>;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* @param geojson
|
|
55
|
+
* @param sourceProjection can be a proj4 string or WKT string, you will likely have a wkt string available with your LandXML if you used Civil 3D exporter and had your drawing geo-referenced
|
|
56
|
+
* @param targetProjection you will most likely want to use WGS84 for online viewing, however any other projection you might need can be used as long as it's valid
|
|
57
|
+
* @param keepOriginalGeometryAsFeatureProperty if you intend to repurpose the original geometry, it can be added to geojson feature properties
|
|
58
|
+
* @returns {FeatureCollection} Geojson FeatureCollection with updated geometry coordinates
|
|
59
|
+
*/
|
|
60
|
+
declare const reprojectGeoJson: (geojson: FeatureCollection, sourceProjection: string, targetProjection?: string, keepOriginalGeometryAsFeatureProperty?: boolean) => FeatureCollection<geojson.Geometry, geojson.GeoJsonProperties>;
|
|
61
|
+
|
|
62
|
+
export { reprojectGeoJson, toGeojsonContours, toGlb };
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,39 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
2
3
|
var __defProp = Object.defineProperty;
|
|
4
|
+
var __defProps = Object.defineProperties;
|
|
3
5
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
|
|
4
7
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
8
|
+
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
|
|
9
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
5
10
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
11
|
+
var __propIsEnum = Object.prototype.propertyIsEnumerable;
|
|
12
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
13
|
+
var __spreadValues = (a, b) => {
|
|
14
|
+
for (var prop in b || (b = {}))
|
|
15
|
+
if (__hasOwnProp.call(b, prop))
|
|
16
|
+
__defNormalProp(a, prop, b[prop]);
|
|
17
|
+
if (__getOwnPropSymbols)
|
|
18
|
+
for (var prop of __getOwnPropSymbols(b)) {
|
|
19
|
+
if (__propIsEnum.call(b, prop))
|
|
20
|
+
__defNormalProp(a, prop, b[prop]);
|
|
21
|
+
}
|
|
22
|
+
return a;
|
|
23
|
+
};
|
|
24
|
+
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
|
|
25
|
+
var __objRest = (source, exclude) => {
|
|
26
|
+
var target = {};
|
|
27
|
+
for (var prop in source)
|
|
28
|
+
if (__hasOwnProp.call(source, prop) && exclude.indexOf(prop) < 0)
|
|
29
|
+
target[prop] = source[prop];
|
|
30
|
+
if (source != null && __getOwnPropSymbols)
|
|
31
|
+
for (var prop of __getOwnPropSymbols(source)) {
|
|
32
|
+
if (exclude.indexOf(prop) < 0 && __propIsEnum.call(source, prop))
|
|
33
|
+
target[prop] = source[prop];
|
|
34
|
+
}
|
|
35
|
+
return target;
|
|
36
|
+
};
|
|
6
37
|
var __export = (target, all) => {
|
|
7
38
|
for (var name in all)
|
|
8
39
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
@@ -15,14 +46,345 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
15
46
|
}
|
|
16
47
|
return to;
|
|
17
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
|
+
));
|
|
18
57
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
58
|
+
var __async = (__this, __arguments, generator) => {
|
|
59
|
+
return new Promise((resolve, reject) => {
|
|
60
|
+
var fulfilled = (value) => {
|
|
61
|
+
try {
|
|
62
|
+
step(generator.next(value));
|
|
63
|
+
} catch (e) {
|
|
64
|
+
reject(e);
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
var rejected = (value) => {
|
|
68
|
+
try {
|
|
69
|
+
step(generator.throw(value));
|
|
70
|
+
} catch (e) {
|
|
71
|
+
reject(e);
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
|
|
75
|
+
step((generator = generator.apply(__this, __arguments)).next());
|
|
76
|
+
});
|
|
77
|
+
};
|
|
19
78
|
|
|
20
79
|
// src/index.ts
|
|
21
80
|
var src_exports = {};
|
|
22
81
|
__export(src_exports, {
|
|
23
|
-
|
|
82
|
+
reprojectGeoJson: () => reproject_geojson_default,
|
|
83
|
+
toGeojsonContours: () => to_geojson_contours_default,
|
|
84
|
+
toGlb: () => to_glb_default
|
|
24
85
|
});
|
|
25
86
|
module.exports = __toCommonJS(src_exports);
|
|
26
|
-
|
|
27
|
-
|
|
87
|
+
|
|
88
|
+
// src/private/filter-by-surfaceId.ts
|
|
89
|
+
var filterBySurfaceId = (parsedSurfaces, surfaceId) => {
|
|
90
|
+
let filtered = [...parsedSurfaces];
|
|
91
|
+
if (typeof surfaceId === "string") {
|
|
92
|
+
filtered = filtered.filter((s) => s.name === surfaceId);
|
|
93
|
+
if (filtered.length === 0)
|
|
94
|
+
throw "Provided SurfaceId doesn't exist within provided LandXML";
|
|
95
|
+
}
|
|
96
|
+
if (typeof surfaceId === "number" && surfaceId > 0) {
|
|
97
|
+
if (!filtered[surfaceId])
|
|
98
|
+
throw `Provided SurfaceId index is out of range. Provided LandXML has ${filtered.length} surfaces.`;
|
|
99
|
+
filtered = [filtered[surfaceId]];
|
|
100
|
+
}
|
|
101
|
+
return filtered;
|
|
102
|
+
};
|
|
103
|
+
var filter_by_surfaceId_default = filterBySurfaceId;
|
|
104
|
+
|
|
105
|
+
// src/private/get-glb.ts
|
|
106
|
+
var import_core = require("@gltf-transform/core");
|
|
107
|
+
var findXYAxisMedians = (vertices) => {
|
|
108
|
+
var _a, _b;
|
|
109
|
+
vertices = vertices.slice().filter(Boolean);
|
|
110
|
+
const middleIndex = Math.floor(vertices.length / 2);
|
|
111
|
+
const medianX = (_a = vertices.slice().sort((a, b) => a[0] - b[0])[middleIndex]) == null ? void 0 : _a[0];
|
|
112
|
+
const medianY = (_b = vertices.slice().sort((a, b) => a[1] - b[1])[middleIndex]) == null ? void 0 : _b[1];
|
|
113
|
+
return [medianX, medianY];
|
|
28
114
|
};
|
|
115
|
+
var getGlb = (data, customCenter) => __async(void 0, null, function* () {
|
|
116
|
+
const center = customCenter || findXYAxisMedians(data.surfaceDefinition.points);
|
|
117
|
+
const vertices = [...data.surfaceDefinition.points].map((p) => p.slice()).map(([x, y, z]) => {
|
|
118
|
+
return [x - center[0], z, -(y - center[1])];
|
|
119
|
+
}).reduce((prev, curr) => prev.concat(curr), []);
|
|
120
|
+
const triangles = data.surfaceDefinition.faces.reduce((prev, curr) => prev.concat(curr), []);
|
|
121
|
+
const doc = new import_core.Document();
|
|
122
|
+
const buffer = doc.createBuffer();
|
|
123
|
+
const position = doc.createAccessor().setType("VEC3").setArray(new Float32Array(vertices)).setBuffer(buffer);
|
|
124
|
+
const indices = doc.createAccessor().setType("SCALAR").setArray(new Uint32Array(triangles)).setBuffer(buffer);
|
|
125
|
+
const prim = doc.createPrimitive().setAttribute("POSITION", position).setIndices(indices);
|
|
126
|
+
const mesh = doc.createMesh().addPrimitive(prim);
|
|
127
|
+
const node = doc.createNode().setMesh(mesh);
|
|
128
|
+
const scene = doc.createScene().addChild(node);
|
|
129
|
+
const glb = yield new import_core.WebIO().writeBinary(doc);
|
|
130
|
+
return { glb, center };
|
|
131
|
+
});
|
|
132
|
+
var get_glb_default = getGlb;
|
|
133
|
+
|
|
134
|
+
// src/private/download-glb.ts
|
|
135
|
+
var downloadGlb = (glbData, fileName) => {
|
|
136
|
+
const blobUrl = URL.createObjectURL(new Blob([glbData], { type: "model/gltf-binary" }));
|
|
137
|
+
const link = document.createElement("a");
|
|
138
|
+
link.href = blobUrl;
|
|
139
|
+
link.download = fileName;
|
|
140
|
+
document.body.appendChild(link);
|
|
141
|
+
link.click();
|
|
142
|
+
document.body.removeChild(link);
|
|
143
|
+
URL.revokeObjectURL(blobUrl);
|
|
144
|
+
};
|
|
145
|
+
var download_glb_default = downloadGlb;
|
|
146
|
+
|
|
147
|
+
// src/private/parse-xml.ts
|
|
148
|
+
var import_xml2json = __toESM(require("xml2json"));
|
|
149
|
+
var parseXML = (xmlString) => __async(void 0, null, function* () {
|
|
150
|
+
const { LandXML } = yield import_xml2json.default.toJson(xmlString, { object: true, coerce: true, arrayNotation: true });
|
|
151
|
+
return LandXML[0].Surfaces[0].Surface.map((surface) => {
|
|
152
|
+
var _a, _b, _c;
|
|
153
|
+
let points = [];
|
|
154
|
+
let faces = [];
|
|
155
|
+
let pointIdMap = {};
|
|
156
|
+
surface.Definition[0].Pnts[0].P.forEach((pt) => {
|
|
157
|
+
const { id, $t } = pt;
|
|
158
|
+
const [y, x, z] = $t.split(" ").map((v) => parseFloat(v));
|
|
159
|
+
points.push([x, y, z]);
|
|
160
|
+
pointIdMap[id] = points.length - 1;
|
|
161
|
+
}, []);
|
|
162
|
+
faces = surface.Definition[0].Faces[0].F.map((face) => {
|
|
163
|
+
const { $t } = face;
|
|
164
|
+
const [a, b, c] = $t.split(" ").map((v) => pointIdMap[v]);
|
|
165
|
+
if ([a, b, c].filter((v) => typeof v === "undefined").length > 0) {
|
|
166
|
+
throw `Invalid LandXML. A face is referencing a point that doesn't exist. Face is referencing points: ${$t}`;
|
|
167
|
+
}
|
|
168
|
+
return [a, b, c];
|
|
169
|
+
});
|
|
170
|
+
return {
|
|
171
|
+
sourceFile: LandXML[0].Project[0].name,
|
|
172
|
+
timeStamp: LandXML[0].Application[0].timeStamp,
|
|
173
|
+
name: surface.name,
|
|
174
|
+
description: surface.desc,
|
|
175
|
+
wktString: ((_c = (_b = (_a = LandXML[0]) == null ? void 0 : _a.CoordinateSystem) == null ? void 0 : _b[0]) == null ? void 0 : _c.ogcWktCode) || void 0,
|
|
176
|
+
surfaceDefinition: {
|
|
177
|
+
points,
|
|
178
|
+
faces
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
var parse_xml_default = parseXML;
|
|
184
|
+
|
|
185
|
+
// src/public/to-glb.ts
|
|
186
|
+
var toGlb = (landXmlString, center = "auto", surfaceId = -1) => __async(void 0, null, function* () {
|
|
187
|
+
const requestedCenter = center == "origin" ? [0, 0] : center === "auto" ? void 0 : center;
|
|
188
|
+
let requestedParsedSurfaces = filter_by_surfaceId_default(yield parse_xml_default(landXmlString), surfaceId);
|
|
189
|
+
const glbs = yield Promise.all(
|
|
190
|
+
requestedParsedSurfaces.map(
|
|
191
|
+
(surface) => new Promise((resolve, reject) => __async(void 0, null, function* () {
|
|
192
|
+
try {
|
|
193
|
+
const { glb, center: center2 } = yield get_glb_default(surface, requestedCenter);
|
|
194
|
+
const _a = surface, { surfaceDefinition } = _a, rest = __objRest(_a, ["surfaceDefinition"]);
|
|
195
|
+
resolve(__spreadProps(__spreadValues({}, rest), {
|
|
196
|
+
glb,
|
|
197
|
+
center: center2,
|
|
198
|
+
download: () => {
|
|
199
|
+
download_glb_default(glb, surface.name.replace(/\.xml$/, `${JSON.stringify(center2)}.glb`));
|
|
200
|
+
}
|
|
201
|
+
}));
|
|
202
|
+
} catch (e) {
|
|
203
|
+
reject(e);
|
|
204
|
+
}
|
|
205
|
+
}))
|
|
206
|
+
)
|
|
207
|
+
);
|
|
208
|
+
return glbs;
|
|
209
|
+
});
|
|
210
|
+
var to_glb_default = toGlb;
|
|
211
|
+
|
|
212
|
+
// src/private/get-contours.ts
|
|
213
|
+
var contourLineOnFace = (face, z) => {
|
|
214
|
+
let line = [];
|
|
215
|
+
for (let i = 0; i < face.length; i++) {
|
|
216
|
+
let vertex1 = face[i];
|
|
217
|
+
let vertex2 = face[(i + 1) % face.length];
|
|
218
|
+
if (vertex1[2] <= z && vertex2[2] >= z || vertex1[2] >= z && vertex2[2] <= z) {
|
|
219
|
+
let t = (z - vertex1[2]) / (vertex2[2] - vertex1[2]);
|
|
220
|
+
line.push([vertex1[0] + t * (vertex2[0] - vertex1[0]), vertex1[1] + t * (vertex2[1] - vertex1[1])]);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
return line.length > 0 ? line : void 0;
|
|
224
|
+
};
|
|
225
|
+
var linesToPolyLines = (lineSegments) => {
|
|
226
|
+
if (!Array.isArray(lineSegments) || lineSegments.length === 0) {
|
|
227
|
+
throw new Error("Invalid input: Please provide a non-empty array of line segments.");
|
|
228
|
+
}
|
|
229
|
+
const segmentsMap = /* @__PURE__ */ new Map();
|
|
230
|
+
const polylines = [];
|
|
231
|
+
lineSegments.forEach(([start, end]) => {
|
|
232
|
+
const startKey = start.join(",");
|
|
233
|
+
const endKey = end.join(",");
|
|
234
|
+
segmentsMap.set(startKey, segmentsMap.get(startKey) || []);
|
|
235
|
+
segmentsMap.set(endKey, segmentsMap.get(endKey) || []);
|
|
236
|
+
segmentsMap.get(startKey).push(end);
|
|
237
|
+
segmentsMap.get(endKey).push(start);
|
|
238
|
+
});
|
|
239
|
+
const constructPolyline = (start, polyline) => {
|
|
240
|
+
let current = start;
|
|
241
|
+
while (segmentsMap.has(current.join(",")) && segmentsMap.get(current.join(",")).length > 0) {
|
|
242
|
+
const next = segmentsMap.get(current.join(",")).pop();
|
|
243
|
+
polyline.push(current);
|
|
244
|
+
if (next) {
|
|
245
|
+
const nextKey = next.join(",");
|
|
246
|
+
segmentsMap.delete(current.join(","));
|
|
247
|
+
current = next;
|
|
248
|
+
if (segmentsMap.has(nextKey)) {
|
|
249
|
+
segmentsMap.set(
|
|
250
|
+
nextKey,
|
|
251
|
+
(segmentsMap.get(nextKey) || []).filter((point) => point.join(",") !== current.join(","))
|
|
252
|
+
);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
polyline.push(current);
|
|
257
|
+
return polyline;
|
|
258
|
+
};
|
|
259
|
+
const traverseSegmentsMap = (endpoints, startKey) => {
|
|
260
|
+
let start = startKey.split(",").map(Number);
|
|
261
|
+
while (endpoints.length > 0) {
|
|
262
|
+
const newPolyline = constructPolyline(start, []);
|
|
263
|
+
polylines.push(newPolyline);
|
|
264
|
+
start = endpoints.pop();
|
|
265
|
+
}
|
|
266
|
+
};
|
|
267
|
+
for (const [startKey, endpoints] of segmentsMap.entries()) {
|
|
268
|
+
if (endpoints && endpoints.length === 0) {
|
|
269
|
+
continue;
|
|
270
|
+
}
|
|
271
|
+
traverseSegmentsMap(endpoints || [], startKey);
|
|
272
|
+
}
|
|
273
|
+
return polylines;
|
|
274
|
+
};
|
|
275
|
+
var contourElevations = (minElevation, maxElevation, increment) => {
|
|
276
|
+
const elevations = [];
|
|
277
|
+
const start = Math.ceil(minElevation * 2) / 2;
|
|
278
|
+
const end = Math.floor(maxElevation * 2) / 2;
|
|
279
|
+
for (let elevation = start; elevation <= end; elevation += increment) {
|
|
280
|
+
elevations.push(elevation);
|
|
281
|
+
}
|
|
282
|
+
return elevations;
|
|
283
|
+
};
|
|
284
|
+
var constructGeojson = (elevationData) => {
|
|
285
|
+
const features = elevationData.reduce((prev, data) => {
|
|
286
|
+
const { elevation, polylines } = data;
|
|
287
|
+
return prev.concat(
|
|
288
|
+
polylines.map((polyline) => ({
|
|
289
|
+
type: "Feature",
|
|
290
|
+
geometry: {
|
|
291
|
+
type: "LineString",
|
|
292
|
+
coordinates: polyline
|
|
293
|
+
},
|
|
294
|
+
properties: {
|
|
295
|
+
z: elevation
|
|
296
|
+
}
|
|
297
|
+
}))
|
|
298
|
+
);
|
|
299
|
+
}, []);
|
|
300
|
+
return {
|
|
301
|
+
type: "FeatureCollection",
|
|
302
|
+
features
|
|
303
|
+
};
|
|
304
|
+
};
|
|
305
|
+
var getContours = (data, interval = 2) => __async(void 0, null, function* () {
|
|
306
|
+
const triangles = data.surfaceDefinition.faces.map(
|
|
307
|
+
(face) => face.map((vert) => data.surfaceDefinition.points[vert])
|
|
308
|
+
);
|
|
309
|
+
const [minElevation, maxElevation] = data.surfaceDefinition.points.reduce(
|
|
310
|
+
([prevMin, prevMax], curr) => [Math.min(prevMin, curr[2]), Math.max(prevMax, curr[2])],
|
|
311
|
+
[Infinity, -Infinity]
|
|
312
|
+
);
|
|
313
|
+
const elevations = contourElevations(minElevation, maxElevation, interval);
|
|
314
|
+
const elevationPolylines = elevations.map((e) => ({
|
|
315
|
+
elevation: e,
|
|
316
|
+
polylines: linesToPolyLines(
|
|
317
|
+
triangles.reduce((prev, curr) => {
|
|
318
|
+
const line = contourLineOnFace(curr, e);
|
|
319
|
+
if (line)
|
|
320
|
+
prev.push(line);
|
|
321
|
+
return prev;
|
|
322
|
+
}, [])
|
|
323
|
+
)
|
|
324
|
+
}));
|
|
325
|
+
return constructGeojson(elevationPolylines);
|
|
326
|
+
});
|
|
327
|
+
var get_contours_default = getContours;
|
|
328
|
+
|
|
329
|
+
// src/public/to-geojson-contours.ts
|
|
330
|
+
var toGeojsonContours = (landXmlString, contourInterval = 2, surfaceId = -1) => __async(void 0, null, function* () {
|
|
331
|
+
let requestedParsedSurfaces = filter_by_surfaceId_default(yield parse_xml_default(landXmlString), surfaceId);
|
|
332
|
+
const contours = yield Promise.all(
|
|
333
|
+
requestedParsedSurfaces.map(
|
|
334
|
+
(surface) => new Promise((resolve, reject) => __async(void 0, null, function* () {
|
|
335
|
+
try {
|
|
336
|
+
const geojson = yield get_contours_default(surface, contourInterval);
|
|
337
|
+
const _a = surface, { surfaceDefinition } = _a, rest = __objRest(_a, ["surfaceDefinition"]);
|
|
338
|
+
resolve(__spreadProps(__spreadValues({}, rest), {
|
|
339
|
+
geojson
|
|
340
|
+
}));
|
|
341
|
+
} catch (e) {
|
|
342
|
+
reject(e);
|
|
343
|
+
}
|
|
344
|
+
}))
|
|
345
|
+
)
|
|
346
|
+
);
|
|
347
|
+
return contours;
|
|
348
|
+
});
|
|
349
|
+
var to_geojson_contours_default = toGeojsonContours;
|
|
350
|
+
|
|
351
|
+
// src/public/reproject-geojson.ts
|
|
352
|
+
var import_proj4 = __toESM(require("proj4"));
|
|
353
|
+
var reprojectGeoJson = (geojson, sourceProjection, targetProjection = "WGS84", keepOriginalGeometryAsFeatureProperty = true) => {
|
|
354
|
+
const transformCoordinates = (coordinates, sourceProjection2, targetProjection2) => {
|
|
355
|
+
if (Array.isArray(coordinates[0])) {
|
|
356
|
+
coordinates = coordinates.map(
|
|
357
|
+
(subCoordinates) => transformCoordinates(subCoordinates, sourceProjection2, targetProjection2)
|
|
358
|
+
);
|
|
359
|
+
} else {
|
|
360
|
+
coordinates = (0, import_proj4.default)(sourceProjection2, targetProjection2, coordinates);
|
|
361
|
+
}
|
|
362
|
+
return coordinates;
|
|
363
|
+
};
|
|
364
|
+
if (!geojson || !geojson.features || !Array.isArray(geojson.features) || !sourceProjection) {
|
|
365
|
+
throw new Error("Invalid GeoJSON or source projection.");
|
|
366
|
+
}
|
|
367
|
+
geojson.features.forEach((feature) => {
|
|
368
|
+
if (keepOriginalGeometryAsFeatureProperty)
|
|
369
|
+
feature.properties = feature.properties || {};
|
|
370
|
+
if (feature.geometry) {
|
|
371
|
+
if (keepOriginalGeometryAsFeatureProperty && feature.properties)
|
|
372
|
+
feature.properties._rawGeometry = __spreadValues({}, feature.geometry);
|
|
373
|
+
if (sourceProjection !== targetProjection) {
|
|
374
|
+
feature.geometry.coordinates = transformCoordinates(
|
|
375
|
+
feature.geometry.coordinates,
|
|
376
|
+
sourceProjection,
|
|
377
|
+
targetProjection
|
|
378
|
+
);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
});
|
|
382
|
+
return geojson;
|
|
383
|
+
};
|
|
384
|
+
var reproject_geojson_default = reprojectGeoJson;
|
|
385
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
386
|
+
0 && (module.exports = {
|
|
387
|
+
reprojectGeoJson,
|
|
388
|
+
toGeojsonContours,
|
|
389
|
+
toGlb
|
|
390
|
+
});
|
package/dist/index.mjs
CHANGED
|
@@ -1,7 +1,354 @@
|
|
|
1
|
-
|
|
2
|
-
var
|
|
3
|
-
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __defProps = Object.defineProperties;
|
|
3
|
+
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
|
|
4
|
+
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __propIsEnum = Object.prototype.propertyIsEnumerable;
|
|
7
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
8
|
+
var __spreadValues = (a, b) => {
|
|
9
|
+
for (var prop in b || (b = {}))
|
|
10
|
+
if (__hasOwnProp.call(b, prop))
|
|
11
|
+
__defNormalProp(a, prop, b[prop]);
|
|
12
|
+
if (__getOwnPropSymbols)
|
|
13
|
+
for (var prop of __getOwnPropSymbols(b)) {
|
|
14
|
+
if (__propIsEnum.call(b, prop))
|
|
15
|
+
__defNormalProp(a, prop, b[prop]);
|
|
16
|
+
}
|
|
17
|
+
return a;
|
|
4
18
|
};
|
|
19
|
+
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
|
|
20
|
+
var __objRest = (source, exclude) => {
|
|
21
|
+
var target = {};
|
|
22
|
+
for (var prop in source)
|
|
23
|
+
if (__hasOwnProp.call(source, prop) && exclude.indexOf(prop) < 0)
|
|
24
|
+
target[prop] = source[prop];
|
|
25
|
+
if (source != null && __getOwnPropSymbols)
|
|
26
|
+
for (var prop of __getOwnPropSymbols(source)) {
|
|
27
|
+
if (exclude.indexOf(prop) < 0 && __propIsEnum.call(source, prop))
|
|
28
|
+
target[prop] = source[prop];
|
|
29
|
+
}
|
|
30
|
+
return target;
|
|
31
|
+
};
|
|
32
|
+
var __async = (__this, __arguments, generator) => {
|
|
33
|
+
return new Promise((resolve, reject) => {
|
|
34
|
+
var fulfilled = (value) => {
|
|
35
|
+
try {
|
|
36
|
+
step(generator.next(value));
|
|
37
|
+
} catch (e) {
|
|
38
|
+
reject(e);
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
var rejected = (value) => {
|
|
42
|
+
try {
|
|
43
|
+
step(generator.throw(value));
|
|
44
|
+
} catch (e) {
|
|
45
|
+
reject(e);
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
|
|
49
|
+
step((generator = generator.apply(__this, __arguments)).next());
|
|
50
|
+
});
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
// src/private/filter-by-surfaceId.ts
|
|
54
|
+
var filterBySurfaceId = (parsedSurfaces, surfaceId) => {
|
|
55
|
+
let filtered = [...parsedSurfaces];
|
|
56
|
+
if (typeof surfaceId === "string") {
|
|
57
|
+
filtered = filtered.filter((s) => s.name === surfaceId);
|
|
58
|
+
if (filtered.length === 0)
|
|
59
|
+
throw "Provided SurfaceId doesn't exist within provided LandXML";
|
|
60
|
+
}
|
|
61
|
+
if (typeof surfaceId === "number" && surfaceId > 0) {
|
|
62
|
+
if (!filtered[surfaceId])
|
|
63
|
+
throw `Provided SurfaceId index is out of range. Provided LandXML has ${filtered.length} surfaces.`;
|
|
64
|
+
filtered = [filtered[surfaceId]];
|
|
65
|
+
}
|
|
66
|
+
return filtered;
|
|
67
|
+
};
|
|
68
|
+
var filter_by_surfaceId_default = filterBySurfaceId;
|
|
69
|
+
|
|
70
|
+
// src/private/get-glb.ts
|
|
71
|
+
import { Document, WebIO } from "@gltf-transform/core";
|
|
72
|
+
var findXYAxisMedians = (vertices) => {
|
|
73
|
+
var _a, _b;
|
|
74
|
+
vertices = vertices.slice().filter(Boolean);
|
|
75
|
+
const middleIndex = Math.floor(vertices.length / 2);
|
|
76
|
+
const medianX = (_a = vertices.slice().sort((a, b) => a[0] - b[0])[middleIndex]) == null ? void 0 : _a[0];
|
|
77
|
+
const medianY = (_b = vertices.slice().sort((a, b) => a[1] - b[1])[middleIndex]) == null ? void 0 : _b[1];
|
|
78
|
+
return [medianX, medianY];
|
|
79
|
+
};
|
|
80
|
+
var getGlb = (data, customCenter) => __async(void 0, null, function* () {
|
|
81
|
+
const center = customCenter || findXYAxisMedians(data.surfaceDefinition.points);
|
|
82
|
+
const vertices = [...data.surfaceDefinition.points].map((p) => p.slice()).map(([x, y, z]) => {
|
|
83
|
+
return [x - center[0], z, -(y - center[1])];
|
|
84
|
+
}).reduce((prev, curr) => prev.concat(curr), []);
|
|
85
|
+
const triangles = data.surfaceDefinition.faces.reduce((prev, curr) => prev.concat(curr), []);
|
|
86
|
+
const doc = new Document();
|
|
87
|
+
const buffer = doc.createBuffer();
|
|
88
|
+
const position = doc.createAccessor().setType("VEC3").setArray(new Float32Array(vertices)).setBuffer(buffer);
|
|
89
|
+
const indices = doc.createAccessor().setType("SCALAR").setArray(new Uint32Array(triangles)).setBuffer(buffer);
|
|
90
|
+
const prim = doc.createPrimitive().setAttribute("POSITION", position).setIndices(indices);
|
|
91
|
+
const mesh = doc.createMesh().addPrimitive(prim);
|
|
92
|
+
const node = doc.createNode().setMesh(mesh);
|
|
93
|
+
const scene = doc.createScene().addChild(node);
|
|
94
|
+
const glb = yield new WebIO().writeBinary(doc);
|
|
95
|
+
return { glb, center };
|
|
96
|
+
});
|
|
97
|
+
var get_glb_default = getGlb;
|
|
98
|
+
|
|
99
|
+
// src/private/download-glb.ts
|
|
100
|
+
var downloadGlb = (glbData, fileName) => {
|
|
101
|
+
const blobUrl = URL.createObjectURL(new Blob([glbData], { type: "model/gltf-binary" }));
|
|
102
|
+
const link = document.createElement("a");
|
|
103
|
+
link.href = blobUrl;
|
|
104
|
+
link.download = fileName;
|
|
105
|
+
document.body.appendChild(link);
|
|
106
|
+
link.click();
|
|
107
|
+
document.body.removeChild(link);
|
|
108
|
+
URL.revokeObjectURL(blobUrl);
|
|
109
|
+
};
|
|
110
|
+
var download_glb_default = downloadGlb;
|
|
111
|
+
|
|
112
|
+
// src/private/parse-xml.ts
|
|
113
|
+
import xml2json from "xml2json";
|
|
114
|
+
var parseXML = (xmlString) => __async(void 0, null, function* () {
|
|
115
|
+
const { LandXML } = yield xml2json.toJson(xmlString, { object: true, coerce: true, arrayNotation: true });
|
|
116
|
+
return LandXML[0].Surfaces[0].Surface.map((surface) => {
|
|
117
|
+
var _a, _b, _c;
|
|
118
|
+
let points = [];
|
|
119
|
+
let faces = [];
|
|
120
|
+
let pointIdMap = {};
|
|
121
|
+
surface.Definition[0].Pnts[0].P.forEach((pt) => {
|
|
122
|
+
const { id, $t } = pt;
|
|
123
|
+
const [y, x, z] = $t.split(" ").map((v) => parseFloat(v));
|
|
124
|
+
points.push([x, y, z]);
|
|
125
|
+
pointIdMap[id] = points.length - 1;
|
|
126
|
+
}, []);
|
|
127
|
+
faces = surface.Definition[0].Faces[0].F.map((face) => {
|
|
128
|
+
const { $t } = face;
|
|
129
|
+
const [a, b, c] = $t.split(" ").map((v) => pointIdMap[v]);
|
|
130
|
+
if ([a, b, c].filter((v) => typeof v === "undefined").length > 0) {
|
|
131
|
+
throw `Invalid LandXML. A face is referencing a point that doesn't exist. Face is referencing points: ${$t}`;
|
|
132
|
+
}
|
|
133
|
+
return [a, b, c];
|
|
134
|
+
});
|
|
135
|
+
return {
|
|
136
|
+
sourceFile: LandXML[0].Project[0].name,
|
|
137
|
+
timeStamp: LandXML[0].Application[0].timeStamp,
|
|
138
|
+
name: surface.name,
|
|
139
|
+
description: surface.desc,
|
|
140
|
+
wktString: ((_c = (_b = (_a = LandXML[0]) == null ? void 0 : _a.CoordinateSystem) == null ? void 0 : _b[0]) == null ? void 0 : _c.ogcWktCode) || void 0,
|
|
141
|
+
surfaceDefinition: {
|
|
142
|
+
points,
|
|
143
|
+
faces
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
var parse_xml_default = parseXML;
|
|
149
|
+
|
|
150
|
+
// src/public/to-glb.ts
|
|
151
|
+
var toGlb = (landXmlString, center = "auto", surfaceId = -1) => __async(void 0, null, function* () {
|
|
152
|
+
const requestedCenter = center == "origin" ? [0, 0] : center === "auto" ? void 0 : center;
|
|
153
|
+
let requestedParsedSurfaces = filter_by_surfaceId_default(yield parse_xml_default(landXmlString), surfaceId);
|
|
154
|
+
const glbs = yield Promise.all(
|
|
155
|
+
requestedParsedSurfaces.map(
|
|
156
|
+
(surface) => new Promise((resolve, reject) => __async(void 0, null, function* () {
|
|
157
|
+
try {
|
|
158
|
+
const { glb, center: center2 } = yield get_glb_default(surface, requestedCenter);
|
|
159
|
+
const _a = surface, { surfaceDefinition } = _a, rest = __objRest(_a, ["surfaceDefinition"]);
|
|
160
|
+
resolve(__spreadProps(__spreadValues({}, rest), {
|
|
161
|
+
glb,
|
|
162
|
+
center: center2,
|
|
163
|
+
download: () => {
|
|
164
|
+
download_glb_default(glb, surface.name.replace(/\.xml$/, `${JSON.stringify(center2)}.glb`));
|
|
165
|
+
}
|
|
166
|
+
}));
|
|
167
|
+
} catch (e) {
|
|
168
|
+
reject(e);
|
|
169
|
+
}
|
|
170
|
+
}))
|
|
171
|
+
)
|
|
172
|
+
);
|
|
173
|
+
return glbs;
|
|
174
|
+
});
|
|
175
|
+
var to_glb_default = toGlb;
|
|
176
|
+
|
|
177
|
+
// src/private/get-contours.ts
|
|
178
|
+
var contourLineOnFace = (face, z) => {
|
|
179
|
+
let line = [];
|
|
180
|
+
for (let i = 0; i < face.length; i++) {
|
|
181
|
+
let vertex1 = face[i];
|
|
182
|
+
let vertex2 = face[(i + 1) % face.length];
|
|
183
|
+
if (vertex1[2] <= z && vertex2[2] >= z || vertex1[2] >= z && vertex2[2] <= z) {
|
|
184
|
+
let t = (z - vertex1[2]) / (vertex2[2] - vertex1[2]);
|
|
185
|
+
line.push([vertex1[0] + t * (vertex2[0] - vertex1[0]), vertex1[1] + t * (vertex2[1] - vertex1[1])]);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return line.length > 0 ? line : void 0;
|
|
189
|
+
};
|
|
190
|
+
var linesToPolyLines = (lineSegments) => {
|
|
191
|
+
if (!Array.isArray(lineSegments) || lineSegments.length === 0) {
|
|
192
|
+
throw new Error("Invalid input: Please provide a non-empty array of line segments.");
|
|
193
|
+
}
|
|
194
|
+
const segmentsMap = /* @__PURE__ */ new Map();
|
|
195
|
+
const polylines = [];
|
|
196
|
+
lineSegments.forEach(([start, end]) => {
|
|
197
|
+
const startKey = start.join(",");
|
|
198
|
+
const endKey = end.join(",");
|
|
199
|
+
segmentsMap.set(startKey, segmentsMap.get(startKey) || []);
|
|
200
|
+
segmentsMap.set(endKey, segmentsMap.get(endKey) || []);
|
|
201
|
+
segmentsMap.get(startKey).push(end);
|
|
202
|
+
segmentsMap.get(endKey).push(start);
|
|
203
|
+
});
|
|
204
|
+
const constructPolyline = (start, polyline) => {
|
|
205
|
+
let current = start;
|
|
206
|
+
while (segmentsMap.has(current.join(",")) && segmentsMap.get(current.join(",")).length > 0) {
|
|
207
|
+
const next = segmentsMap.get(current.join(",")).pop();
|
|
208
|
+
polyline.push(current);
|
|
209
|
+
if (next) {
|
|
210
|
+
const nextKey = next.join(",");
|
|
211
|
+
segmentsMap.delete(current.join(","));
|
|
212
|
+
current = next;
|
|
213
|
+
if (segmentsMap.has(nextKey)) {
|
|
214
|
+
segmentsMap.set(
|
|
215
|
+
nextKey,
|
|
216
|
+
(segmentsMap.get(nextKey) || []).filter((point) => point.join(",") !== current.join(","))
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
polyline.push(current);
|
|
222
|
+
return polyline;
|
|
223
|
+
};
|
|
224
|
+
const traverseSegmentsMap = (endpoints, startKey) => {
|
|
225
|
+
let start = startKey.split(",").map(Number);
|
|
226
|
+
while (endpoints.length > 0) {
|
|
227
|
+
const newPolyline = constructPolyline(start, []);
|
|
228
|
+
polylines.push(newPolyline);
|
|
229
|
+
start = endpoints.pop();
|
|
230
|
+
}
|
|
231
|
+
};
|
|
232
|
+
for (const [startKey, endpoints] of segmentsMap.entries()) {
|
|
233
|
+
if (endpoints && endpoints.length === 0) {
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
traverseSegmentsMap(endpoints || [], startKey);
|
|
237
|
+
}
|
|
238
|
+
return polylines;
|
|
239
|
+
};
|
|
240
|
+
var contourElevations = (minElevation, maxElevation, increment) => {
|
|
241
|
+
const elevations = [];
|
|
242
|
+
const start = Math.ceil(minElevation * 2) / 2;
|
|
243
|
+
const end = Math.floor(maxElevation * 2) / 2;
|
|
244
|
+
for (let elevation = start; elevation <= end; elevation += increment) {
|
|
245
|
+
elevations.push(elevation);
|
|
246
|
+
}
|
|
247
|
+
return elevations;
|
|
248
|
+
};
|
|
249
|
+
var constructGeojson = (elevationData) => {
|
|
250
|
+
const features = elevationData.reduce((prev, data) => {
|
|
251
|
+
const { elevation, polylines } = data;
|
|
252
|
+
return prev.concat(
|
|
253
|
+
polylines.map((polyline) => ({
|
|
254
|
+
type: "Feature",
|
|
255
|
+
geometry: {
|
|
256
|
+
type: "LineString",
|
|
257
|
+
coordinates: polyline
|
|
258
|
+
},
|
|
259
|
+
properties: {
|
|
260
|
+
z: elevation
|
|
261
|
+
}
|
|
262
|
+
}))
|
|
263
|
+
);
|
|
264
|
+
}, []);
|
|
265
|
+
return {
|
|
266
|
+
type: "FeatureCollection",
|
|
267
|
+
features
|
|
268
|
+
};
|
|
269
|
+
};
|
|
270
|
+
var getContours = (data, interval = 2) => __async(void 0, null, function* () {
|
|
271
|
+
const triangles = data.surfaceDefinition.faces.map(
|
|
272
|
+
(face) => face.map((vert) => data.surfaceDefinition.points[vert])
|
|
273
|
+
);
|
|
274
|
+
const [minElevation, maxElevation] = data.surfaceDefinition.points.reduce(
|
|
275
|
+
([prevMin, prevMax], curr) => [Math.min(prevMin, curr[2]), Math.max(prevMax, curr[2])],
|
|
276
|
+
[Infinity, -Infinity]
|
|
277
|
+
);
|
|
278
|
+
const elevations = contourElevations(minElevation, maxElevation, interval);
|
|
279
|
+
const elevationPolylines = elevations.map((e) => ({
|
|
280
|
+
elevation: e,
|
|
281
|
+
polylines: linesToPolyLines(
|
|
282
|
+
triangles.reduce((prev, curr) => {
|
|
283
|
+
const line = contourLineOnFace(curr, e);
|
|
284
|
+
if (line)
|
|
285
|
+
prev.push(line);
|
|
286
|
+
return prev;
|
|
287
|
+
}, [])
|
|
288
|
+
)
|
|
289
|
+
}));
|
|
290
|
+
return constructGeojson(elevationPolylines);
|
|
291
|
+
});
|
|
292
|
+
var get_contours_default = getContours;
|
|
293
|
+
|
|
294
|
+
// src/public/to-geojson-contours.ts
|
|
295
|
+
var toGeojsonContours = (landXmlString, contourInterval = 2, surfaceId = -1) => __async(void 0, null, function* () {
|
|
296
|
+
let requestedParsedSurfaces = filter_by_surfaceId_default(yield parse_xml_default(landXmlString), surfaceId);
|
|
297
|
+
const contours = yield Promise.all(
|
|
298
|
+
requestedParsedSurfaces.map(
|
|
299
|
+
(surface) => new Promise((resolve, reject) => __async(void 0, null, function* () {
|
|
300
|
+
try {
|
|
301
|
+
const geojson = yield get_contours_default(surface, contourInterval);
|
|
302
|
+
const _a = surface, { surfaceDefinition } = _a, rest = __objRest(_a, ["surfaceDefinition"]);
|
|
303
|
+
resolve(__spreadProps(__spreadValues({}, rest), {
|
|
304
|
+
geojson
|
|
305
|
+
}));
|
|
306
|
+
} catch (e) {
|
|
307
|
+
reject(e);
|
|
308
|
+
}
|
|
309
|
+
}))
|
|
310
|
+
)
|
|
311
|
+
);
|
|
312
|
+
return contours;
|
|
313
|
+
});
|
|
314
|
+
var to_geojson_contours_default = toGeojsonContours;
|
|
315
|
+
|
|
316
|
+
// src/public/reproject-geojson.ts
|
|
317
|
+
import proj4 from "proj4";
|
|
318
|
+
var reprojectGeoJson = (geojson, sourceProjection, targetProjection = "WGS84", keepOriginalGeometryAsFeatureProperty = true) => {
|
|
319
|
+
const transformCoordinates = (coordinates, sourceProjection2, targetProjection2) => {
|
|
320
|
+
if (Array.isArray(coordinates[0])) {
|
|
321
|
+
coordinates = coordinates.map(
|
|
322
|
+
(subCoordinates) => transformCoordinates(subCoordinates, sourceProjection2, targetProjection2)
|
|
323
|
+
);
|
|
324
|
+
} else {
|
|
325
|
+
coordinates = proj4(sourceProjection2, targetProjection2, coordinates);
|
|
326
|
+
}
|
|
327
|
+
return coordinates;
|
|
328
|
+
};
|
|
329
|
+
if (!geojson || !geojson.features || !Array.isArray(geojson.features) || !sourceProjection) {
|
|
330
|
+
throw new Error("Invalid GeoJSON or source projection.");
|
|
331
|
+
}
|
|
332
|
+
geojson.features.forEach((feature) => {
|
|
333
|
+
if (keepOriginalGeometryAsFeatureProperty)
|
|
334
|
+
feature.properties = feature.properties || {};
|
|
335
|
+
if (feature.geometry) {
|
|
336
|
+
if (keepOriginalGeometryAsFeatureProperty && feature.properties)
|
|
337
|
+
feature.properties._rawGeometry = __spreadValues({}, feature.geometry);
|
|
338
|
+
if (sourceProjection !== targetProjection) {
|
|
339
|
+
feature.geometry.coordinates = transformCoordinates(
|
|
340
|
+
feature.geometry.coordinates,
|
|
341
|
+
sourceProjection,
|
|
342
|
+
targetProjection
|
|
343
|
+
);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
});
|
|
347
|
+
return geojson;
|
|
348
|
+
};
|
|
349
|
+
var reproject_geojson_default = reprojectGeoJson;
|
|
5
350
|
export {
|
|
6
|
-
|
|
351
|
+
reproject_geojson_default as reprojectGeoJson,
|
|
352
|
+
to_geojson_contours_default as toGeojsonContours,
|
|
353
|
+
to_glb_default as toGlb
|
|
7
354
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "landxml",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.1.0",
|
|
4
4
|
"description": "Parse LandXML surfaces on the modern web.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
@@ -23,9 +23,16 @@
|
|
|
23
23
|
"license": "MIT",
|
|
24
24
|
"devDependencies": {
|
|
25
25
|
"@changesets/cli": "^2.26.2",
|
|
26
|
+
"@types/proj4": "^2.5.5",
|
|
27
|
+
"@types/xml2json": "^0.11.6",
|
|
26
28
|
"tsup": "^8.0.0",
|
|
27
29
|
"typescript": "^5.2.2",
|
|
28
30
|
"vitest": "^0.34.6"
|
|
29
31
|
},
|
|
30
|
-
"dependencies": {
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"@gltf-transform/core": "^3.9.0",
|
|
34
|
+
"@types/geojson": "^7946.0.13",
|
|
35
|
+
"proj4": "^2.9.2",
|
|
36
|
+
"xml2json": "^0.12.0"
|
|
37
|
+
}
|
|
31
38
|
}
|