@itowns/geographic 2.44.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.
@@ -0,0 +1,410 @@
1
+ import * as THREE from 'three';
2
+ import proj4 from 'proj4';
3
+ import Coordinates from 'Coordinates';
4
+
5
+ const DEG2RAD = THREE.MathUtils.DEG2RAD;
6
+ const matrix = new THREE.Matrix4();
7
+ const north = new THREE.Vector3();
8
+ const east = new THREE.Vector3();
9
+ const axis = new THREE.Vector3().set(0, 0, 1);
10
+ const coord = new Coordinates('EPSG:4326', 0, 0, 0);
11
+ const euler = new THREE.Euler();
12
+ const quat = new THREE.Quaternion();
13
+
14
+ function quaternionIdentity(coordinates, target = new THREE.Quaternion()) {
15
+ return coordinates ? target.set(0, 0, 0, 1) : quaternionIdentity;
16
+ }
17
+
18
+ /**
19
+ * The OrientationUtils module provides methods to compute the quaternion that
20
+ * models a rotation defined with various conventions, including between different
21
+ * CRS.
22
+ * The local <a href="https://en.wikipedia.org/wiki/Local_tangent_plane_coordinates#Local_east,_north,_up_(ENU)_coordinates">
23
+ * East/North/Up frame (ENU)</a> is used as a pivot frame when computing the rotation between two distinct CRS.
24
+ * If the origin of the frame is undefined, CRS-related methods precompute and return a function
25
+ * that can be applied efficiently to many points of origin.
26
+ * Otherwise, the target quaternion is returned at the provided origin coordinates.
27
+ *
28
+ * @example
29
+ * // Compute the rotation around the point of origin from a frame aligned with Lambert93 axes (epsg:2154),
30
+ * // to the geocentric frame (epsg:4978)
31
+ * quat_crs2crs = OrientationUtils.quaternionFromCRSToCRS("EPSG:2154", "EPSG:4978")(origin);
32
+ * // Compute the rotation of a sensor platform defined by its attitude
33
+ * quat_attitude = OrientationUtils.quaternionFromAttitude(attitude);
34
+ * // Compute the rotation from the sensor platform frame to the geocentric frame
35
+ * quat = quat_crs2crs.multiply(quat_attitude);
36
+ *
37
+ * @module OrientationUtils
38
+ */
39
+ export default {
40
+ /**
41
+ * @typedef {Object} Attitude
42
+ * Properties are either defined as (omega, phi, kappa) or as (roll, pitch,
43
+ * heading) or all `undefined`.
44
+ *
45
+ * @property {number} omega - angle in degrees
46
+ * @property {number} phi - angle in degrees
47
+ * @property {number} kappa - angle in degrees
48
+ * @property {number} roll - angle in degrees
49
+ * @property {number} pitch - angle in degrees
50
+ * @property {number} heading - angle in degrees
51
+ */
52
+
53
+ /**
54
+ * The transform from the platform frame to the local East, North, Up (ENU)
55
+ * frame is `RotationZ(heading).RotationX(pitch).RotationY(roll)`
56
+ *
57
+ * @param {number} [roll=0] - angle in degrees
58
+ * @param {number} [pitch=0] - angle in degrees
59
+ * @param {number} [heading=0] - angle in degrees
60
+ * @param {THREE.Quaternion} [target=new THREE.Quaternion()] - output Quaternion
61
+ *
62
+ * @return {THREE.Quaternion} target quaternion
63
+ */
64
+ quaternionFromRollPitchHeading(roll = 0, pitch = 0, heading = 0, target = new THREE.Quaternion()) {
65
+ roll *= DEG2RAD;
66
+ pitch *= DEG2RAD;
67
+ heading *= DEG2RAD;
68
+ // return this.setFromEuler(euler.set(pitch, roll, heading , 'ZXY')).conjugate();
69
+ return target.setFromEuler(euler.set(-pitch, -roll, -heading, 'YXZ')); // optimized version of above
70
+ },
71
+
72
+ /**
73
+ * From
74
+ * [DocMicMac](https://github.com/micmacIGN/Documentation/raw/master/DocMicMac.pdf),
75
+ * the transform from the platform frame to the local East, North, Up (ENU)
76
+ * frame is:
77
+ *
78
+ * ```
79
+ * RotationX(omega).RotationY(phi).RotationZ(kappa).RotationX(PI)
80
+ * RotationX(PI) <=> Quaternion(1,0,0,0) : converts between the 2 conventions for the camera local frame:
81
+ * X right, Y bottom, Z front : convention in photogrammetry and computer vision
82
+ * X right, Y top, Z back : convention in webGL, threejs
83
+ * ```
84
+ *
85
+ * @param {number} [omega=0] - angle in degrees
86
+ * @param {number} [phi=0] - angle in degrees
87
+ * @param {number} [kappa=0] - angle in degrees
88
+ * @param {THREE.Quaternion} [target=new THREE.Quaternion()] output Quaternion
89
+ *
90
+ * @return {THREE.Quaternion} target quaternion
91
+ */
92
+ quaternionFromOmegaPhiKappa(omega = 0, phi = 0, kappa = 0, target = new THREE.Quaternion()) {
93
+ omega *= DEG2RAD;
94
+ phi *= DEG2RAD;
95
+ kappa *= DEG2RAD;
96
+ target.setFromEuler(euler.set(omega, phi, kappa, 'XYZ'));
97
+ target.set(target.w, target.z, -target.y, -target.x); // <=> target.multiply(new THREE.Quaternion(1, 0, 0, 0));
98
+ return target;
99
+ },
100
+
101
+ /**
102
+ * Set the quaternion according to the rotation from the platform frame to
103
+ * the local frame.
104
+ *
105
+ * @param {Attitude} attitude - Attitude
106
+ * @param {THREE.Quaternion} [target=new THREE.Quaternion()] output Quaternion
107
+ *
108
+ * @return {THREE.Quaternion} target quaternion
109
+ */
110
+ quaternionFromAttitude(attitude, target = new THREE.Quaternion()) {
111
+ if ((attitude.roll !== undefined) || (attitude.pitch !== undefined) || (attitude.heading !== undefined)) {
112
+ return this.quaternionFromRollPitchHeading(attitude.roll, attitude.pitch, attitude.heading, target);
113
+ }
114
+ if ((attitude.omega !== undefined) || (attitude.phi !== undefined) || (attitude.kappa !== undefined)) {
115
+ return this.quaternionFromOmegaPhiKappa(attitude.omega, attitude.phi, attitude.kappa, target);
116
+ }
117
+ return target.set(0, 0, 0, 1);
118
+ },
119
+
120
+ /**
121
+ * @typedef {Function|THREE.Quaternion} FunctionOrQuaternion - Either a
122
+ * THREE.Quaternion or a function that accepts arguments `(coordinates,
123
+ * target)` and returns the quaternion that models a rotation around the
124
+ * point of origin. If target is not provided, a new quaternion is created
125
+ * and returned instead.
126
+ *
127
+ * @property {Coordinates} coordinates the origin of the local East North Up
128
+ * (ENU) frame
129
+ * @property {THREE.Quaternion} [target=new THREE.Quaternion()] output Quaternion.
130
+ */
131
+
132
+ /**
133
+ * A Projection object models a Coordinate Reference System (CRS).
134
+ * Such an object is usually created with proj4 using `proj4.defs(crs);`
135
+ *
136
+ * @typedef {Object} Projection
137
+ *
138
+ * @property {string} projName
139
+ */
140
+
141
+ /**
142
+ * Set the quaternion according to the rotation from the local East North Up (ENU)
143
+ * frame to the geocentric frame. The up direction of the ENU frame is
144
+ * provided by the normalized geodetic normal of the provided coordinates
145
+ * (geodeticNormal property).
146
+ *
147
+ * @param {Coordinates} [coordinates] the origin of the local East North Up
148
+ * (ENU) frame
149
+ * @param {THREE.Quaternion} [target=new THREE.Quaternion()] output Quaternion
150
+ * @return {FunctionOrQuaternion} The target quaternion if coordinates is defined, otherwise, a function to compute it from coordinates.
151
+ */
152
+ quaternionFromEnuToGeocent(coordinates, target = new THREE.Quaternion()) {
153
+ if (coordinates) { return this.quaternionFromEnuToGeocent()(coordinates, target); }
154
+ return (coordinates, target = new THREE.Quaternion()) => {
155
+ const up = coordinates.geodesicNormal;
156
+ if (up.x == 0 && up.y == 0) {
157
+ return target.set(0, 0, 0, 1);
158
+ }
159
+ // this is an optimized version of matrix.lookAt(up, new THREE.Vector3(0, 0, 0), new THREE.Vector3(0, 0, 1));
160
+ east.set(-up.y, up.x, 0).normalize();
161
+ north.crossVectors(up, east);
162
+ matrix.makeBasis(east, north, up);
163
+ return target.setFromRotationMatrix(matrix);
164
+ };
165
+ },
166
+
167
+ /**
168
+ * Set the quaternion according to the rotation from a geocentric frame
169
+ * to the local East North Up (ENU) frame. The up direction of the ENU frame is
170
+ * provided by the normalized geodetic normal of the provided coordinates
171
+ * (geodeticNormal property).
172
+ *
173
+ * @param {Coordinates} [coordinates] the origin of the local East North Up
174
+ * (ENU) frame
175
+ * @param {THREE.Quaternion} [target=new THREE.Quaternion()] output Quaternion
176
+ * @return {FunctionOrQuaternion} The target quaternion if coordinates is defined, otherwise, a function to compute it from coordinates.
177
+ */
178
+ quaternionFromGeocentToEnu(coordinates, target = new THREE.Quaternion()) {
179
+ if (coordinates) { return this.quaternionFromGeocentToEnu()(coordinates, target); }
180
+ const toGeocent = this.quaternionFromEnuToGeocent();
181
+ return (coordinates, target = new THREE.Quaternion()) => toGeocent(coordinates, target).conjugate();
182
+ },
183
+
184
+
185
+ /**
186
+ * Computes the rotation from a Lambert Conformal Conic (LCC) frame to the local East North Up (ENU) frame.
187
+ * The quaternion accounts for the
188
+ * <a href="https://geodesie.ign.fr/contenu/fichiers/documentation/algorithmes/alg0060.pdf">meridian convergence</a>
189
+ * between the ENU and LCC frames.
190
+ * This is a generally small rotation around Z.
191
+ *
192
+ * @param {Object} proj the lcc projection (may be parsed using proj4)
193
+ * @param {number} proj.lat0 - the latitude of origin
194
+ * @param {number} proj.long0 - the longitude of the central meridian
195
+ * @param {Coordinates} [coordinates] coordinates the origin of the local East North Up
196
+ * (ENU) frame
197
+ * @param {THREE.Quaternion} [target=new THREE.Quaternion()] output Quaternion
198
+ * @return {FunctionOrQuaternion} The target quaternion if coordinates is defined, otherwise, a function to compute it from coordinates.
199
+ */
200
+ quaternionFromLCCToEnu(proj, coordinates, target = new THREE.Quaternion()) {
201
+ if (coordinates) { return this.quaternionFromLCCToEnu(proj)(coordinates, target); }
202
+ const sinlat0 = Math.sin(proj.lat0);
203
+ return (coordinates, target = new THREE.Quaternion()) => {
204
+ const long = coordinates.as(coord.crs, coord).longitude * DEG2RAD;
205
+ return target.setFromAxisAngle(axis, sinlat0 * (proj.long0 - long));
206
+ };
207
+ },
208
+
209
+ /**
210
+ * Computes the rotation from the local East North Up (ENU) frame to a Lambert Conformal Conic (LCC) frame.
211
+ * The quaternion accounts for the
212
+ * <a href="https://geodesie.ign.fr/contenu/fichiers/documentation/algorithmes/alg0060.pdf">meridian convergence</a>
213
+ * between the ENU and LCC frames.
214
+ * This is a generally small rotation around Z.
215
+ *
216
+ * @param {Object} proj the lcc projection (may be parsed using proj4)
217
+ * @param {number} proj.lat0 - the latitude of origin
218
+ * @param {number} proj.long0 - the longitude of the central meridian
219
+ * @param {Coordinates} [coordinates] coordinates the origin of the local East North Up
220
+ * (ENU) frame
221
+ * @param {THREE.Quaternion} [target=new THREE.Quaternion()] output Quaternion
222
+ * @return {FunctionOrQuaternion} The target quaternion if coordinates is defined, otherwise, a function to compute it from coordinates.
223
+ */
224
+ quaternionFromEnuToLCC(proj, coordinates, target = new THREE.Quaternion()) {
225
+ if (coordinates) { return this.quaternionFromEnuToLCC(proj)(coordinates, target); }
226
+ const fromLCC = this.quaternionFromLCCToEnu(proj);
227
+ return (coordinates, target = new THREE.Quaternion()) => fromLCC(coordinates, target).conjugate();
228
+ },
229
+
230
+ /**
231
+ * Computes the rotation from a Transverse Mercator frame (TMerc) to the local East North Up (ENU) frame.
232
+ * The quaternion accounts for the
233
+ * <a href="https://geodesie.ign.fr/contenu/fichiers/documentation/algorithmes/alg0061.pdf">meridian convergence</a>
234
+ * between the ENU and TMerc frames.
235
+ * This is a generally small rotation around Z.
236
+ *
237
+ * @param {Object} proj the tmerc projection (may be parsed using proj4)
238
+ * @param {number} proj.e - the excentricity of the ellipsoid (supersedes {proj.a} and {proj.b})
239
+ * @param {number} proj.a - the semimajor radius of the ellipsoid axis
240
+ * @param {number} proj.b - the semiminor radius of the ellipsoid axis
241
+ * @param {number} proj.long0 - the longitude of the central meridian
242
+ *
243
+ * @param {Coordinates} [coordinates] coordinates the origin of the local East North Up
244
+ * (ENU) frame
245
+ * @param {THREE.Quaternion} [target=new THREE.Quaternion()] output Quaternion
246
+ * @return {FunctionOrQuaternion} The target quaternion if coordinates is defined, otherwise, a function to compute it from coordinates.
247
+ */
248
+ quaternionFromTMercToEnu(proj, coordinates, target = new THREE.Quaternion()) {
249
+ if (coordinates) { return this.quaternionFromTMercToEnu(proj)(coordinates, target); }
250
+ const a2 = proj.a * proj.a;
251
+ const b2 = proj.b * proj.b;
252
+ const e2 = proj.e * proj.e;
253
+ const eta0 = proj.e ? (e2 / (1 - e2)) : (a2 / b2 - 1);
254
+ return (coordinates, target = new THREE.Quaternion()) => {
255
+ coordinates.as(coord.crs, coord);
256
+ const long = coord.longitude * DEG2RAD;
257
+ const lat = coord.latitude * DEG2RAD;
258
+ const dlong = proj.long0 - long;
259
+ const coslat = Math.cos(lat);
260
+ const sinlat = Math.sin(lat);
261
+ const tanlat = sinlat / coslat;
262
+ const coslat2 = coslat * coslat;
263
+ const dl2 = dlong * dlong * coslat2;
264
+ const eta2 = eta0 * coslat2;
265
+ const gamma = dlong * sinlat * (1 + dl2 / 3 * (1 + 3 * eta2 + 2 * eta2 * eta2) + dl2 * dl2 * (2 - tanlat) / 15);
266
+ return target.setFromAxisAngle(axis, gamma);
267
+ };
268
+ },
269
+
270
+ /**
271
+ * Computes the rotation from the local East North Up (ENU) to a Transverse Mercator frame.
272
+ * The quaternion accounts for the
273
+ * <a href="https://geodesie.ign.fr/contenu/fichiers/documentation/algorithmes/alg0061.pdf">meridian convergence</a>
274
+ * between the ENU and TMerc frames.
275
+ * This is a generally small rotation around Z.
276
+ *
277
+ * @param {Object} proj the tmerc projection (may be parsed using proj4)
278
+ * @param {number} proj.e - the excentricity of the ellipsoid (supersedes
279
+ * {proj.a} and {proj.b})
280
+ * @param {number} proj.a - the semimajor radius of the ellipsoid axis
281
+ * @param {number} proj.b - the semiminor radius of the ellipsoid axis
282
+ * @param {number} proj.long0 - the longitude of the central meridian
283
+ *
284
+ * @param {Coordinates} [coordinates] coordinates the origin of the local East North Up
285
+ * (ENU) frame
286
+ * @param {THREE.Quaternion} [target=new THREE.Quaternion()] output Quaternion
287
+ * @return {FunctionOrQuaternion} The target quaternion if coordinates is defined, otherwise, a function to compute it from coordinates.
288
+ */
289
+ quaternionFromEnuToTMerc(proj, coordinates, target = new THREE.Quaternion()) {
290
+ if (coordinates) { return this.quaternionFromEnuToTMerc(proj)(coordinates, target); }
291
+ const fromTMerc = this.quaternionFromTMercToEnu(proj);
292
+ return (coordinates, target = new THREE.Quaternion()) => fromTMerc(coordinates, target).conjugate();
293
+ },
294
+
295
+ /**
296
+ * Computes the rotation from a LongLat frame to the local East North Up (ENU) frame.
297
+ * The identity quaternion (0,0,0,1) is returned, as longlat and ENU frame are assumed to be aligned.
298
+ *
299
+ * @param {Coordinates} [coordinates] coordinates the origin of the local East North Up
300
+ * (ENU) frame
301
+ * @param {THREE.Quaternion} [target=new THREE.Quaternion()] output Quaternion
302
+ * @return {FunctionOrQuaternion} The target quaternion if coordinates is defined, otherwise, a function to compute it from coordinates.
303
+ */
304
+ quaternionFromLongLatToEnu(coordinates, target = new THREE.Quaternion()) {
305
+ return quaternionIdentity(coordinates, target);
306
+ },
307
+
308
+ /**
309
+ * Computes the rotation from the local East North Up (ENU) frame to a LongLat frame.
310
+ * The identity quaternion (0,0,0,1) is returned, as longlat and ENU frame are assumed to be aligned.
311
+ *
312
+ * @param {Coordinates} [coordinates] coordinates the origin of the local East North Up
313
+ * (ENU) frame
314
+ * @param {THREE.Quaternion} [target=new THREE.Quaternion()] output Quaternion
315
+ * @return {FunctionOrQuaternion} The target quaternion if coordinates is defined, otherwise, a function to compute it from coordinates.
316
+ */
317
+ quaternionFromEnuToLongLat(coordinates, target = new THREE.Quaternion()) {
318
+ return quaternionIdentity(coordinates, target);
319
+ },
320
+
321
+
322
+ /**
323
+ * Warns for an unimplemented projection, sets the quaternion to the
324
+ * identity (0,0,0,1).
325
+ *
326
+ * @param {Projection} proj - the unimplemented projection (may be parsed
327
+ * using proj4)
328
+ *
329
+ * @param {Coordinates} [coordinates] coordinates the origin of the local East North Up
330
+ * (ENU) frame
331
+ * @param {THREE.Quaternion} [target=new THREE.Quaternion()] output Quaternion
332
+ * @return {FunctionOrQuaternion} The target quaternion if coordinates is defined, otherwise, a function to compute it from coordinates.
333
+ */
334
+ quaternionUnimplemented(proj, coordinates, target = new THREE.Quaternion()) {
335
+ console.warn('This quaternion function is not implemented for projections of type', proj.projName);
336
+ return quaternionIdentity(coordinates, target);
337
+ },
338
+
339
+ /**
340
+ * Compute the quaternion that models the rotation from the local East North
341
+ * Up (ENU) frame to the frame of the given crs.
342
+ *
343
+ * @param {string|Projection} crsOrProj - the CRS of the target frame or its
344
+ * proj4-compatible object.
345
+ *
346
+ * @param {Coordinates} [coordinates] coordinates the origin of the local East North Up
347
+ * (ENU) frame
348
+ * @param {THREE.Quaternion} [target=new THREE.Quaternion()] output Quaternion
349
+ * @return {FunctionOrQuaternion} The target quaternion if coordinates is defined, otherwise, a function to compute it from coordinates.
350
+ */
351
+ quaternionFromEnuToCRS(crsOrProj, coordinates, target = new THREE.Quaternion()) {
352
+ if (coordinates) { return this.quaternionFromEnuToCRS(crsOrProj)(coordinates, target); }
353
+ const proj = crsOrProj.projName ? crsOrProj : proj4.defs(crsOrProj);
354
+ switch (proj.projName) {
355
+ case 'geocent': return this.quaternionFromEnuToGeocent();
356
+ case 'lcc': return this.quaternionFromEnuToLCC(proj);
357
+ case 'tmerc': return this.quaternionFromEnuToTMerc(proj);
358
+ case 'longlat': return this.quaternionFromEnuToLongLat();
359
+ default: return this.quaternionUnimplemented(proj);
360
+ }
361
+ },
362
+
363
+ /**
364
+ * Compute the quaternion that models the rotation from the frame of the
365
+ * given crs to the local East North Up (ENU) frame.
366
+ *
367
+ * @param {string|Projection} crsOrProj - the CRS of the source frame or its
368
+ * proj4-compatible object.
369
+ *
370
+ * @param {Coordinates} [coordinates] coordinates the origin of the local East North Up
371
+ * (ENU) frame
372
+ * @param {THREE.Quaternion} [target=new THREE.Quaternion()] output Quaternion
373
+ * @return {FunctionOrQuaternion} The target quaternion if coordinates is defined, otherwise, a function to compute it from coordinates.
374
+ */
375
+ quaternionFromCRSToEnu(crsOrProj, coordinates, target = new THREE.Quaternion()) {
376
+ if (coordinates) { return this.quaternionFromCRSToEnu(crsOrProj)(coordinates, target); }
377
+ const proj = crsOrProj.projName ? crsOrProj : proj4.defs(crsOrProj);
378
+ switch (proj.projName) {
379
+ case 'geocent': return this.quaternionFromGeocentToEnu();
380
+ case 'lcc': return this.quaternionFromLCCToEnu(proj);
381
+ case 'tmerc': return this.quaternionFromTMercToEnu(proj);
382
+ case 'longlat': return this.quaternionFromLongLatToEnu();
383
+ default: return this.quaternionUnimplemented(proj);
384
+ }
385
+ },
386
+
387
+ /**
388
+ * Return the function that computes the quaternion that represents a
389
+ * rotation of coordinates between two CRS frames.
390
+ *
391
+ * @param {string} crsIn - the CRS of the input frame.
392
+ * @param {string} crsOut - the CRS of the output frame.
393
+ * @param {Coordinates} [coordinates] coordinates - the origin of the local East North Up
394
+ * (ENU) frame
395
+ * @param {THREE.Quaternion} [target=new THREE.Quaternion()] output Quaternion
396
+ * @return {FunctionOrQuaternion} The target quaternion if coordinates is defined, otherwise, a function to compute it from coordinates.
397
+ */
398
+ quaternionFromCRSToCRS(crsIn, crsOut, coordinates, target = new THREE.Quaternion()) {
399
+ if (coordinates) { return this.quaternionFromCRSToCRS(crsIn, crsOut)(coordinates, target); }
400
+ if (crsIn == crsOut) {
401
+ return (origin, target = new THREE.Quaternion()) => target.set(0, 0, 0, 1);
402
+ }
403
+
404
+ // get rotations from the local East/North/Up (ENU) frame to both CRS.
405
+ const fromCrs = this.quaternionFromCRSToEnu(crsIn);
406
+ const toCrs = this.quaternionFromEnuToCRS(crsOut);
407
+ return (origin, target = new THREE.Quaternion()) =>
408
+ toCrs(origin, target).multiply(fromCrs(origin, quat));
409
+ },
410
+ };