giser-convert-crs 1.0.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +195 -231
- package/dist/geometryCrs.esm.js +487 -0
- package/dist/geometryCrs.umd.js +495 -0
- package/dist/giserConvertCrs.esm.js +630 -458
- package/dist/giserConvertCrs.esm.js.map +1 -0
- package/dist/giserConvertCrs.min.js +2 -0
- package/dist/giserConvertCrs.min.js.map +1 -0
- package/dist/giserConvertCrs.umd.js +630 -458
- package/dist/giserConvertCrs.umd.js.map +1 -0
- package/dist/index.d.ts +167 -117
- package/package.json +14 -3
|
@@ -1,454 +1,624 @@
|
|
|
1
|
-
const PI = Math.PI;
|
|
2
|
-
const X_PI = PI * 3000.0 / 180.0;
|
|
3
|
-
const SEMI_MAJOR_AXIS = 6378245.0;
|
|
4
|
-
const ECCENTRICITY_SQUARED = 0.00669342162296594323;
|
|
5
|
-
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
return [wgsLon
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
return [gcjLon
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
let
|
|
48
|
-
let
|
|
49
|
-
|
|
50
|
-
let
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
wgsLon
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
return [
|
|
67
|
-
},
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
this._assertLonLat(
|
|
71
|
-
const
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
return
|
|
110
|
-
},
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
}
|
|
1
|
+
const PI = Math.PI;
|
|
2
|
+
const X_PI = PI * 3000.0 / 180.0;
|
|
3
|
+
const SEMI_MAJOR_AXIS = 6378245.0;
|
|
4
|
+
const ECCENTRICITY_SQUARED = 0.00669342162296594323;
|
|
5
|
+
const EPSG3857_RADIUS = 6378137.0;
|
|
6
|
+
const EPSG3857_MAX_LAT = 85.0511287798066;
|
|
7
|
+
|
|
8
|
+
const GPS = {
|
|
9
|
+
PI,
|
|
10
|
+
x_pi: X_PI,
|
|
11
|
+
EARTH_RADIUS: EPSG3857_RADIUS,
|
|
12
|
+
|
|
13
|
+
delta(lat, lon) {
|
|
14
|
+
let dLat = this.transformLat(lon - 105.0, lat - 35.0);
|
|
15
|
+
let dLon = this.transformLon(lon - 105.0, lat - 35.0);
|
|
16
|
+
const radLat = lat / 180.0 * PI;
|
|
17
|
+
const sinLat = Math.sin(radLat);
|
|
18
|
+
const magic = 1 - ECCENTRICITY_SQUARED * sinLat * sinLat;
|
|
19
|
+
const sqrtMagic = Math.sqrt(magic);
|
|
20
|
+
|
|
21
|
+
dLat = (dLat * 180.0) / ((SEMI_MAJOR_AXIS * (1 - ECCENTRICITY_SQUARED)) / (magic * sqrtMagic) * PI);
|
|
22
|
+
dLon = (dLon * 180.0) / (SEMI_MAJOR_AXIS / sqrtMagic * Math.cos(radLat) * PI);
|
|
23
|
+
|
|
24
|
+
return { lat: dLat, lon: dLon };
|
|
25
|
+
},
|
|
26
|
+
|
|
27
|
+
wgs84_gcj02(wgsLon, wgsLat) {
|
|
28
|
+
this._assertLonLat(wgsLon, wgsLat);
|
|
29
|
+
if (this.outOfChina(wgsLat, wgsLon)) return [wgsLon, wgsLat];
|
|
30
|
+
|
|
31
|
+
const d = this.delta(wgsLat, wgsLon);
|
|
32
|
+
return [wgsLon + d.lon, wgsLat + d.lat];
|
|
33
|
+
},
|
|
34
|
+
|
|
35
|
+
gcj02_wgs84(gcjLon, gcjLat) {
|
|
36
|
+
this._assertLonLat(gcjLon, gcjLat);
|
|
37
|
+
if (this.outOfChina(gcjLat, gcjLon)) return [gcjLon, gcjLat];
|
|
38
|
+
|
|
39
|
+
const d = this.delta(gcjLat, gcjLon);
|
|
40
|
+
return [gcjLon - d.lon, gcjLat - d.lat];
|
|
41
|
+
},
|
|
42
|
+
|
|
43
|
+
gcj02_wgs84_precise(gcjLon, gcjLat) {
|
|
44
|
+
this._assertLonLat(gcjLon, gcjLat);
|
|
45
|
+
if (this.outOfChina(gcjLat, gcjLon)) return [gcjLon, gcjLat];
|
|
46
|
+
|
|
47
|
+
let wgsLon = gcjLon;
|
|
48
|
+
let wgsLat = gcjLat;
|
|
49
|
+
|
|
50
|
+
for (let i = 0; i < 5; i++) {
|
|
51
|
+
const [currLon, currLat] = this.wgs84_gcj02(wgsLon, wgsLat);
|
|
52
|
+
const dLon = currLon - gcjLon;
|
|
53
|
+
const dLat = currLat - gcjLat;
|
|
54
|
+
wgsLon -= dLon;
|
|
55
|
+
wgsLat -= dLat;
|
|
56
|
+
if (Math.abs(dLon) < 1e-11 && Math.abs(dLat) < 1e-11) break;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return [wgsLon, wgsLat];
|
|
60
|
+
},
|
|
61
|
+
|
|
62
|
+
gcj02_bd09ll(gcjLon, gcjLat) {
|
|
63
|
+
this._assertLonLat(gcjLon, gcjLat);
|
|
64
|
+
const z = Math.sqrt(gcjLon * gcjLon + gcjLat * gcjLat) + 0.00002 * Math.sin(gcjLat * X_PI);
|
|
65
|
+
const theta = Math.atan2(gcjLat, gcjLon) + 0.000003 * Math.cos(gcjLon * X_PI);
|
|
66
|
+
return [z * Math.cos(theta) + 0.0065, z * Math.sin(theta) + 0.006];
|
|
67
|
+
},
|
|
68
|
+
|
|
69
|
+
bd09ll_gcj02(bdLon, bdLat) {
|
|
70
|
+
this._assertLonLat(bdLon, bdLat);
|
|
71
|
+
const x = bdLon - 0.0065;
|
|
72
|
+
const y = bdLat - 0.006;
|
|
73
|
+
const z = Math.sqrt(x * x + y * y) - 0.00002 * Math.sin(y * X_PI);
|
|
74
|
+
const theta = Math.atan2(y, x) - 0.000003 * Math.cos(x * X_PI);
|
|
75
|
+
return [z * Math.cos(theta), z * Math.sin(theta)];
|
|
76
|
+
},
|
|
77
|
+
|
|
78
|
+
bd09ll_gcj02_precise(bdLon, bdLat) {
|
|
79
|
+
let [gLon, gLat] = this.bd09ll_gcj02(bdLon, bdLat);
|
|
80
|
+
for (let i = 0; i < 2; i++) {
|
|
81
|
+
const [tBdLon, tBdLat] = this.gcj02_bd09ll(gLon, gLat);
|
|
82
|
+
gLon -= (tBdLon - bdLon);
|
|
83
|
+
gLat -= (tBdLat - bdLat);
|
|
84
|
+
}
|
|
85
|
+
return [gLon, gLat];
|
|
86
|
+
},
|
|
87
|
+
|
|
88
|
+
wgs84_bd09ll(wgsLon, wgsLat) {
|
|
89
|
+
const gcj = this.wgs84_gcj02(wgsLon, wgsLat);
|
|
90
|
+
return this.gcj02_bd09ll(gcj[0], gcj[1]);
|
|
91
|
+
},
|
|
92
|
+
|
|
93
|
+
bd09ll_wgs84(bdLon, bdLat) {
|
|
94
|
+
const gcj = this.bd09ll_gcj02(bdLon, bdLat);
|
|
95
|
+
return this.gcj02_wgs84(gcj[0], gcj[1]);
|
|
96
|
+
},
|
|
97
|
+
|
|
98
|
+
bd09ll_wgs84_precise(bdLon, bdLat) {
|
|
99
|
+
const gcj = this.bd09ll_gcj02_precise(bdLon, bdLat);
|
|
100
|
+
return this.gcj02_wgs84_precise(gcj[0], gcj[1]);
|
|
101
|
+
},
|
|
102
|
+
|
|
103
|
+
wgs84_epsg3857(lon, lat) {
|
|
104
|
+
this._assertLonLat(lon, lat);
|
|
105
|
+
const clampedLat = Math.max(-EPSG3857_MAX_LAT, Math.min(EPSG3857_MAX_LAT, lat));
|
|
106
|
+
const x = lon * EPSG3857_RADIUS * PI / 180.0;
|
|
107
|
+
const radLat = clampedLat * PI / 180.0;
|
|
108
|
+
const y = EPSG3857_RADIUS * Math.log(Math.tan(PI / 4.0 + radLat / 2.0));
|
|
109
|
+
return [x, y];
|
|
110
|
+
},
|
|
111
|
+
|
|
112
|
+
epsg3857_wgs84(x, y) {
|
|
113
|
+
if (!Number.isFinite(x) || !Number.isFinite(y)) {
|
|
114
|
+
throw new Error('Invalid coordinate: x and y must be finite numbers.');
|
|
115
|
+
}
|
|
116
|
+
const lon = (x / (EPSG3857_RADIUS * PI)) * 180.0;
|
|
117
|
+
const lat = (2.0 * Math.atan(Math.exp(y / EPSG3857_RADIUS)) - PI / 2.0) * 180.0 / PI;
|
|
118
|
+
return [lon, lat];
|
|
119
|
+
},
|
|
120
|
+
|
|
121
|
+
gcj02_epsg3857(lon, lat) {
|
|
122
|
+
const wgs = this.gcj02_wgs84_precise(lon, lat);
|
|
123
|
+
return this.wgs84_epsg3857(wgs[0], wgs[1]);
|
|
124
|
+
},
|
|
125
|
+
|
|
126
|
+
epsg3857_gcj02(x, y) {
|
|
127
|
+
const wgs = this.epsg3857_wgs84(x, y);
|
|
128
|
+
return this.wgs84_gcj02(wgs[0], wgs[1]);
|
|
129
|
+
},
|
|
130
|
+
|
|
131
|
+
bd09ll_epsg3857(lon, lat) {
|
|
132
|
+
const wgs = this.bd09ll_wgs84_precise(lon, lat);
|
|
133
|
+
return this.wgs84_epsg3857(wgs[0], wgs[1]);
|
|
134
|
+
},
|
|
135
|
+
|
|
136
|
+
epsg3857_bd09ll(x, y) {
|
|
137
|
+
const wgs = this.epsg3857_wgs84(x, y);
|
|
138
|
+
return this.wgs84_bd09ll(wgs[0], wgs[1]);
|
|
139
|
+
},
|
|
140
|
+
|
|
141
|
+
outOfChina(lat, lon) {
|
|
142
|
+
if (Array.isArray(lat)) {
|
|
143
|
+
return this.outOfChina(lat[1], lat[0]);
|
|
144
|
+
}
|
|
145
|
+
return lon < 72.004 || lon > 137.8347 || lat < 0.8293 || lat > 55.8271;
|
|
146
|
+
},
|
|
147
|
+
|
|
148
|
+
transformLat(x, y) {
|
|
149
|
+
let ret = -100 + 2.0 * x + 3.0 * y + 0.2 * y * y + 0.1 * x * y + 0.2 * Math.sqrt(Math.abs(x));
|
|
150
|
+
ret += (20.0 * Math.sin(6.0 * x * PI) + 20.0 * Math.sin(2.0 * x * PI)) * 2.0 / 3.0;
|
|
151
|
+
ret += (20.0 * Math.sin(y * PI) + 40.0 * Math.sin(y / 3.0 * PI)) * 2.0 / 3.0;
|
|
152
|
+
ret += (160.0 * Math.sin(y / 12.0 * PI) + 320.0 * Math.sin(y * PI / 30.0)) * 2.0 / 3.0;
|
|
153
|
+
return ret;
|
|
154
|
+
},
|
|
155
|
+
|
|
156
|
+
transformLon(x, y) {
|
|
157
|
+
let ret = 300.0 + x + 2.0 * y + 0.1 * x * x + 0.1 * x * y + 0.1 * Math.sqrt(Math.abs(x));
|
|
158
|
+
ret += (20.0 * Math.sin(6.0 * x * PI) + 20.0 * Math.sin(2.0 * x * PI)) * 2.0 / 3.0;
|
|
159
|
+
ret += (20.0 * Math.sin(x * PI) + 40.0 * Math.sin(x / 3.0 * PI)) * 2.0 / 3.0;
|
|
160
|
+
ret += (150.0 * Math.sin(x / 12.0 * PI) + 300.0 * Math.sin(x / 30.0 * PI)) * 2.0 / 3.0;
|
|
161
|
+
return ret;
|
|
162
|
+
},
|
|
163
|
+
|
|
164
|
+
gcj_encrypt(wgsLat, wgsLon) {
|
|
165
|
+
const [lon, lat] = this.wgs84_gcj02(wgsLon, wgsLat);
|
|
166
|
+
return { lat, lon };
|
|
167
|
+
},
|
|
168
|
+
|
|
169
|
+
_assertLonLat(lon, lat) {
|
|
170
|
+
if (!Number.isFinite(lon) || !Number.isFinite(lat)) {
|
|
171
|
+
throw new Error('Invalid coordinate: longitude and latitude must be finite numbers.');
|
|
172
|
+
}
|
|
173
|
+
}
|
|
130
174
|
};
|
|
131
175
|
|
|
132
|
-
const crs = {
|
|
133
|
-
bd09ll: '+proj=longlat +datum=BD09',
|
|
134
|
-
gcj02: '+proj=longlat +datum=GCJ02',
|
|
135
|
-
wgs84: '+proj=longlat +datum=WGS84 +no_defs'
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
const CRS_ALIASES = {
|
|
139
|
-
'bd-09': 'bd09ll',
|
|
140
|
-
'bd09': 'bd09ll',
|
|
141
|
-
'bd09ll': 'bd09ll',
|
|
142
|
-
'epsg:4326': 'wgs84',
|
|
143
|
-
'gcj-02': 'gcj02',
|
|
144
|
-
'gcj02': 'gcj02',
|
|
145
|
-
'wgs-84': 'wgs84',
|
|
146
|
-
'wgs84': 'wgs84'
|
|
147
|
-
};
|
|
148
|
-
|
|
149
|
-
const ProjectionTransform = {
|
|
150
|
-
crs,
|
|
151
|
-
|
|
152
|
-
transform(source, fromCRS, toCRS) {
|
|
153
|
-
if (source == null) return null;
|
|
154
|
-
|
|
155
|
-
const from = this._normalizeCRS(fromCRS);
|
|
156
|
-
const to = this._normalizeCRS(toCRS);
|
|
157
|
-
|
|
158
|
-
if (this._isCoord(source)) {
|
|
159
|
-
return this._transformCoordinate(source, from, to);
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
if (Array.isArray(source)) {
|
|
163
|
-
return this._transformCoordinateTree(source, from, to);
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
return this._transformGeoJSON(source, from, to);
|
|
167
|
-
},
|
|
168
|
-
|
|
169
|
-
_transformGeoJSON(geoJSON, fromCRS, toCRS) {
|
|
170
|
-
if (!geoJSON || typeof geoJSON !== 'object') {
|
|
171
|
-
throw new Error('Invalid GeoJSON object.');
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
if (geoJSON.type === 'Feature') {
|
|
175
|
-
return {
|
|
176
|
-
...geoJSON,
|
|
177
|
-
geometry: geoJSON.geometry ? this.transform(geoJSON.geometry, fromCRS, toCRS) : null
|
|
178
|
-
};
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
if (geoJSON.type === 'FeatureCollection') {
|
|
182
|
-
if (!Array.isArray(geoJSON.features)) throw new Error('Invalid FeatureCollection: features must be an array.');
|
|
183
|
-
return {
|
|
184
|
-
...geoJSON,
|
|
185
|
-
features: geoJSON.features.map(feature => this.transform(feature, fromCRS, toCRS))
|
|
186
|
-
};
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
if (geoJSON.type === 'GeometryCollection') {
|
|
190
|
-
if (!Array.isArray(geoJSON.geometries)) throw new Error('Invalid GeometryCollection: geometries must be an array.');
|
|
191
|
-
return {
|
|
192
|
-
...geoJSON,
|
|
193
|
-
geometries: geoJSON.geometries.map(geometry => this.transform(geometry, fromCRS, toCRS))
|
|
194
|
-
};
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
if ('coordinates' in geoJSON) {
|
|
198
|
-
return {
|
|
199
|
-
...geoJSON,
|
|
200
|
-
coordinates: this._transformCoordinateTree(geoJSON.coordinates, fromCRS, toCRS)
|
|
201
|
-
};
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
throw new Error(`Unsupported GeoJSON type: ${geoJSON.type || 'unknown'}.`);
|
|
205
|
-
},
|
|
206
|
-
|
|
207
|
-
_transformCoordinateTree(coordinates, fromCRS, toCRS) {
|
|
208
|
-
if (coordinates == null) return coordinates;
|
|
209
|
-
if (this._isCoord(coordinates)) return this._transformCoordinate(coordinates, fromCRS, toCRS);
|
|
210
|
-
if (!Array.isArray(coordinates)) throw new Error('Invalid coordinates: expected coordinate array.');
|
|
211
|
-
return coordinates.map(item => this._transformCoordinateTree(item, fromCRS, toCRS));
|
|
212
|
-
},
|
|
213
|
-
|
|
214
|
-
_transformCoordinate(coordinate, fromCRS, toCRS) {
|
|
215
|
-
const from = this._normalizeCRS(fromCRS);
|
|
216
|
-
const to = this._normalizeCRS(toCRS);
|
|
217
|
-
const lon = Number(coordinate[0]);
|
|
218
|
-
const lat = Number(coordinate[1]);
|
|
219
|
-
|
|
220
|
-
if (!Number.isFinite(lon) || !Number.isFinite(lat)) {
|
|
221
|
-
throw new Error('Invalid coordinate: longitude and latitude must be finite numbers.');
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
if (from === to) return [lon, lat, ...coordinate.slice(2)];
|
|
225
|
-
|
|
226
|
-
const method = `${from}_${to}`;
|
|
227
|
-
if (typeof GPS[method] !== 'function') {
|
|
228
|
-
throw new Error(`Unsupported CRS transform: ${fromCRS} -> ${toCRS}.`);
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
const transformed = GPS[method](lon, lat);
|
|
232
|
-
return [transformed[0], transformed[1], ...coordinate.slice(2)];
|
|
233
|
-
},
|
|
234
|
-
|
|
235
|
-
_normalizeCRS(value) {
|
|
236
|
-
if (!value) throw new Error('must provide a valid fromCRS and toCRS.');
|
|
237
|
-
|
|
238
|
-
if (typeof value === 'object') {
|
|
239
|
-
if (value.type === 'proj4' && value.properties && value.properties.proj) {
|
|
240
|
-
const crsName = this._toCRS(value.properties.proj);
|
|
241
|
-
if (crsName) return crsName;
|
|
242
|
-
}
|
|
243
|
-
throw new Error('Unsupported CRS object.');
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
const key = String(value).trim().toLowerCase();
|
|
247
|
-
const normalized = CRS_ALIASES[key];
|
|
248
|
-
if (!normalized) throw new Error(`Unsupported CRS: ${value}.`);
|
|
249
|
-
return normalized;
|
|
250
|
-
},
|
|
251
|
-
|
|
252
|
-
_toCRS(proj) {
|
|
253
|
-
for (const key in this.crs) {
|
|
254
|
-
if (proj === this.crs[key]) return key;
|
|
255
|
-
}
|
|
256
|
-
return null;
|
|
257
|
-
},
|
|
258
|
-
|
|
259
|
-
_isCoord(coordinate) {
|
|
260
|
-
return Array.isArray(coordinate) &&
|
|
261
|
-
coordinate.length >= 2 &&
|
|
262
|
-
Number.isFinite(Number(coordinate[0])) &&
|
|
263
|
-
Number.isFinite(Number(coordinate[1])) &&
|
|
264
|
-
!Array.isArray(coordinate[0]) &&
|
|
265
|
-
!Array.isArray(coordinate[1]);
|
|
266
|
-
}
|
|
176
|
+
const crs = {
|
|
177
|
+
bd09ll: '+proj=longlat +datum=BD09',
|
|
178
|
+
gcj02: '+proj=longlat +datum=GCJ02',
|
|
179
|
+
wgs84: '+proj=longlat +datum=WGS84 +no_defs',
|
|
180
|
+
epsg3857: '+proj=merc +a=6378137 +b=6378137 +lat_ts=0 +lon_0=0 +x_0=0 +y_0=0 +k=1 +units=m +nadgrids=@null +wktext +no_defs'
|
|
267
181
|
};
|
|
268
182
|
|
|
269
|
-
const
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
if (
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
if (
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
return
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
183
|
+
const CRS_ALIASES = {
|
|
184
|
+
'bd-09': 'bd09ll',
|
|
185
|
+
'bd09': 'bd09ll',
|
|
186
|
+
'bd09ll': 'bd09ll',
|
|
187
|
+
'bd-09ll': 'bd09ll',
|
|
188
|
+
'epsg:4326': 'wgs84',
|
|
189
|
+
'epsg4326': 'wgs84',
|
|
190
|
+
'4326': 'wgs84',
|
|
191
|
+
'cgcs2000': 'wgs84',
|
|
192
|
+
'cgcs-2000': 'wgs84',
|
|
193
|
+
'epsg:4490': 'wgs84',
|
|
194
|
+
'epsg4490': 'wgs84',
|
|
195
|
+
'4490': 'wgs84',
|
|
196
|
+
'gcj-02': 'gcj02',
|
|
197
|
+
'gcj02': 'gcj02',
|
|
198
|
+
'wgs-84': 'wgs84',
|
|
199
|
+
'wgs84': 'wgs84',
|
|
200
|
+
'epsg:3857': 'epsg3857',
|
|
201
|
+
'epsg3857': 'epsg3857',
|
|
202
|
+
'3857': 'epsg3857',
|
|
203
|
+
'epsg:900913': 'epsg3857',
|
|
204
|
+
'epsg900913': 'epsg3857',
|
|
205
|
+
'900913': 'epsg3857',
|
|
206
|
+
'mercator': 'epsg3857',
|
|
207
|
+
'webmercator': 'epsg3857'
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
const ProjectionTransform = {
|
|
211
|
+
crs,
|
|
212
|
+
|
|
213
|
+
transform(source, fromCRS, toCRS, options = {}) {
|
|
214
|
+
if (source == null) return null;
|
|
215
|
+
|
|
216
|
+
const from = this._normalizeCRS(fromCRS);
|
|
217
|
+
const to = this._normalizeCRS(toCRS);
|
|
218
|
+
const transformCoord = this._getCoordTransformer(from, to, options);
|
|
219
|
+
|
|
220
|
+
if (this._isCoord(source)) {
|
|
221
|
+
return transformCoord(source);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (Array.isArray(source)) {
|
|
225
|
+
return this._transformCoordinateTree(source, transformCoord, options);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return this._transformGeoJSON(source, transformCoord, options);
|
|
229
|
+
},
|
|
230
|
+
|
|
231
|
+
_getCoordTransformer(from, to, options = {}) {
|
|
232
|
+
const isMutate = Boolean(options.mutate);
|
|
233
|
+
const isPrecise = options.precise !== false;
|
|
234
|
+
|
|
235
|
+
if (from === to) {
|
|
236
|
+
return isMutate
|
|
237
|
+
? coord => coord
|
|
238
|
+
: coord => [Number(coord[0]), Number(coord[1]), ...coord.slice(2)];
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
let method;
|
|
242
|
+
if (isPrecise && (from === 'gcj02' || from === 'bd09ll') && to === 'wgs84') {
|
|
243
|
+
method = `${from}_wgs84_precise`;
|
|
244
|
+
} else {
|
|
245
|
+
method = `${from}_${to}`;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const fn = GPS[method];
|
|
249
|
+
if (typeof fn !== 'function') {
|
|
250
|
+
throw new Error(`Unsupported CRS transform: ${from} -> ${to}.`);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (isMutate) {
|
|
254
|
+
return coord => {
|
|
255
|
+
const x = Number(coord[0]);
|
|
256
|
+
const y = Number(coord[1]);
|
|
257
|
+
if (!Number.isFinite(x) || !Number.isFinite(y)) {
|
|
258
|
+
throw new Error('Invalid coordinate: longitude and latitude must be finite numbers.');
|
|
259
|
+
}
|
|
260
|
+
const [tx, ty] = fn.call(GPS, x, y);
|
|
261
|
+
coord[0] = tx;
|
|
262
|
+
coord[1] = ty;
|
|
263
|
+
return coord;
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
return coord => {
|
|
268
|
+
const x = Number(coord[0]);
|
|
269
|
+
const y = Number(coord[1]);
|
|
270
|
+
if (!Number.isFinite(x) || !Number.isFinite(y)) {
|
|
271
|
+
throw new Error('Invalid coordinate: longitude and latitude must be finite numbers.');
|
|
272
|
+
}
|
|
273
|
+
const [tx, ty] = fn.call(GPS, x, y);
|
|
274
|
+
return [tx, ty, ...coord.slice(2)];
|
|
275
|
+
};
|
|
276
|
+
},
|
|
277
|
+
|
|
278
|
+
_transformGeoJSON(geoJSON, transformCoord, options = {}) {
|
|
279
|
+
if (!geoJSON || typeof geoJSON !== 'object') {
|
|
280
|
+
throw new Error('Invalid GeoJSON object.');
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const result = options.mutate ? geoJSON : { ...geoJSON };
|
|
284
|
+
|
|
285
|
+
if (Array.isArray(geoJSON.bbox)) {
|
|
286
|
+
result.bbox = this._transformBbox(geoJSON.bbox, transformCoord);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
if (geoJSON.type === 'Feature') {
|
|
290
|
+
result.geometry = geoJSON.geometry
|
|
291
|
+
? this._transformGeoJSON(geoJSON.geometry, transformCoord, options)
|
|
292
|
+
: null;
|
|
293
|
+
return result;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
if (geoJSON.type === 'FeatureCollection') {
|
|
297
|
+
if (!Array.isArray(geoJSON.features)) {
|
|
298
|
+
throw new Error('Invalid FeatureCollection: features must be an array.');
|
|
299
|
+
}
|
|
300
|
+
if (options.mutate) {
|
|
301
|
+
for (let i = 0; i < geoJSON.features.length; i++) {
|
|
302
|
+
geoJSON.features[i] = this._transformGeoJSON(geoJSON.features[i], transformCoord, options);
|
|
303
|
+
}
|
|
304
|
+
result.features = geoJSON.features;
|
|
305
|
+
} else {
|
|
306
|
+
result.features = geoJSON.features.map(f => this._transformGeoJSON(f, transformCoord, options));
|
|
307
|
+
}
|
|
308
|
+
return result;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
if (geoJSON.type === 'GeometryCollection') {
|
|
312
|
+
if (!Array.isArray(geoJSON.geometries)) {
|
|
313
|
+
throw new Error('Invalid GeometryCollection: geometries must be an array.');
|
|
314
|
+
}
|
|
315
|
+
if (options.mutate) {
|
|
316
|
+
for (let i = 0; i < geoJSON.geometries.length; i++) {
|
|
317
|
+
geoJSON.geometries[i] = geoJSON.geometries[i]
|
|
318
|
+
? this._transformGeoJSON(geoJSON.geometries[i], transformCoord, options)
|
|
319
|
+
: null;
|
|
320
|
+
}
|
|
321
|
+
result.geometries = geoJSON.geometries;
|
|
322
|
+
} else {
|
|
323
|
+
result.geometries = geoJSON.geometries.map(g => (g ? this._transformGeoJSON(g, transformCoord, options) : null));
|
|
324
|
+
}
|
|
325
|
+
return result;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
if ('coordinates' in geoJSON) {
|
|
329
|
+
result.coordinates = this._transformCoordinateTree(geoJSON.coordinates, transformCoord, options);
|
|
330
|
+
return result;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
throw new Error(`Unsupported GeoJSON type: ${geoJSON.type || 'unknown'}.`);
|
|
334
|
+
},
|
|
335
|
+
|
|
336
|
+
_transformCoordinateTree(coordinates, transformCoord, options = {}) {
|
|
337
|
+
if (coordinates == null) return coordinates;
|
|
338
|
+
if (this._isCoord(coordinates)) return transformCoord(coordinates);
|
|
339
|
+
if (!Array.isArray(coordinates)) throw new Error('Invalid coordinates: expected coordinate array.');
|
|
340
|
+
|
|
341
|
+
if (options.mutate) {
|
|
342
|
+
for (let i = 0; i < coordinates.length; i++) {
|
|
343
|
+
coordinates[i] = this._transformCoordinateTree(coordinates[i], transformCoord, options);
|
|
344
|
+
}
|
|
345
|
+
return coordinates;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
return coordinates.map(item => this._transformCoordinateTree(item, transformCoord, options));
|
|
349
|
+
},
|
|
350
|
+
|
|
351
|
+
_transformBbox(bbox, transformCoord) {
|
|
352
|
+
if (!Array.isArray(bbox) || (bbox.length !== 4 && bbox.length !== 6)) {
|
|
353
|
+
return bbox;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
if (bbox.length === 4) {
|
|
357
|
+
const [minX, minY, maxX, maxY] = bbox;
|
|
358
|
+
const p1 = transformCoord([minX, minY]);
|
|
359
|
+
const p2 = transformCoord([maxX, maxY]);
|
|
360
|
+
return [
|
|
361
|
+
Math.min(p1[0], p2[0]),
|
|
362
|
+
Math.min(p1[1], p2[1]),
|
|
363
|
+
Math.max(p1[0], p2[0]),
|
|
364
|
+
Math.max(p1[1], p2[1])
|
|
365
|
+
];
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
const [minX, minY, minZ, maxX, maxY, maxZ] = bbox;
|
|
369
|
+
const p1 = transformCoord([minX, minY, minZ]);
|
|
370
|
+
const p2 = transformCoord([maxX, maxY, maxZ]);
|
|
371
|
+
return [
|
|
372
|
+
Math.min(p1[0], p2[0]),
|
|
373
|
+
Math.min(p1[1], p2[1]),
|
|
374
|
+
minZ,
|
|
375
|
+
Math.max(p1[0], p2[0]),
|
|
376
|
+
Math.max(p1[1], p2[1]),
|
|
377
|
+
maxZ
|
|
378
|
+
];
|
|
379
|
+
},
|
|
380
|
+
|
|
381
|
+
_normalizeCRS(value) {
|
|
382
|
+
if (!value) throw new Error('must provide a valid fromCRS and toCRS.');
|
|
383
|
+
|
|
384
|
+
if (typeof value === 'object') {
|
|
385
|
+
if (value.type === 'proj4' && value.properties && value.properties.proj) {
|
|
386
|
+
const crsName = this._toCRS(value.properties.proj);
|
|
387
|
+
if (crsName) return crsName;
|
|
388
|
+
}
|
|
389
|
+
throw new Error('Unsupported CRS object.');
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
const key = String(value).trim().toLowerCase();
|
|
393
|
+
const normalized = CRS_ALIASES[key];
|
|
394
|
+
if (!normalized) throw new Error(`Unsupported CRS: ${value}.`);
|
|
395
|
+
return normalized;
|
|
396
|
+
},
|
|
397
|
+
|
|
398
|
+
_toCRS(proj) {
|
|
399
|
+
for (const key in this.crs) {
|
|
400
|
+
if (proj === this.crs[key]) return key;
|
|
401
|
+
}
|
|
402
|
+
return null;
|
|
403
|
+
},
|
|
404
|
+
|
|
405
|
+
_isCoord(coordinate) {
|
|
406
|
+
return Array.isArray(coordinate) &&
|
|
407
|
+
coordinate.length >= 2 &&
|
|
408
|
+
!Array.isArray(coordinate[0]) &&
|
|
409
|
+
!Array.isArray(coordinate[1]) &&
|
|
410
|
+
Number.isFinite(Number(coordinate[0])) &&
|
|
411
|
+
Number.isFinite(Number(coordinate[1]));
|
|
412
|
+
}
|
|
413
|
+
};
|
|
414
|
+
|
|
415
|
+
const TYPE_MAP = {
|
|
416
|
+
POINT: 'Point',
|
|
417
|
+
LINESTRING: 'LineString',
|
|
418
|
+
POLYGON: 'Polygon',
|
|
419
|
+
MULTIPOINT: 'MultiPoint',
|
|
420
|
+
MULTILINESTRING: 'MultiLineString',
|
|
421
|
+
MULTIPOLYGON: 'MultiPolygon',
|
|
422
|
+
GEOMETRYCOLLECTION: 'GeometryCollection'
|
|
423
|
+
};
|
|
424
|
+
|
|
425
|
+
const REVERSE_TYPE_MAP = Object.fromEntries(
|
|
426
|
+
Object.entries(TYPE_MAP).map(([wktType, geoJSONType]) => [geoJSONType, wktType])
|
|
427
|
+
);
|
|
428
|
+
|
|
429
|
+
const WKT = {
|
|
430
|
+
parse(wkt) {
|
|
431
|
+
if (typeof wkt !== 'string' || !wkt.trim()) {
|
|
432
|
+
throw new Error('Invalid WKT string.');
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
const source = wkt.trim();
|
|
436
|
+
const header = source.match(/^([A-Za-z]+)(?:\s+(ZM|Z|M))?\s*/i);
|
|
437
|
+
if (!header) throw new Error('Invalid WKT string.');
|
|
438
|
+
|
|
439
|
+
const wktType = header[1].toUpperCase();
|
|
440
|
+
const geoJSONType = TYPE_MAP[wktType];
|
|
441
|
+
if (!geoJSONType) throw new Error(`Unsupported WKT type: ${wktType}.`);
|
|
442
|
+
|
|
443
|
+
const body = source.slice(header[0].length).trim();
|
|
444
|
+
if (/^EMPTY$/i.test(body)) {
|
|
445
|
+
if (geoJSONType === 'GeometryCollection') {
|
|
446
|
+
return { type: geoJSONType, geometries: [] };
|
|
447
|
+
}
|
|
448
|
+
return { type: geoJSONType, coordinates: this._emptyCoordinates(geoJSONType) };
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
if (!body.startsWith('(') || !body.endsWith(')')) {
|
|
452
|
+
throw new Error('Invalid WKT string: missing coordinate parentheses.');
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
const content = this._stripOuterParens(body);
|
|
456
|
+
|
|
457
|
+
if (geoJSONType === 'GeometryCollection') {
|
|
458
|
+
return {
|
|
459
|
+
type: 'GeometryCollection',
|
|
460
|
+
geometries: this._splitTopLevel(content).map(item => this.parse(item))
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
return {
|
|
465
|
+
type: geoJSONType,
|
|
466
|
+
coordinates: this._parseCoordinates(content, geoJSONType)
|
|
467
|
+
};
|
|
468
|
+
},
|
|
469
|
+
|
|
470
|
+
stringify(geoJSON, options = {}) {
|
|
471
|
+
if (!geoJSON || typeof geoJSON !== 'object' || !geoJSON.type) {
|
|
472
|
+
throw new Error('Invalid GeoJSON.');
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
const wktType = REVERSE_TYPE_MAP[geoJSON.type];
|
|
476
|
+
if (!wktType) throw new Error(`Unsupported geometry type: ${geoJSON.type}.`);
|
|
477
|
+
|
|
478
|
+
const precision = typeof options === 'number' ? options : options?.precision;
|
|
479
|
+
|
|
480
|
+
if (geoJSON.type === 'GeometryCollection') {
|
|
481
|
+
if (!Array.isArray(geoJSON.geometries) || geoJSON.geometries.length === 0) {
|
|
482
|
+
return 'GEOMETRYCOLLECTION EMPTY';
|
|
483
|
+
}
|
|
484
|
+
return `GEOMETRYCOLLECTION(${geoJSON.geometries.map(geom => this.stringify(geom, options)).join(', ')})`;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
const coords = geoJSON.coordinates;
|
|
488
|
+
if (this._isEmpty(coords)) return `${wktType} EMPTY`;
|
|
489
|
+
|
|
490
|
+
return `${wktType}(${this._stringifyCoordinates(coords, geoJSON.type, precision)})`;
|
|
491
|
+
},
|
|
492
|
+
|
|
493
|
+
_parseCoordinates(content, type) {
|
|
494
|
+
switch (type) {
|
|
495
|
+
case 'Point':
|
|
496
|
+
return this._parsePoint(content);
|
|
497
|
+
case 'LineString':
|
|
498
|
+
return this._parsePointList(content);
|
|
499
|
+
case 'Polygon':
|
|
500
|
+
return this._parsePolygon(content);
|
|
501
|
+
case 'MultiPoint':
|
|
502
|
+
return this._parseMultiPoint(content);
|
|
503
|
+
case 'MultiLineString':
|
|
504
|
+
return this._splitTopLevel(content).map(line => this._parsePointList(this._stripOptionalOuterParens(line)));
|
|
505
|
+
case 'MultiPolygon':
|
|
506
|
+
return this._splitTopLevel(content).map(poly => this._parsePolygon(this._stripOptionalOuterParens(poly)));
|
|
507
|
+
default:
|
|
508
|
+
throw new Error(`Unsupported geometry type: ${type}.`);
|
|
509
|
+
}
|
|
510
|
+
},
|
|
511
|
+
|
|
512
|
+
_parsePoint(value) {
|
|
513
|
+
const numbers = value.trim().split(/\s+/).filter(Boolean).map(Number);
|
|
514
|
+
if (numbers.length < 2 || numbers.some(n => !Number.isFinite(n))) {
|
|
515
|
+
throw new Error(`Invalid WKT coordinate: ${value}.`);
|
|
516
|
+
}
|
|
517
|
+
return numbers;
|
|
518
|
+
},
|
|
519
|
+
|
|
520
|
+
_parsePointList(value) {
|
|
521
|
+
const points = this._splitTopLevel(value).map(part => this._parsePoint(this._stripOptionalOuterParens(part)));
|
|
522
|
+
if (!points.length) throw new Error('Invalid WKT coordinate list.');
|
|
523
|
+
return points;
|
|
524
|
+
},
|
|
525
|
+
|
|
526
|
+
_parsePolygon(value) {
|
|
527
|
+
const rings = this._splitTopLevel(value).map(ring => this._parsePointList(this._stripOptionalOuterParens(ring)));
|
|
528
|
+
if (!rings.length) throw new Error('Invalid WKT polygon.');
|
|
529
|
+
return rings;
|
|
530
|
+
},
|
|
531
|
+
|
|
532
|
+
_parseMultiPoint(value) {
|
|
533
|
+
return this._splitTopLevel(value).map(point => this._parsePoint(this._stripOptionalOuterParens(point)));
|
|
534
|
+
},
|
|
535
|
+
|
|
536
|
+
_splitTopLevel(value) {
|
|
537
|
+
const parts = [];
|
|
538
|
+
let depth = 0;
|
|
539
|
+
let start = 0;
|
|
540
|
+
|
|
541
|
+
for (let i = 0; i < value.length; i++) {
|
|
542
|
+
const char = value[i];
|
|
543
|
+
if (char === '(') depth++;
|
|
544
|
+
else if (char === ')') depth--;
|
|
545
|
+
|
|
546
|
+
if (depth < 0) throw new Error('Invalid WKT string: unbalanced parentheses.');
|
|
547
|
+
|
|
548
|
+
if (char === ',' && depth === 0) {
|
|
549
|
+
parts.push(value.slice(start, i).trim());
|
|
550
|
+
start = i + 1;
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
if (depth !== 0) throw new Error('Invalid WKT string: unbalanced parentheses.');
|
|
555
|
+
const last = value.slice(start).trim();
|
|
556
|
+
if (last) parts.push(last);
|
|
557
|
+
return parts;
|
|
558
|
+
},
|
|
559
|
+
|
|
560
|
+
_stripOuterParens(value) {
|
|
561
|
+
const trimmed = value.trim();
|
|
562
|
+
if (!trimmed.startsWith('(') || !trimmed.endsWith(')')) {
|
|
563
|
+
throw new Error('Invalid WKT string: missing coordinate parentheses.');
|
|
564
|
+
}
|
|
565
|
+
return trimmed.slice(1, -1).trim();
|
|
566
|
+
},
|
|
567
|
+
|
|
568
|
+
_stripOptionalOuterParens(value) {
|
|
569
|
+
const trimmed = value.trim();
|
|
570
|
+
if (!trimmed.startsWith('(')) return trimmed;
|
|
571
|
+
return this._stripOuterParens(trimmed);
|
|
572
|
+
},
|
|
573
|
+
|
|
574
|
+
_stringifyCoordinates(coords, type, precision) {
|
|
575
|
+
switch (type) {
|
|
576
|
+
case 'Point':
|
|
577
|
+
return this._stringifyPoint(coords, precision);
|
|
578
|
+
case 'LineString':
|
|
579
|
+
return this._stringifyPointList(coords, precision);
|
|
580
|
+
case 'Polygon':
|
|
581
|
+
return this._stringifyPolygon(coords, precision);
|
|
582
|
+
case 'MultiPoint':
|
|
583
|
+
return coords.map(point => `(${this._stringifyPoint(point, precision)})`).join(', ');
|
|
584
|
+
case 'MultiLineString':
|
|
585
|
+
return coords.map(line => `(${this._stringifyPointList(line, precision)})`).join(', ');
|
|
586
|
+
case 'MultiPolygon':
|
|
587
|
+
return coords.map(poly => `(${this._stringifyPolygon(poly, precision)})`).join(', ');
|
|
588
|
+
default:
|
|
589
|
+
throw new Error(`Unsupported geometry type: ${type}.`);
|
|
590
|
+
}
|
|
591
|
+
},
|
|
592
|
+
|
|
593
|
+
_stringifyPoint(point, precision) {
|
|
594
|
+
if (!Array.isArray(point) || point.length < 2) throw new Error('Invalid coordinate.');
|
|
595
|
+
return point.map(value => {
|
|
596
|
+
const number = Number(value);
|
|
597
|
+
if (!Number.isFinite(number)) throw new Error('Invalid coordinate value.');
|
|
598
|
+
if (typeof precision === 'number' && precision >= 0) {
|
|
599
|
+
return Number(number.toFixed(precision)).toString();
|
|
600
|
+
}
|
|
601
|
+
return String(number);
|
|
602
|
+
}).join(' ');
|
|
603
|
+
},
|
|
604
|
+
|
|
605
|
+
_stringifyPointList(points, precision) {
|
|
606
|
+
if (!Array.isArray(points)) throw new Error('Invalid coordinate list.');
|
|
607
|
+
return points.map(point => this._stringifyPoint(point, precision)).join(', ');
|
|
608
|
+
},
|
|
609
|
+
|
|
610
|
+
_stringifyPolygon(rings, precision) {
|
|
611
|
+
if (!Array.isArray(rings)) throw new Error('Invalid polygon coordinates.');
|
|
612
|
+
return rings.map(ring => `(${this._stringifyPointList(ring, precision)})`).join(', ');
|
|
613
|
+
},
|
|
614
|
+
|
|
615
|
+
_emptyCoordinates(type) {
|
|
616
|
+
return [];
|
|
617
|
+
},
|
|
618
|
+
|
|
619
|
+
_isEmpty(coords) {
|
|
620
|
+
return Array.isArray(coords) && coords.length === 0;
|
|
621
|
+
}
|
|
452
622
|
};
|
|
453
623
|
|
|
454
624
|
const giserConvertCrs = {
|
|
@@ -456,32 +626,34 @@ const giserConvertCrs = {
|
|
|
456
626
|
ProjectionTransform,
|
|
457
627
|
WKT,
|
|
458
628
|
|
|
459
|
-
transform(input, fromCRS, toCRS) {
|
|
629
|
+
transform(input, fromCRS, toCRS, options) {
|
|
460
630
|
if (input == null || input === '') return null;
|
|
461
631
|
|
|
462
632
|
if (typeof input === 'string') {
|
|
463
633
|
const geoJSON = WKT.parse(input);
|
|
464
|
-
|
|
634
|
+
const transformed = ProjectionTransform.transform(geoJSON, fromCRS, toCRS, options);
|
|
635
|
+
return WKT.stringify(transformed, options);
|
|
465
636
|
}
|
|
466
637
|
|
|
467
|
-
return ProjectionTransform.transform(input, fromCRS, toCRS);
|
|
638
|
+
return ProjectionTransform.transform(input, fromCRS, toCRS, options);
|
|
468
639
|
},
|
|
469
640
|
|
|
470
|
-
transformPoint(coord, fromCRS, toCRS) {
|
|
471
|
-
return this.transform(coord, fromCRS, toCRS);
|
|
641
|
+
transformPoint(coord, fromCRS, toCRS, options) {
|
|
642
|
+
return this.transform(coord, fromCRS, toCRS, options);
|
|
472
643
|
},
|
|
473
644
|
|
|
474
|
-
transformPoints(points, fromCRS, toCRS) {
|
|
475
|
-
return this.transform(points, fromCRS, toCRS);
|
|
645
|
+
transformPoints(points, fromCRS, toCRS, options) {
|
|
646
|
+
return this.transform(points, fromCRS, toCRS, options);
|
|
476
647
|
},
|
|
477
648
|
|
|
478
|
-
transformGeoJSON(geoJSON, fromCRS, toCRS) {
|
|
479
|
-
return this.transform(geoJSON, fromCRS, toCRS);
|
|
649
|
+
transformGeoJSON(geoJSON, fromCRS, toCRS, options) {
|
|
650
|
+
return this.transform(geoJSON, fromCRS, toCRS, options);
|
|
480
651
|
},
|
|
481
652
|
|
|
482
|
-
transformWKT(wkt, fromCRS, toCRS) {
|
|
483
|
-
return this.transform(wkt, fromCRS, toCRS);
|
|
653
|
+
transformWKT(wkt, fromCRS, toCRS, options) {
|
|
654
|
+
return this.transform(wkt, fromCRS, toCRS, options);
|
|
484
655
|
}
|
|
485
656
|
};
|
|
486
657
|
|
|
487
658
|
export { GPS, ProjectionTransform, WKT, giserConvertCrs as default };
|
|
659
|
+
//# sourceMappingURL=giserConvertCrs.esm.js.map
|