giser-convert-crs 1.0.1 → 1.0.2

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.
@@ -1,487 +0,0 @@
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 GPS = {
7
- PI,
8
- x_pi: X_PI,
9
-
10
- delta(lat, lon) {
11
- let dLat = this.transformLat(lon - 105.0, lat - 35.0);
12
- let dLon = this.transformLon(lon - 105.0, lat - 35.0);
13
- const radLat = lat / 180.0 * PI;
14
- const sinLat = Math.sin(radLat);
15
- const magic = 1 - ECCENTRICITY_SQUARED * sinLat * sinLat;
16
- const sqrtMagic = Math.sqrt(magic);
17
-
18
- dLat = (dLat * 180.0) / ((SEMI_MAJOR_AXIS * (1 - ECCENTRICITY_SQUARED)) / (magic * sqrtMagic) * PI);
19
- dLon = (dLon * 180.0) / (SEMI_MAJOR_AXIS / sqrtMagic * Math.cos(radLat) * PI);
20
-
21
- return { lat: dLat, lon: dLon };
22
- },
23
-
24
- wgs84_gcj02(wgsLon, wgsLat) {
25
- this._assertLonLat(wgsLon, wgsLat);
26
- if (this.outOfChina(wgsLat, wgsLon)) return [wgsLon, wgsLat];
27
-
28
- const d = this.delta(wgsLat, wgsLon);
29
- return [wgsLon + d.lon, wgsLat + d.lat];
30
- },
31
-
32
- gcj02_wgs84(gcjLon, gcjLat) {
33
- this._assertLonLat(gcjLon, gcjLat);
34
- if (this.outOfChina(gcjLat, gcjLon)) return [gcjLon, gcjLat];
35
-
36
- const d = this.delta(gcjLat, gcjLon);
37
- return [gcjLon - d.lon, gcjLat - d.lat];
38
- },
39
-
40
- gcj02_wgs84_precise(gcjLon, gcjLat) {
41
- this._assertLonLat(gcjLon, gcjLat);
42
- if (this.outOfChina(gcjLat, gcjLon)) return [gcjLon, gcjLat];
43
-
44
- const threshold = 0.000000001;
45
- let minLat = gcjLat - 0.01;
46
- let minLon = gcjLon - 0.01;
47
- let maxLat = gcjLat + 0.01;
48
- let maxLon = gcjLon + 0.01;
49
- let wgsLat = gcjLat;
50
- let wgsLon = gcjLon;
51
-
52
- for (let i = 0; i < 10000; i++) {
53
- wgsLat = (minLat + maxLat) / 2;
54
- wgsLon = (minLon + maxLon) / 2;
55
- const [tmpLon, tmpLat] = this.wgs84_gcj02(wgsLon, wgsLat);
56
- const dLat = tmpLat - gcjLat;
57
- const dLon = tmpLon - gcjLon;
58
-
59
- if (Math.abs(dLat) < threshold && Math.abs(dLon) < threshold) break;
60
- if (dLat > 0) maxLat = wgsLat;
61
- else minLat = wgsLat;
62
- if (dLon > 0) maxLon = wgsLon;
63
- else minLon = wgsLon;
64
- }
65
-
66
- return [wgsLon, wgsLat];
67
- },
68
-
69
- gcj02_bd09ll(gcjLon, gcjLat) {
70
- this._assertLonLat(gcjLon, gcjLat);
71
- const z = Math.sqrt(gcjLon * gcjLon + gcjLat * gcjLat) + 0.00002 * Math.sin(gcjLat * X_PI);
72
- const theta = Math.atan2(gcjLat, gcjLon) + 0.000003 * Math.cos(gcjLon * X_PI);
73
- return [z * Math.cos(theta) + 0.0065, z * Math.sin(theta) + 0.006];
74
- },
75
-
76
- bd09ll_gcj02(bdLon, bdLat) {
77
- this._assertLonLat(bdLon, bdLat);
78
- const x = bdLon - 0.0065;
79
- const y = bdLat - 0.006;
80
- const z = Math.sqrt(x * x + y * y) - 0.00002 * Math.sin(y * X_PI);
81
- const theta = Math.atan2(y, x) - 0.000003 * Math.cos(x * X_PI);
82
- return [z * Math.cos(theta), z * Math.sin(theta)];
83
- },
84
-
85
- wgs84_bd09ll(wgsLon, wgsLat) {
86
- const gcj = this.wgs84_gcj02(wgsLon, wgsLat);
87
- return this.gcj02_bd09ll(gcj[0], gcj[1]);
88
- },
89
-
90
- bd09ll_wgs84(bdLon, bdLat) {
91
- const gcj = this.bd09ll_gcj02(bdLon, bdLat);
92
- return this.gcj02_wgs84(gcj[0], gcj[1]);
93
- },
94
-
95
- bd09ll_wgs84_precise(bdLon, bdLat) {
96
- const gcj = this.bd09ll_gcj02(bdLon, bdLat);
97
- return this.gcj02_wgs84_precise(gcj[0], gcj[1]);
98
- },
99
-
100
- outOfChina(lat, lon) {
101
- return lon < 72.004 || lon > 137.8347 || lat < 0.8293 || lat > 55.8271;
102
- },
103
-
104
- transformLat(x, y) {
105
- let ret = -100 + 2.0 * x + 3.0 * y + 0.2 * y * y + 0.1 * x * y + 0.2 * Math.sqrt(Math.abs(x));
106
- ret += (20.0 * Math.sin(6.0 * x * PI) + 20.0 * Math.sin(2.0 * x * PI)) * 2.0 / 3.0;
107
- ret += (20.0 * Math.sin(y * PI) + 40.0 * Math.sin(y / 3.0 * PI)) * 2.0 / 3.0;
108
- ret += (160.0 * Math.sin(y / 12.0 * PI) + 320.0 * Math.sin(y * PI / 30.0)) * 2.0 / 3.0;
109
- return ret;
110
- },
111
-
112
- transformLon(x, y) {
113
- let ret = 300.0 + x + 2.0 * y + 0.1 * x * x + 0.1 * x * y + 0.1 * Math.sqrt(Math.abs(x));
114
- ret += (20.0 * Math.sin(6.0 * x * PI) + 20.0 * Math.sin(2.0 * x * PI)) * 2.0 / 3.0;
115
- ret += (20.0 * Math.sin(x * PI) + 40.0 * Math.sin(x / 3.0 * PI)) * 2.0 / 3.0;
116
- ret += (150.0 * Math.sin(x / 12.0 * PI) + 300.0 * Math.sin(x / 30.0 * PI)) * 2.0 / 3.0;
117
- return ret;
118
- },
119
-
120
- gcj_encrypt(wgsLat, wgsLon) {
121
- const [lon, lat] = this.wgs84_gcj02(wgsLon, wgsLat);
122
- return { lat, lon };
123
- },
124
-
125
- _assertLonLat(lon, lat) {
126
- if (!Number.isFinite(lon) || !Number.isFinite(lat)) {
127
- throw new Error('Invalid coordinate: longitude and latitude must be finite numbers.');
128
- }
129
- }
130
- };
131
-
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
- }
267
- };
268
-
269
- const TYPE_MAP = {
270
- POINT: 'Point',
271
- LINESTRING: 'LineString',
272
- POLYGON: 'Polygon',
273
- MULTIPOINT: 'MultiPoint',
274
- MULTILINESTRING: 'MultiLineString',
275
- MULTIPOLYGON: 'MultiPolygon'
276
- };
277
-
278
- const REVERSE_TYPE_MAP = Object.fromEntries(
279
- Object.entries(TYPE_MAP).map(([wktType, geoJSONType]) => [geoJSONType, wktType])
280
- );
281
-
282
- const WKT = {
283
- parse(wkt) {
284
- if (typeof wkt !== 'string' || !wkt.trim()) {
285
- throw new Error('Invalid WKT string.');
286
- }
287
-
288
- const source = wkt.trim();
289
- const header = source.match(/^([A-Za-z]+)(?:\s+(ZM|Z|M))?\s*/i);
290
- if (!header) throw new Error('Invalid WKT string.');
291
-
292
- const wktType = header[1].toUpperCase();
293
- const geoJSONType = TYPE_MAP[wktType];
294
- if (!geoJSONType) throw new Error(`Unsupported WKT type: ${wktType}.`);
295
-
296
- const body = source.slice(header[0].length).trim();
297
- if (/^EMPTY$/i.test(body)) {
298
- return { type: geoJSONType, coordinates: this._emptyCoordinates(geoJSONType) };
299
- }
300
-
301
- if (!body.startsWith('(') || !body.endsWith(')')) {
302
- throw new Error('Invalid WKT string: missing coordinate parentheses.');
303
- }
304
-
305
- const content = this._stripOuterParens(body);
306
- return {
307
- type: geoJSONType,
308
- coordinates: this._parseCoordinates(content, geoJSONType)
309
- };
310
- },
311
-
312
- stringify(geoJSON) {
313
- if (!geoJSON || typeof geoJSON !== 'object' || !geoJSON.type) {
314
- throw new Error('Invalid GeoJSON.');
315
- }
316
-
317
- const wktType = REVERSE_TYPE_MAP[geoJSON.type];
318
- if (!wktType) throw new Error(`Unsupported geometry type: ${geoJSON.type}.`);
319
-
320
- const coords = geoJSON.coordinates;
321
- if (this._isEmpty(coords)) return `${wktType} EMPTY`;
322
-
323
- return `${wktType}(${this._stringifyCoordinates(coords, geoJSON.type)})`;
324
- },
325
-
326
- _parseCoordinates(content, type) {
327
- switch (type) {
328
- case 'Point':
329
- return this._parsePoint(content);
330
- case 'LineString':
331
- return this._parsePointList(content);
332
- case 'Polygon':
333
- return this._parsePolygon(content);
334
- case 'MultiPoint':
335
- return this._parseMultiPoint(content);
336
- case 'MultiLineString':
337
- return this._splitTopLevel(content).map(line => this._parsePointList(this._stripOptionalOuterParens(line)));
338
- case 'MultiPolygon':
339
- return this._splitTopLevel(content).map(poly => this._parsePolygon(this._stripOptionalOuterParens(poly)));
340
- default:
341
- throw new Error(`Unsupported geometry type: ${type}.`);
342
- }
343
- },
344
-
345
- _parsePoint(value) {
346
- const numbers = value.trim().split(/\s+/).filter(Boolean).map(Number);
347
- if (numbers.length < 2 || numbers.some(n => !Number.isFinite(n))) {
348
- throw new Error(`Invalid WKT coordinate: ${value}.`);
349
- }
350
- return numbers;
351
- },
352
-
353
- _parsePointList(value) {
354
- const points = this._splitTopLevel(value).map(part => this._parsePoint(this._stripOptionalOuterParens(part)));
355
- if (!points.length) throw new Error('Invalid WKT coordinate list.');
356
- return points;
357
- },
358
-
359
- _parsePolygon(value) {
360
- const rings = this._splitTopLevel(value).map(ring => this._parsePointList(this._stripOptionalOuterParens(ring)));
361
- if (!rings.length) throw new Error('Invalid WKT polygon.');
362
- return rings;
363
- },
364
-
365
- _parseMultiPoint(value) {
366
- return this._splitTopLevel(value).map(point => this._parsePoint(this._stripOptionalOuterParens(point)));
367
- },
368
-
369
- _splitTopLevel(value) {
370
- const parts = [];
371
- let depth = 0;
372
- let start = 0;
373
-
374
- for (let i = 0; i < value.length; i++) {
375
- const char = value[i];
376
- if (char === '(') depth++;
377
- else if (char === ')') depth--;
378
-
379
- if (depth < 0) throw new Error('Invalid WKT string: unbalanced parentheses.');
380
-
381
- if (char === ',' && depth === 0) {
382
- parts.push(value.slice(start, i).trim());
383
- start = i + 1;
384
- }
385
- }
386
-
387
- if (depth !== 0) throw new Error('Invalid WKT string: unbalanced parentheses.');
388
- const last = value.slice(start).trim();
389
- if (last) parts.push(last);
390
- return parts;
391
- },
392
-
393
- _stripOuterParens(value) {
394
- const trimmed = value.trim();
395
- if (!trimmed.startsWith('(') || !trimmed.endsWith(')')) {
396
- throw new Error('Invalid WKT string: missing coordinate parentheses.');
397
- }
398
- return trimmed.slice(1, -1).trim();
399
- },
400
-
401
- _stripOptionalOuterParens(value) {
402
- const trimmed = value.trim();
403
- if (!trimmed.startsWith('(')) return trimmed;
404
- return this._stripOuterParens(trimmed);
405
- },
406
-
407
- _stringifyCoordinates(coords, type) {
408
- switch (type) {
409
- case 'Point':
410
- return this._stringifyPoint(coords);
411
- case 'LineString':
412
- return this._stringifyPointList(coords);
413
- case 'Polygon':
414
- return this._stringifyPolygon(coords);
415
- case 'MultiPoint':
416
- return coords.map(point => `(${this._stringifyPoint(point)})`).join(', ');
417
- case 'MultiLineString':
418
- return coords.map(line => `(${this._stringifyPointList(line)})`).join(', ');
419
- case 'MultiPolygon':
420
- return coords.map(poly => `(${this._stringifyPolygon(poly)})`).join(', ');
421
- default:
422
- throw new Error(`Unsupported geometry type: ${type}.`);
423
- }
424
- },
425
-
426
- _stringifyPoint(point) {
427
- if (!Array.isArray(point) || point.length < 2) throw new Error('Invalid coordinate.');
428
- return point.map(value => {
429
- const number = Number(value);
430
- if (!Number.isFinite(number)) throw new Error('Invalid coordinate value.');
431
- return String(number);
432
- }).join(' ');
433
- },
434
-
435
- _stringifyPointList(points) {
436
- if (!Array.isArray(points)) throw new Error('Invalid coordinate list.');
437
- return points.map(point => this._stringifyPoint(point)).join(', ');
438
- },
439
-
440
- _stringifyPolygon(rings) {
441
- if (!Array.isArray(rings)) throw new Error('Invalid polygon coordinates.');
442
- return rings.map(ring => `(${this._stringifyPointList(ring)})`).join(', ');
443
- },
444
-
445
- _emptyCoordinates(type) {
446
- return type === 'Point' ? [] : [];
447
- },
448
-
449
- _isEmpty(coords) {
450
- return Array.isArray(coords) && coords.length === 0;
451
- }
452
- };
453
-
454
- const geometryCrs = {
455
- GPS,
456
- ProjectionTransform,
457
- WKT,
458
-
459
- transform(input, fromCRS, toCRS) {
460
- if (input == null || input === '') return null;
461
-
462
- if (typeof input === 'string') {
463
- const geoJSON = WKT.parse(input);
464
- return WKT.stringify(ProjectionTransform.transform(geoJSON, fromCRS, toCRS));
465
- }
466
-
467
- return ProjectionTransform.transform(input, fromCRS, toCRS);
468
- },
469
-
470
- transformPoint(coord, fromCRS, toCRS) {
471
- return this.transform(coord, fromCRS, toCRS);
472
- },
473
-
474
- transformPoints(points, fromCRS, toCRS) {
475
- return this.transform(points, fromCRS, toCRS);
476
- },
477
-
478
- transformGeoJSON(geoJSON, fromCRS, toCRS) {
479
- return this.transform(geoJSON, fromCRS, toCRS);
480
- },
481
-
482
- transformWKT(wkt, fromCRS, toCRS) {
483
- return this.transform(wkt, fromCRS, toCRS);
484
- }
485
- };
486
-
487
- export { GPS, ProjectionTransform, WKT, geometryCrs as default };