@itowns/geographic 2.46.1-next.6 → 2.46.1-next.60

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.
@@ -8,6 +8,9 @@ const axis = /* @__PURE__ */new Vector3().set(0, 0, 1);
8
8
  const coord = /* @__PURE__ */new Coordinates('EPSG:4326', 0, 0, 0);
9
9
  const euler = /* @__PURE__ */new Euler();
10
10
  const quat = /* @__PURE__ */new Quaternion();
11
+
12
+ // To unify with Crs.js ProjectionLike type ?
13
+
11
14
  /**
12
15
  * The transform from the platform frame to the local East, North, Up (ENU)
13
16
  * frame is `RotationZ(heading).RotationX(pitch).RotationY(roll)`.
@@ -19,11 +22,7 @@ const quat = /* @__PURE__ */new Quaternion();
19
22
  *
20
23
  * @returns The target quaternion
21
24
  */
22
- export function quaternionFromRollPitchHeading() {
23
- let roll = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0;
24
- let pitch = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
25
- let heading = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0;
26
- let target = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : new Quaternion();
25
+ export function quaternionFromRollPitchHeading(roll = 0, pitch = 0, heading = 0, target = new Quaternion()) {
27
26
  roll *= MathUtils.DEG2RAD;
28
27
  pitch *= MathUtils.DEG2RAD;
29
28
  heading *= MathUtils.DEG2RAD;
@@ -53,11 +52,7 @@ export function quaternionFromRollPitchHeading() {
53
52
  *
54
53
  * @returns The target quaternion
55
54
  */
56
- export function quaternionFromOmegaPhiKappa() {
57
- let omega = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0;
58
- let phi = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
59
- let kappa = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0;
60
- let target = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : new Quaternion();
55
+ export function quaternionFromOmegaPhiKappa(omega = 0, phi = 0, kappa = 0, target = new Quaternion()) {
61
56
  omega *= MathUtils.DEG2RAD;
62
57
  phi *= MathUtils.DEG2RAD;
63
58
  kappa *= MathUtils.DEG2RAD;
@@ -76,8 +71,7 @@ export function quaternionFromOmegaPhiKappa() {
76
71
  *
77
72
  * @returns The target quaternion
78
73
  */
79
- export function quaternionFromAttitude(attitude) {
80
- let target = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : new Quaternion();
74
+ export function quaternionFromAttitude(attitude, target = new Quaternion()) {
81
75
  if ('roll' in attitude || 'pitch' in attitude || 'heading' in attitude) {
82
76
  return quaternionFromRollPitchHeading(attitude.roll, attitude.pitch, attitude.heading, target);
83
77
  }
@@ -97,13 +91,11 @@ export function quaternionFromAttitude(attitude) {
97
91
  * @returns The target quaternion if coordinates is defined. Otherwise, a
98
92
  * function to compute it from coordinates.
99
93
  */
100
- export function quaternionFromEnuToGeocent(coordinates) {
101
- let target = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : new Quaternion();
94
+ export function quaternionFromEnuToGeocent(coordinates, target = new Quaternion()) {
102
95
  if (coordinates) {
103
96
  return quaternionFromEnuToGeocent()(coordinates, target);
104
97
  }
105
- return function (coordinates) {
106
- let target = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : new Quaternion();
98
+ return (coordinates, target = new Quaternion()) => {
107
99
  const up = coordinates.geodesicNormal;
108
100
  if (up.x == 0 && up.y == 0) {
109
101
  return target.set(0, 0, 0, 1);
@@ -127,16 +119,12 @@ export function quaternionFromEnuToGeocent(coordinates) {
127
119
  * @returns The target quaternion if coordinates is defined. Otherwise, a
128
120
  * function to compute it from coordinates.
129
121
  */
130
- export function quaternionFromGeocentToEnu(coordinates) {
131
- let target = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : new Quaternion();
122
+ export function quaternionFromGeocentToEnu(coordinates, target = new Quaternion()) {
132
123
  if (coordinates) {
133
124
  return quaternionFromGeocentToEnu()(coordinates, target);
134
125
  }
135
126
  const toGeocent = quaternionFromEnuToGeocent();
136
- return function (coordinates) {
137
- let target = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : new Quaternion();
138
- return toGeocent(coordinates, target).conjugate();
139
- };
127
+ return (coordinates, target = new Quaternion()) => toGeocent(coordinates, target).conjugate();
140
128
  }
141
129
  /**
142
130
  * Computes the rotation from a Lambert Conformal Conic (LCC) frame to the local
@@ -152,14 +140,12 @@ export function quaternionFromGeocentToEnu(coordinates) {
152
140
  * @returns The target quaternion if coordinates is defined. Otherwise, a
153
141
  * function to compute it from coordinates.
154
142
  */
155
- export function quaternionFromLCCToEnu(proj, coordinates) {
156
- let target = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : new Quaternion();
143
+ export function quaternionFromLCCToEnu(proj, coordinates, target = new Quaternion()) {
157
144
  if (coordinates) {
158
145
  return quaternionFromLCCToEnu(proj)(coordinates, target);
159
146
  }
160
147
  const sinlat0 = Math.sin(proj.lat0);
161
- return function (coordinates) {
162
- let target = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : new Quaternion();
148
+ return (coordinates, target = new Quaternion()) => {
163
149
  const long = coordinates.as(coord.crs, coord).longitude * MathUtils.DEG2RAD;
164
150
  return target.setFromAxisAngle(axis, sinlat0 * (proj.long0 - long));
165
151
  };
@@ -177,16 +163,12 @@ export function quaternionFromLCCToEnu(proj, coordinates) {
177
163
  * @returns The target quaternion if coordinates is defined. Otherwise, a
178
164
  * function to compute it from coordinates.
179
165
  */
180
- export function quaternionFromEnuToLCC(proj, coordinates) {
181
- let target = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : new Quaternion();
166
+ export function quaternionFromEnuToLCC(proj, coordinates, target = new Quaternion()) {
182
167
  if (coordinates) {
183
168
  return quaternionFromEnuToLCC(proj)(coordinates, target);
184
169
  }
185
170
  const fromLCC = quaternionFromLCCToEnu(proj);
186
- return function (coordinates) {
187
- let target = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : new Quaternion();
188
- return fromLCC(coordinates, target).conjugate();
189
- };
171
+ return (coordinates, target = new Quaternion()) => fromLCC(coordinates, target).conjugate();
190
172
  }
191
173
  /**
192
174
  * Computes the rotation from a Transverse Mercator frame (TMerc) to the
@@ -201,8 +183,7 @@ export function quaternionFromEnuToLCC(proj, coordinates) {
201
183
  * @returns The target quaternion if coordinates is defined. Otherwise, a
202
184
  * function to compute it from coordinates.
203
185
  */
204
- export function quaternionFromTMercToEnu(proj, coordinates) {
205
- let target = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : new Quaternion();
186
+ export function quaternionFromTMercToEnu(proj, coordinates, target = new Quaternion()) {
206
187
  if (coordinates) {
207
188
  return quaternionFromTMercToEnu(proj)(coordinates, target);
208
189
  }
@@ -215,8 +196,7 @@ export function quaternionFromTMercToEnu(proj, coordinates) {
215
196
  const e2 = proj.e * proj.e;
216
197
  eta0 = e2 / (1 - e2);
217
198
  }
218
- return function (coordinates) {
219
- let target = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : new Quaternion();
199
+ return (coordinates, target = new Quaternion()) => {
220
200
  coordinates.as(coord.crs, coord);
221
201
  const long = coord.longitude * MathUtils.DEG2RAD;
222
202
  const lat = coord.latitude * MathUtils.DEG2RAD;
@@ -244,16 +224,12 @@ export function quaternionFromTMercToEnu(proj, coordinates) {
244
224
  * @returns The target quaternion if coordinates is defined. Otherwise, a
245
225
  * function to compute it from coordinates.
246
226
  */
247
- export function quaternionFromEnuToTMerc(proj, coordinates) {
248
- let target = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : new Quaternion();
227
+ export function quaternionFromEnuToTMerc(proj, coordinates, target = new Quaternion()) {
249
228
  if (coordinates) {
250
229
  return quaternionFromEnuToTMerc(proj)(coordinates, target);
251
230
  }
252
231
  const fromTMerc = quaternionFromTMercToEnu(proj);
253
- return function (coordinates) {
254
- let target = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : new Quaternion();
255
- return fromTMerc(coordinates, target).conjugate();
256
- };
232
+ return (coordinates, target = new Quaternion()) => fromTMerc(coordinates, target).conjugate();
257
233
  }
258
234
  /**
259
235
  * Computes the rotation from a LongLat frame to the local East North Up
@@ -266,12 +242,8 @@ export function quaternionFromEnuToTMerc(proj, coordinates) {
266
242
  * @returns The target quaternion if coordinates is defined, otherwise, a
267
243
  * function to compute it from coordinates.
268
244
  */
269
- export function quaternionFromLongLatToEnu(coordinates) {
270
- let target = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : new Quaternion();
271
- return coordinates ? target.set(0, 0, 0, 1) : function (coordinates) {
272
- let target = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : new Quaternion();
273
- return quaternionFromLongLatToEnu(coordinates, target);
274
- };
245
+ export function quaternionFromLongLatToEnu(coordinates, target = new Quaternion()) {
246
+ return coordinates ? target.set(0, 0, 0, 1) : (coordinates, target = new Quaternion()) => quaternionFromLongLatToEnu(coordinates, target);
275
247
  }
276
248
  /**
277
249
  * Computes the rotation from the local East North Up (ENU) frame to a
@@ -283,30 +255,23 @@ export function quaternionFromLongLatToEnu(coordinates) {
283
255
  * @returns The target quaternion if coordinates is defined, otherwise, a
284
256
  * function to compute it from coordinates.
285
257
  */
286
- export function quaternionFromEnuToLongLat(coordinates) {
287
- let target = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : new Quaternion();
288
- return coordinates ? target.set(0, 0, 0, 1) : function (coordinates) {
289
- let target = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : new Quaternion();
290
- return quaternionFromEnuToLongLat(coordinates, target);
291
- };
258
+ export function quaternionFromEnuToLongLat(coordinates, target = new Quaternion()) {
259
+ return coordinates ? target.set(0, 0, 0, 1) : (coordinates, target = new Quaternion()) => quaternionFromEnuToLongLat(coordinates, target);
292
260
  }
293
261
  /**
294
262
  * Warns for an unimplemented projection, sets the quaternion to the
295
263
  * identity (0,0,0,1).
296
264
  *
297
265
  * @param proj - the unimplemented projection (may be parsed using proj4)
266
+ * @param proj.projName - the name of the projection
298
267
  * @param coordinates - the origin of the local East North Up (ENU) frame
299
268
  * @param target - output Quaternion
300
269
  * @returns The target quaternion if coordinates is defined, otherwise, a
301
270
  * function to compute it from coordinates.
302
271
  */
303
- export function quaternionUnimplemented(proj, coordinates) {
304
- let target = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : new Quaternion();
272
+ export function quaternionUnimplemented(proj, coordinates, target = new Quaternion()) {
305
273
  console.warn('This quaternion function is not implemented for projections of type', proj.projName);
306
- return coordinates ? target.set(0, 0, 0, 1) : function (coordinates) {
307
- let target = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : new Quaternion();
308
- return quaternionUnimplemented(proj, coordinates, target);
309
- };
274
+ return coordinates ? target.set(0, 0, 0, 1) : (coordinates, target = new Quaternion()) => quaternionUnimplemented(proj, coordinates, target);
310
275
  }
311
276
  /**
312
277
  * Compute the quaternion that models the rotation from the local East North
@@ -319,8 +284,7 @@ export function quaternionUnimplemented(proj, coordinates) {
319
284
  * @returns The target quaternion if coordinates is defined, otherwise, a
320
285
  * function to compute it from coordinates.
321
286
  */
322
- export function quaternionFromEnuToCRS(crsOrProj, coordinates) {
323
- let target = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : new Quaternion();
287
+ export function quaternionFromEnuToCRS(crsOrProj, coordinates, target = new Quaternion()) {
324
288
  if (coordinates) {
325
289
  return quaternionFromEnuToCRS(crsOrProj)(coordinates, target);
326
290
  }
@@ -331,6 +295,7 @@ export function quaternionFromEnuToCRS(crsOrProj, coordinates) {
331
295
  return quaternionFromEnuToGeocent();
332
296
  case 'Lambert Tangential Conformal Conic Projection':
333
297
  return quaternionFromEnuToLCC(proj);
298
+ case 'Universal Transverse Mercator System':
334
299
  case 'Fast_Transverse_Mercator':
335
300
  return quaternionFromEnuToTMerc(proj);
336
301
  case 'longlat':
@@ -350,8 +315,7 @@ export function quaternionFromEnuToCRS(crsOrProj, coordinates) {
350
315
  * @returns The target quaternion if coordinates is defined, otherwise, a
351
316
  * function to compute it from coordinates.
352
317
  */
353
- export function quaternionFromCRSToEnu(crsOrProj, coordinates) {
354
- let target = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : new Quaternion();
318
+ export function quaternionFromCRSToEnu(crsOrProj, coordinates, target = new Quaternion()) {
355
319
  if (coordinates) {
356
320
  return quaternionFromCRSToEnu(crsOrProj)(coordinates, target);
357
321
  }
@@ -362,6 +326,7 @@ export function quaternionFromCRSToEnu(crsOrProj, coordinates) {
362
326
  return quaternionFromGeocentToEnu();
363
327
  case 'Lambert Tangential Conformal Conic Projection':
364
328
  return quaternionFromLCCToEnu(proj);
329
+ case 'Universal Transverse Mercator System':
365
330
  case 'Fast_Transverse_Mercator':
366
331
  return quaternionFromTMercToEnu(proj);
367
332
  case 'longlat':
@@ -370,6 +335,7 @@ export function quaternionFromCRSToEnu(crsOrProj, coordinates) {
370
335
  return quaternionUnimplemented(proj);
371
336
  }
372
337
  }
338
+ const quaternionCrs2CrsCache = {};
373
339
  /**
374
340
  * Return the function that computes the quaternion that represents a
375
341
  * rotation of coordinates between two CRS frames.
@@ -381,23 +347,25 @@ export function quaternionFromCRSToEnu(crsOrProj, coordinates) {
381
347
  * @returns The target quaternion if coordinates is defined, otherwise, a
382
348
  * function to compute it from coordinates.
383
349
  */
384
- export function quaternionFromCRSToCRS(crsIn, crsOut, coordinates) {
385
- let target = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : new Quaternion();
350
+ export function quaternionFromCRSToCRS(crsIn, crsOut, coordinates, target = new Quaternion()) {
386
351
  if (coordinates) {
387
352
  return quaternionFromCRSToCRS(crsIn, crsOut)(coordinates, target);
388
353
  }
389
354
  if (crsIn == crsOut) {
390
- return function (origin) {
391
- let target = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : new Quaternion();
392
- return target.set(0, 0, 0, 1);
393
- };
355
+ return (origin, target = new Quaternion()) => target.set(0, 0, 0, 1);
356
+ }
357
+ if (quaternionCrs2CrsCache[crsIn] && quaternionCrs2CrsCache[crsIn][crsOut]) {
358
+ return quaternionCrs2CrsCache[crsIn][crsOut];
394
359
  }
395
360
 
396
361
  // get rotations from the local East/North/Up (ENU) frame to both CRS.
397
362
  const fromCrs = quaternionFromCRSToEnu(crsIn);
398
363
  const toCrs = quaternionFromEnuToCRS(crsOut);
399
- return function (origin) {
400
- let target = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : new Quaternion();
401
- return toCrs(origin, target).multiply(fromCrs(origin, quat));
402
- };
364
+ if (!quaternionCrs2CrsCache[crsIn]) {
365
+ quaternionCrs2CrsCache[crsIn] = {};
366
+ }
367
+ if (!quaternionCrs2CrsCache[crsIn][crsOut]) {
368
+ quaternionCrs2CrsCache[crsIn][crsOut] = (origin, target = new Quaternion()) => toCrs(origin, target).multiply(fromCrs(origin, quat));
369
+ }
370
+ return quaternionCrs2CrsCache[crsIn][crsOut];
403
371
  }
package/lib/index.d.ts ADDED
@@ -0,0 +1,7 @@
1
+ export { default as Extent, type ExtentLike } from './Extent';
2
+ export { default as Coordinates, type CoordinatesLike } from './Coordinates';
3
+ export * as CRS from './Crs';
4
+ export { type ProjectionLike } from './Crs';
5
+ export { default as CoordStars } from './CoordStars';
6
+ export * as OrientationUtils from './OrientationUtils';
7
+ export { default as Ellipsoid, ellipsoidSizes } from './Ellipsoid';
package/package.json CHANGED
@@ -1,20 +1,19 @@
1
1
  {
2
2
  "name": "@itowns/geographic",
3
- "version": "2.46.1-next.6",
4
- "description": "Geodesy",
3
+ "version": "2.46.1-next.60",
4
+ "description": "Proj4-based three.js geodesy toolkit",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
7
7
  "exports": {
8
- "types": "./src/index.ts",
8
+ "types": "./lib/index.d.ts",
9
9
  "default": "./lib/index.js"
10
10
  },
11
- "types": "./src/index.ts",
11
+ "types": "./lib/index.d.ts",
12
12
  "scripts": {
13
13
  "build": "",
14
- "lint": "eslint \"src/**/*.{js,ts,tsx}\" \"test/**/*.js\"",
15
14
  "transpile": "tsc && cross-env BABEL_DISABLE_CACHE=1 babel src --out-dir lib --extensions .js,.ts",
16
15
  "test-unit": "npm run base-test-unit test/unit",
17
- "base-test-unit": "cross-env BABEL_DISABLE_CACHE=1 mocha --import=../../config/babel-register/register.mjs",
16
+ "base-test-unit": "cross-env BABEL_DISABLE_CACHE=1 NODE_OPTIONS=--no-experimental-strip-types mocha --import=../../config/babel-register/register.mjs",
18
17
  "test-with-coverage": "c8 -n src -r html cross-env npm run test-unit",
19
18
  "test-with-coverage_lcov": "c8 -n src --reporter=lcov cross-env npm run test-unit",
20
19
  "watch": "npm run transpile -- --watch",
@@ -35,8 +34,8 @@
35
34
  "url": "https://github.com/itowns/itowns/issues"
36
35
  },
37
36
  "peerDependencies": {
38
- "proj4": "^2.19.10",
39
- "three": "^0.174.0"
37
+ "proj4": ">=2.20.0",
38
+ "three": ">=0.182.0"
40
39
  },
41
40
  "homepage": "https://itowns.github.io/"
42
41
  }
@@ -1,19 +1,14 @@
1
1
  import { Vector3, type Vector3Like, type Matrix4, MathUtils } from 'three';
2
- import proj4, { type Converter } from 'proj4';
3
2
  import Ellipsoid from './Ellipsoid';
4
3
  import * as CRS from './Crs';
5
4
 
6
5
  import type { ProjectionLike } from './Crs';
7
6
 
8
7
  const ellipsoid = /* @__PURE__ */ new Ellipsoid();
9
- const projectionCache: Record<string, Record<string, Converter>> = {};
10
8
 
11
9
  const v0 = /* @__PURE__ */ new Vector3();
12
10
  const v1 = /* @__PURE__ */ new Vector3();
13
11
 
14
- let coord0: Coordinates;
15
- let coord1: Coordinates;
16
-
17
12
  export interface CoordinatesLike {
18
13
  readonly crs: string;
19
14
  readonly x: number;
@@ -21,18 +16,6 @@ export interface CoordinatesLike {
21
16
  readonly z: number;
22
17
  }
23
18
 
24
- function proj4cache(crsIn: ProjectionLike, crsOut: ProjectionLike): Converter {
25
- if (!projectionCache[crsIn]) {
26
- projectionCache[crsIn] = {};
27
- }
28
-
29
- if (!projectionCache[crsIn][crsOut]) {
30
- projectionCache[crsIn][crsOut] = proj4(crsIn, crsOut);
31
- }
32
-
33
- return projectionCache[crsIn][crsOut];
34
- }
35
-
36
19
  /**
37
20
  * A class representing a geographic or geocentric coordinate.
38
21
  *
@@ -91,7 +74,7 @@ class Coordinates {
91
74
  * @param y - y or latitude value.
92
75
  * @param z - z or altitude value.
93
76
  */
94
- constructor(crs: ProjectionLike, x: number = 0, y: number = 0, z: number = 0) {
77
+ constructor(crs: ProjectionLike, x = 0, y = 0, z = 0) {
95
78
  this.isCoordinates = true;
96
79
 
97
80
  CRS.isValid(crs);
@@ -132,6 +115,7 @@ class Coordinates {
132
115
  /**
133
116
  * Sets the Coordinate Reference System.
134
117
  * @param crs - Coordinate Reference System (e.g. 'EPSG:4978')
118
+ * @returns
135
119
  */
136
120
  setCrs(crs: ProjectionLike): this {
137
121
  CRS.isValid(crs);
@@ -145,8 +129,9 @@ class Coordinates {
145
129
  * @param x - x or longitude value.
146
130
  * @param y - y or latitude value.
147
131
  * @param z - z or altitude value.
132
+ * @returns
148
133
  */
149
- setFromValues(x: number = 0, y: number = 0, z: number = 0): this {
134
+ setFromValues(x = 0, y = 0, z = 0): this {
150
135
  this.x = x;
151
136
  this.y = y;
152
137
  this.z = z;
@@ -163,8 +148,9 @@ class Coordinates {
163
148
  *
164
149
  * @param array - The source array.
165
150
  * @param offset - Optional offset into the array. Default is 0.
151
+ * @returns
166
152
  */
167
- setFromArray(array: number[], offset: number = 0): this {
153
+ setFromArray(array: number[], offset = 0): this {
168
154
  return this.setFromValues(
169
155
  array[offset],
170
156
  array[offset + 1],
@@ -178,6 +164,7 @@ class Coordinates {
178
164
  * properties.
179
165
  *
180
166
  * @param v - The source object.
167
+ * @returns
181
168
  */
182
169
  setFromVector3(v: Vector3Like): this {
183
170
  return this.setFromValues(v.x, v.y, v.z);
@@ -186,6 +173,7 @@ class Coordinates {
186
173
  /**
187
174
  * Returns a new coordinate with the same `(x, y, z)` vector and crs as this
188
175
  * one.
176
+ * @returns
189
177
  */
190
178
  clone(): Coordinates {
191
179
  return new Coordinates(this.crs, this.x, this.y, this.z);
@@ -196,6 +184,7 @@ class Coordinates {
196
184
  * to this coordinate.
197
185
  *
198
186
  * @param src - The source coordinate to copy from.
187
+ * @returns
199
188
  */
200
189
  copy(src: CoordinatesLike): this {
201
190
  this.crs = src.crs;
@@ -220,6 +209,7 @@ class Coordinates {
220
209
 
221
210
  /**
222
211
  * The geodesic normal of the coordinate.
212
+ * @returns
223
213
  */
224
214
  get geodesicNormal() {
225
215
  if (this._normalNeedsUpdate) {
@@ -260,7 +250,7 @@ class Coordinates {
260
250
  * @returns An array [x, y, z], or copies x, y and z into the provided
261
251
  * array.
262
252
  */
263
- toArray(array: number[] = [], offset: number = 0): ArrayLike<number> {
253
+ toArray(array: number[] = [], offset = 0): ArrayLike<number> {
264
254
  return Vector3.prototype.toArray.call(this, array, offset);
265
255
  }
266
256
 
@@ -268,6 +258,8 @@ class Coordinates {
268
258
  * Computes the planar distance from this coordinates to `coord`.
269
259
  * **Planar distance** is the straight-line euclidean distance calculated in
270
260
  * a 2D cartesian coordinate system.
261
+ * @param coord
262
+ * @returns
271
263
  */
272
264
  planarDistanceTo(coord: Coordinates): number {
273
265
  this.toVector3(v0).setZ(0);
@@ -279,6 +271,8 @@ class Coordinates {
279
271
  * Computes the geodetic distance from this coordinates to `coord`.
280
272
  * **Geodetic distance** is calculated in an ellipsoid space as the shortest
281
273
  * distance across the curved surface of the ellipsoid.
274
+ * @param coord
275
+ * @returns
282
276
  */
283
277
  geodeticDistanceTo(coord: Coordinates): number {
284
278
  this.as('EPSG:4326', coord0);
@@ -304,6 +298,7 @@ class Coordinates {
304
298
  * by `mat`, and divides by perspective.
305
299
  *
306
300
  * @param mat - The matrix.
301
+ * @returns
307
302
  */
308
303
  applyMatrix4(mat: Matrix4): this {
309
304
  Vector3.prototype.applyMatrix4.call(this, mat);
@@ -348,7 +343,7 @@ class Coordinates {
348
343
  this.y = MathUtils.clamp(this.y, -89.999999, 89.999999);
349
344
  }
350
345
 
351
- target.setFromArray(proj4cache(this.crs, crs)
346
+ target.setFromArray(CRS.transform(this.crs, crs)
352
347
  .forward([this.x, this.y, this.z]));
353
348
  }
354
349
 
@@ -358,7 +353,7 @@ class Coordinates {
358
353
  }
359
354
  }
360
355
 
361
- coord0 = /* @__PURE__ */ new Coordinates('EPSG:4326', 0, 0, 0);
362
- coord1 = /* @__PURE__ */ new Coordinates('EPSG:4326', 0, 0, 0);
356
+ const coord0 = /* @__PURE__ */ new Coordinates('EPSG:4326', 0, 0, 0);
357
+ const coord1 = /* @__PURE__ */ new Coordinates('EPSG:4326', 0, 0, 0);
363
358
 
364
359
  export default Coordinates;
package/src/Crs.ts CHANGED
@@ -1,7 +1,15 @@
1
1
  import proj4 from 'proj4';
2
-
2
+ import type { Converter } from 'proj4';
3
3
  import type { ProjectionDefinition } from 'proj4/dist/lib/defs';
4
4
 
5
+ interface proj4Def {
6
+ type: string;
7
+ PROJCS: proj4Def;
8
+ unknown?: string;
9
+ (alias: string): proj4Def & { name: string };
10
+ title: string;
11
+ }
12
+
5
13
  proj4.defs('EPSG:4978', '+proj=geocent +datum=WGS84 +units=m +no_defs');
6
14
 
7
15
  // Redefining proj4 global projections to match epsg.org database axis order.
@@ -15,8 +23,22 @@ proj4.defs('WGS84').axis = 'neu';
15
23
  * projection definition previously defined with
16
24
  * [`proj4.defs`](https://github.com/proj4js/proj4js#named-projections).
17
25
  */
26
+ // TODO Unify with OrientationUtils.js
18
27
  export type ProjectionLike = string;
19
28
 
29
+ const proj4Cache: Record<string, Record<string, Converter>> = {};
30
+ export function transform(crsIn: ProjectionLike, crsOut: ProjectionLike): Converter {
31
+ if (!proj4Cache[crsIn]) {
32
+ proj4Cache[crsIn] = {};
33
+ }
34
+
35
+ if (!proj4Cache[crsIn][crsOut]) {
36
+ proj4Cache[crsIn][crsOut] = proj4(crsIn, crsOut);
37
+ }
38
+
39
+ return proj4Cache[crsIn][crsOut];
40
+ }
41
+
20
42
  function isString(s: unknown): s is string {
21
43
  return typeof s === 'string' || s instanceof String;
22
44
  }
@@ -52,6 +74,7 @@ export const UNIT = {
52
74
  * @internal
53
75
  *
54
76
  * @param crs - The CRS to test.
77
+ * @returns
55
78
  */
56
79
  export function is4326(crs: ProjectionLike) {
57
80
  return crs === 'EPSG:4326';
@@ -62,7 +85,7 @@ function unitFromProj4Unit(proj: ProjectionDefinition) {
62
85
  return UNIT.DEGREE;
63
86
  } else if (proj.units === 'm' || proj.units === 'meter') {
64
87
  return UNIT.METER;
65
- } else if (proj.units === 'foot') {
88
+ } else if (proj.units === 'foot' || proj.units === 'ft') {
66
89
  return UNIT.FOOT;
67
90
  } else if (proj.units === undefined && proj.to_meter === undefined) {
68
91
  // See https://proj.org/en/9.4/usage/projections.html [17/10/2024]
@@ -92,7 +115,8 @@ export function getUnit(crs: ProjectionLike) {
92
115
  * Asserts that the CRS is using metric units.
93
116
  *
94
117
  * @param crs - The CRS to check.
95
- * @throws {@link Error} if the CRS is not valid.
118
+ * @returns
119
+ * @throws {Error} if the CRS is not valid.
96
120
  */
97
121
  export function isMetricUnit(crs: ProjectionLike) {
98
122
  return getUnit(crs) === UNIT.METER;
@@ -102,7 +126,8 @@ export function isMetricUnit(crs: ProjectionLike) {
102
126
  * Asserts that the CRS is geographic.
103
127
  *
104
128
  * @param crs - The CRS to check.
105
- * @throws {@link Error} if the CRS is not valid.
129
+ * @returns
130
+ * @throws {Error} if the CRS is not valid.
106
131
  */
107
132
  export function isGeographic(crs: ProjectionLike) {
108
133
  return getUnit(crs) === UNIT.DEGREE;
@@ -125,12 +150,12 @@ export function isGeocentric(crs: ProjectionLike) {
125
150
  * includes an unit.
126
151
  *
127
152
  * @param crs - The CRS to test.
128
- * @throws {@link Error} if the crs is not valid.
153
+ * @throws {Error} if the crs is not valid.
129
154
  */
130
155
  export function isValid(crs: ProjectionLike) {
131
156
  const proj = proj4.defs(crs);
132
157
  if (!proj) {
133
- throw new Error(`Undefined crs '${crs}'. Add it with proj4.defs('${crs}', string)`);
158
+ throw new Error(`Undefined crs '${crs}'. Add it with itowns.CRS.defs('${crs}', wktString)`);
134
159
  }
135
160
  if (!unitFromProj4Unit(proj)) {
136
161
  throw new Error(`No valid unit found for crs '${crs}', found ${proj.units}`);
@@ -166,11 +191,56 @@ export function axisOrder(crs: ProjectionLike) {
166
191
 
167
192
  /**
168
193
  * Defines a proj4 projection as a named alias.
169
- * This function is a specialized wrapper over the
170
- * [`proj4.defs`](https://github.com/proj4js/proj4js#named-projections)
171
- * function.
194
+ * This function is an alias for the
195
+ * [`proj4.defs`](https://github.com/proj4js/proj4js#named-projections) function.
196
+ */
197
+ export const defs = proj4.defs;
198
+
199
+ /**
200
+ * Fetches a CRS definition from epsg.io and registers it with proj4.
201
+ * If the CRS is already defined, returns the existing definition.
172
202
  *
173
- * @param code - Named alias of the currently defined projection.
174
- * @param proj4def - Proj4 or WKT string of the defined projection.
203
+ * @param crs - The EPSG code string (e.g. "EPSG:2154").
204
+ * @returns The proj4 projection definition.
205
+ *
206
+ * @example
207
+ * // Register EPSG:2154 (RGF93 / Lambert-93)
208
+ * await CRS.fromEPSG('EPSG:2154');
209
+ *
210
+ * // Register EPSG:4269 (NAD83)
211
+ * await CRS.fromEPSG('EPSG:4269');
175
212
  */
176
- export const defs = (code: string, proj4def: string) => proj4.defs(code, proj4def);
213
+ export async function fromEPSG(crs: string): Promise<ProjectionDefinition> {
214
+ const def = proj4.defs(crs);
215
+ if (def) {
216
+ return def;
217
+ }
218
+
219
+ const code = crs.replace(/^EPSG:/i, '');
220
+ const response = await fetch(`https://epsg.io/${code}.proj4`);
221
+ if (!response.ok) {
222
+ throw new Error(`Failed to fetch EPSG:${code} from epsg.io: ${response.status}`);
223
+ }
224
+
225
+ const proj4def = await response.text();
226
+ proj4.defs(crs, proj4def);
227
+ return proj4.defs(crs);
228
+ }
229
+
230
+ export function defsFromWkt(wkt: string): string {
231
+ proj4.defs('unknown', wkt);
232
+ const proj4Defs = proj4.defs as unknown as proj4Def;
233
+ let projCS;
234
+ if (proj4Defs('unknown').type === 'COMPD_CS') {
235
+ console.warn('Compound coordinate system is not yet supported.');
236
+ projCS = proj4Defs('unknown').PROJCS;
237
+ } else {
238
+ projCS = proj4Defs('unknown');
239
+ }
240
+ const crsAlias = (projCS.title || projCS.name || 'EPSG:XXXX');
241
+ if (!(crsAlias in proj4.defs)) {
242
+ proj4.defs(crsAlias, projCS);
243
+ }
244
+ delete proj4Defs.unknown;
245
+ return crsAlias;
246
+ }