@itowns/geographic 2.46.1-next.4 → 2.46.1-next.40
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/lib/Coordinates.js +7 -30
- package/lib/Crs.js +61 -8
- package/lib/Ellipsoid.js +5 -10
- package/lib/Extent.js +14 -33
- package/lib/OrientationUtils.js +41 -74
- package/package.json +5 -5
- package/src/Coordinates.ts +1 -15
- package/src/Crs.ts +76 -9
- package/src/OrientationUtils.ts +26 -8
package/lib/Coordinates.js
CHANGED
|
@@ -1,23 +1,11 @@
|
|
|
1
1
|
import { Vector3, MathUtils } from 'three';
|
|
2
|
-
import proj4 from 'proj4';
|
|
3
2
|
import Ellipsoid from "./Ellipsoid.js";
|
|
4
3
|
import * as CRS from "./Crs.js";
|
|
5
4
|
const ellipsoid = /* @__PURE__ */new Ellipsoid();
|
|
6
|
-
const projectionCache = {};
|
|
7
5
|
const v0 = /* @__PURE__ */new Vector3();
|
|
8
6
|
const v1 = /* @__PURE__ */new Vector3();
|
|
9
7
|
let coord0;
|
|
10
8
|
let coord1;
|
|
11
|
-
function proj4cache(crsIn, crsOut) {
|
|
12
|
-
if (!projectionCache[crsIn]) {
|
|
13
|
-
projectionCache[crsIn] = {};
|
|
14
|
-
}
|
|
15
|
-
if (!projectionCache[crsIn][crsOut]) {
|
|
16
|
-
projectionCache[crsIn][crsOut] = proj4(crsIn, crsOut);
|
|
17
|
-
}
|
|
18
|
-
return projectionCache[crsIn][crsOut];
|
|
19
|
-
}
|
|
20
|
-
|
|
21
9
|
/**
|
|
22
10
|
* A class representing a geographic or geocentric coordinate.
|
|
23
11
|
*
|
|
@@ -71,10 +59,7 @@ class Coordinates {
|
|
|
71
59
|
* @param y - y or latitude value.
|
|
72
60
|
* @param z - z or altitude value.
|
|
73
61
|
*/
|
|
74
|
-
constructor(crs) {
|
|
75
|
-
let x = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
|
|
76
|
-
let y = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0;
|
|
77
|
-
let z = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 0;
|
|
62
|
+
constructor(crs, x = 0, y = 0, z = 0) {
|
|
78
63
|
this.isCoordinates = true;
|
|
79
64
|
CRS.isValid(crs);
|
|
80
65
|
this.crs = crs;
|
|
@@ -122,10 +107,7 @@ class Coordinates {
|
|
|
122
107
|
* @param y - y or latitude value.
|
|
123
108
|
* @param z - z or altitude value.
|
|
124
109
|
*/
|
|
125
|
-
setFromValues() {
|
|
126
|
-
let x = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0;
|
|
127
|
-
let y = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
|
|
128
|
-
let z = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0;
|
|
110
|
+
setFromValues(x = 0, y = 0, z = 0) {
|
|
129
111
|
this.x = x;
|
|
130
112
|
this.y = y;
|
|
131
113
|
this.z = z;
|
|
@@ -142,8 +124,7 @@ class Coordinates {
|
|
|
142
124
|
* @param array - The source array.
|
|
143
125
|
* @param offset - Optional offset into the array. Default is 0.
|
|
144
126
|
*/
|
|
145
|
-
setFromArray(array) {
|
|
146
|
-
let offset = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
|
|
127
|
+
setFromArray(array, offset = 0) {
|
|
147
128
|
return this.setFromValues(array[offset], array[offset + 1], array[offset + 2]);
|
|
148
129
|
}
|
|
149
130
|
|
|
@@ -215,8 +196,7 @@ class Coordinates {
|
|
|
215
196
|
* @returns A vector `(x, y, z)`, or copies x, y and z into the provided
|
|
216
197
|
* vector.
|
|
217
198
|
*/
|
|
218
|
-
toVector3() {
|
|
219
|
-
let target = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : new Vector3();
|
|
199
|
+
toVector3(target = new Vector3()) {
|
|
220
200
|
return target.copy(this);
|
|
221
201
|
}
|
|
222
202
|
|
|
@@ -230,9 +210,7 @@ class Coordinates {
|
|
|
230
210
|
* @returns An array [x, y, z], or copies x, y and z into the provided
|
|
231
211
|
* array.
|
|
232
212
|
*/
|
|
233
|
-
toArray() {
|
|
234
|
-
let array = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
|
|
235
|
-
let offset = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
|
|
213
|
+
toArray(array = [], offset = 0) {
|
|
236
214
|
return Vector3.prototype.toArray.call(this, array, offset);
|
|
237
215
|
}
|
|
238
216
|
|
|
@@ -312,15 +290,14 @@ class Coordinates {
|
|
|
312
290
|
* const geographicCoords = geocentricCoords.as('EPSG:4326');
|
|
313
291
|
* ```
|
|
314
292
|
*/
|
|
315
|
-
as(crs) {
|
|
316
|
-
let target = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : new Coordinates(crs);
|
|
293
|
+
as(crs, target = new Coordinates(crs)) {
|
|
317
294
|
if (this.crs == crs) {
|
|
318
295
|
target.copy(this);
|
|
319
296
|
} else {
|
|
320
297
|
if (CRS.is4326(this.crs) && crs == 'EPSG:3857') {
|
|
321
298
|
this.y = MathUtils.clamp(this.y, -89.999999, 89.999999);
|
|
322
299
|
}
|
|
323
|
-
target.setFromArray(
|
|
300
|
+
target.setFromArray(CRS.transform(this.crs, crs).forward([this.x, this.y, this.z]));
|
|
324
301
|
}
|
|
325
302
|
target.crs = crs;
|
|
326
303
|
return target;
|
package/lib/Crs.js
CHANGED
|
@@ -12,7 +12,18 @@ proj4.defs('WGS84').axis = 'neu';
|
|
|
12
12
|
* projection definition previously defined with
|
|
13
13
|
* [`proj4.defs`](https://github.com/proj4js/proj4js#named-projections).
|
|
14
14
|
*/
|
|
15
|
+
// TODO Unify with OrientationUtils.js
|
|
15
16
|
|
|
17
|
+
const proj4Cache = {};
|
|
18
|
+
export function transform(crsIn, crsOut) {
|
|
19
|
+
if (!proj4Cache[crsIn]) {
|
|
20
|
+
proj4Cache[crsIn] = {};
|
|
21
|
+
}
|
|
22
|
+
if (!proj4Cache[crsIn][crsOut]) {
|
|
23
|
+
proj4Cache[crsIn][crsOut] = proj4(crsIn, crsOut);
|
|
24
|
+
}
|
|
25
|
+
return proj4Cache[crsIn][crsOut];
|
|
26
|
+
}
|
|
16
27
|
function isString(s) {
|
|
17
28
|
return typeof s === 'string' || s instanceof String;
|
|
18
29
|
}
|
|
@@ -56,7 +67,7 @@ function unitFromProj4Unit(proj) {
|
|
|
56
67
|
return UNIT.DEGREE;
|
|
57
68
|
} else if (proj.units === 'm' || proj.units === 'meter') {
|
|
58
69
|
return UNIT.METER;
|
|
59
|
-
} else if (proj.units === 'foot') {
|
|
70
|
+
} else if (proj.units === 'foot' || proj.units === 'ft') {
|
|
60
71
|
return UNIT.FOOT;
|
|
61
72
|
} else if (proj.units === undefined && proj.to_meter === undefined) {
|
|
62
73
|
// See https://proj.org/en/9.4/usage/projections.html [17/10/2024]
|
|
@@ -124,7 +135,7 @@ export function isGeocentric(crs) {
|
|
|
124
135
|
export function isValid(crs) {
|
|
125
136
|
const proj = proj4.defs(crs);
|
|
126
137
|
if (!proj) {
|
|
127
|
-
throw new Error(`Undefined crs '${crs}'. Add it with
|
|
138
|
+
throw new Error(`Undefined crs '${crs}'. Add it with itowns.CRS.defs('${crs}', wktString)`);
|
|
128
139
|
}
|
|
129
140
|
if (!unitFromProj4Unit(proj)) {
|
|
130
141
|
throw new Error(`No valid unit found for crs '${crs}', found ${proj.units}`);
|
|
@@ -160,11 +171,53 @@ export function axisOrder(crs) {
|
|
|
160
171
|
|
|
161
172
|
/**
|
|
162
173
|
* Defines a proj4 projection as a named alias.
|
|
163
|
-
* This function is
|
|
164
|
-
* [`proj4.defs`](https://github.com/proj4js/proj4js#named-projections)
|
|
165
|
-
|
|
174
|
+
* This function is an alias for the
|
|
175
|
+
* [`proj4.defs`](https://github.com/proj4js/proj4js#named-projections) function.
|
|
176
|
+
*/
|
|
177
|
+
export const defs = proj4.defs;
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Fetches a CRS definition from epsg.io and registers it with proj4.
|
|
181
|
+
* If the CRS is already defined, returns the existing definition.
|
|
182
|
+
*
|
|
183
|
+
* @param crs - The EPSG code string (e.g. "EPSG:2154").
|
|
184
|
+
* @returns The proj4 projection definition.
|
|
166
185
|
*
|
|
167
|
-
* @
|
|
168
|
-
*
|
|
186
|
+
* @example
|
|
187
|
+
* // Register EPSG:2154 (RGF93 / Lambert-93)
|
|
188
|
+
* await CRS.fromEPSG('EPSG:2154');
|
|
189
|
+
*
|
|
190
|
+
* // Register EPSG:4269 (NAD83)
|
|
191
|
+
* await CRS.fromEPSG('EPSG:4269');
|
|
169
192
|
*/
|
|
170
|
-
export
|
|
193
|
+
export async function fromEPSG(crs) {
|
|
194
|
+
const def = proj4.defs(crs);
|
|
195
|
+
if (def) {
|
|
196
|
+
return def;
|
|
197
|
+
}
|
|
198
|
+
const code = crs.replace(/^EPSG:/i, '');
|
|
199
|
+
const response = await fetch(`https://epsg.io/${code}.proj4`);
|
|
200
|
+
if (!response.ok) {
|
|
201
|
+
throw new Error(`Failed to fetch EPSG:${code} from epsg.io: ${response.status}`);
|
|
202
|
+
}
|
|
203
|
+
const proj4def = await response.text();
|
|
204
|
+
proj4.defs(crs, proj4def);
|
|
205
|
+
return proj4.defs(crs);
|
|
206
|
+
}
|
|
207
|
+
export function defsFromWkt(wkt) {
|
|
208
|
+
proj4.defs('unknown', wkt);
|
|
209
|
+
const proj4Defs = proj4.defs;
|
|
210
|
+
let projCS;
|
|
211
|
+
if (proj4Defs('unknown').type === 'COMPD_CS') {
|
|
212
|
+
console.warn('Compound coordinate system is not yet supported.');
|
|
213
|
+
projCS = proj4Defs('unknown').PROJCS;
|
|
214
|
+
} else {
|
|
215
|
+
projCS = proj4Defs('unknown');
|
|
216
|
+
}
|
|
217
|
+
const crsAlias = projCS.title || projCS.name || 'EPSG:XXXX';
|
|
218
|
+
if (!(crsAlias in proj4.defs)) {
|
|
219
|
+
proj4.defs(crsAlias, projCS);
|
|
220
|
+
}
|
|
221
|
+
delete proj4Defs.unknown;
|
|
222
|
+
return crsAlias;
|
|
223
|
+
}
|
package/lib/Ellipsoid.js
CHANGED
|
@@ -21,8 +21,7 @@ class Ellipsoid {
|
|
|
21
21
|
* @param size - Length of the semi-axes of the ellipsoid. Defaults to those
|
|
22
22
|
* defined by the WGS84 ellipsoid.
|
|
23
23
|
*/
|
|
24
|
-
constructor() {
|
|
25
|
-
let size = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ellipsoidSizes;
|
|
24
|
+
constructor(size = ellipsoidSizes) {
|
|
26
25
|
this.size = new Vector3();
|
|
27
26
|
this._radiiSquared = new Vector3();
|
|
28
27
|
this._invRadiiSquared = new Vector3();
|
|
@@ -38,8 +37,7 @@ class Ellipsoid {
|
|
|
38
37
|
* @param target - An object to store this vector to. If this is not
|
|
39
38
|
* specified, a new vector will be created.
|
|
40
39
|
*/
|
|
41
|
-
geodeticSurfaceNormal(cartesian) {
|
|
42
|
-
let target = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : new Vector3();
|
|
40
|
+
geodeticSurfaceNormal(cartesian, target = new Vector3()) {
|
|
43
41
|
return cartesian.toVector3(target).multiply(this._invRadiiSquared).normalize();
|
|
44
42
|
}
|
|
45
43
|
|
|
@@ -51,8 +49,7 @@ class Ellipsoid {
|
|
|
51
49
|
* @param target - An object to store this vector to. If this is not
|
|
52
50
|
* specified, a new vector will be created.
|
|
53
51
|
*/
|
|
54
|
-
geodeticSurfaceNormalCartographic(coordCarto) {
|
|
55
|
-
let target = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : new Vector3();
|
|
52
|
+
geodeticSurfaceNormalCartographic(coordCarto, target = new Vector3()) {
|
|
56
53
|
const longitude = MathUtils.degToRad(coordCarto.longitude);
|
|
57
54
|
const latitude = MathUtils.degToRad(coordCarto.latitude);
|
|
58
55
|
const cosLatitude = Math.cos(latitude);
|
|
@@ -75,8 +72,7 @@ class Ellipsoid {
|
|
|
75
72
|
this.eccentricity = Math.sqrt(this._radiiSquared.x - this._radiiSquared.z) / this.size.x;
|
|
76
73
|
return this;
|
|
77
74
|
}
|
|
78
|
-
cartographicToCartesian(coordCarto) {
|
|
79
|
-
let target = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : new Vector3();
|
|
75
|
+
cartographicToCartesian(coordCarto, target = new Vector3()) {
|
|
80
76
|
normal.copy(coordCarto.geodesicNormal);
|
|
81
77
|
target.multiplyVectors(this._radiiSquared, normal);
|
|
82
78
|
const gamma = Math.sqrt(normal.dot(target));
|
|
@@ -93,8 +89,7 @@ class Ellipsoid {
|
|
|
93
89
|
* @returns an object describing the coordinates on the reference ellipsoid,
|
|
94
90
|
* angles are in degree
|
|
95
91
|
*/
|
|
96
|
-
cartesianToCartographic(position) {
|
|
97
|
-
let target = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : new Coordinates('EPSG:4326', 0, 0, 0);
|
|
92
|
+
cartesianToCartographic(position, target = new Coordinates('EPSG:4326', 0, 0, 0)) {
|
|
98
93
|
// for details, see for example http://www.linz.govt.nz/data/geodetic-system/coordinate-conversion/geodetic-datum-conversions/equations-used-datum
|
|
99
94
|
// TODO the following is only valable for oblate ellipsoid of
|
|
100
95
|
// revolution. do we want to support triaxial ellipsoid?
|
package/lib/Extent.js
CHANGED
|
@@ -54,11 +54,7 @@ class Extent {
|
|
|
54
54
|
* @param south - the `south` value of this extent. Default is 0.
|
|
55
55
|
* @param north - the `north` value of this extent. Default is 0.
|
|
56
56
|
*/
|
|
57
|
-
constructor(crs) {
|
|
58
|
-
let west = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
|
|
59
|
-
let east = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0;
|
|
60
|
-
let south = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 0;
|
|
61
|
-
let north = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : 0;
|
|
57
|
+
constructor(crs, west = 0, east = 0, south = 0, north = 0) {
|
|
62
58
|
if (CRS.isGeocentric(crs)) {
|
|
63
59
|
throw new Error(`Non-compatible geocentric projection ${crs} to build a geographical extent`);
|
|
64
60
|
}
|
|
@@ -85,8 +81,7 @@ class Extent {
|
|
|
85
81
|
* @param target - The target to store the projected extent. If this not
|
|
86
82
|
* provided a new extent will be created.
|
|
87
83
|
*/
|
|
88
|
-
as(crs) {
|
|
89
|
-
let target = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : new Extent('EPSG:4326');
|
|
84
|
+
as(crs, target = new Extent('EPSG:4326')) {
|
|
90
85
|
CRS.isValid(crs);
|
|
91
86
|
if (this.crs != crs) {
|
|
92
87
|
// Compute min/max in x/y by projecting 8 cardinal points,
|
|
@@ -126,8 +121,7 @@ class Extent {
|
|
|
126
121
|
* @param target - The target to store the center coordinate. If this not
|
|
127
122
|
* provided a new coordinate will be created.
|
|
128
123
|
*/
|
|
129
|
-
center() {
|
|
130
|
-
let target = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : new Coordinates(this.crs);
|
|
124
|
+
center(target = new Coordinates(this.crs)) {
|
|
131
125
|
this.planarDimensions(_dim);
|
|
132
126
|
target.crs = this.crs;
|
|
133
127
|
target.setFromValues(this.west + _dim.x * 0.5, this.south + _dim.y * 0.5);
|
|
@@ -142,8 +136,7 @@ class Extent {
|
|
|
142
136
|
*
|
|
143
137
|
* @param target - optional target
|
|
144
138
|
*/
|
|
145
|
-
planarDimensions() {
|
|
146
|
-
let target = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : new Vector2();
|
|
139
|
+
planarDimensions(target = new Vector2()) {
|
|
147
140
|
// Calculte the dimensions for x and y
|
|
148
141
|
return target.set(Math.abs(this.east - this.west), Math.abs(this.north - this.south));
|
|
149
142
|
}
|
|
@@ -156,8 +149,7 @@ class Extent {
|
|
|
156
149
|
*
|
|
157
150
|
* @param target - optional target
|
|
158
151
|
*/
|
|
159
|
-
geodeticDimensions() {
|
|
160
|
-
let target = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : new Vector2();
|
|
152
|
+
geodeticDimensions(target = new Vector2()) {
|
|
161
153
|
// set 3 corners extent
|
|
162
154
|
cNorthWest.crs = this.crs;
|
|
163
155
|
cSouthWest.crs = this.crs;
|
|
@@ -177,8 +169,7 @@ class Extent {
|
|
|
177
169
|
*
|
|
178
170
|
* @param target - optional target
|
|
179
171
|
*/
|
|
180
|
-
spatialEuclideanDimensions() {
|
|
181
|
-
let target = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : new Vector2();
|
|
172
|
+
spatialEuclideanDimensions(target = new Vector2()) {
|
|
182
173
|
// set 3 corners extent
|
|
183
174
|
cNorthWest.crs = this.crs;
|
|
184
175
|
cSouthWest.crs = this.crs;
|
|
@@ -198,8 +189,7 @@ class Extent {
|
|
|
198
189
|
* @param epsilon - error margin when comparing to the coordinates.
|
|
199
190
|
* Default is 0.
|
|
200
191
|
*/
|
|
201
|
-
isPointInside(coord) {
|
|
202
|
-
let epsilon = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
|
|
192
|
+
isPointInside(coord, epsilon = 0) {
|
|
203
193
|
if (this.crs == coord.crs) {
|
|
204
194
|
_c.copy(coord);
|
|
205
195
|
} else {
|
|
@@ -216,8 +206,7 @@ class Extent {
|
|
|
216
206
|
* @param extent - the extent to check
|
|
217
207
|
* @param epsilon - error margin when comparing the extent bounds.
|
|
218
208
|
*/
|
|
219
|
-
isInside(extent) {
|
|
220
|
-
let epsilon = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : CRS.reasonableEpsilon(this.crs);
|
|
209
|
+
isInside(extent, epsilon = CRS.reasonableEpsilon(this.crs)) {
|
|
221
210
|
extent.as(this.crs, _extent);
|
|
222
211
|
return this.east - _extent.east <= epsilon && _extent.west - this.west <= epsilon && this.north - _extent.north <= epsilon && _extent.south - this.south <= epsilon;
|
|
223
212
|
}
|
|
@@ -233,8 +222,7 @@ class Extent {
|
|
|
233
222
|
* south-north, the `z` property the scale on west-east, the `w` property
|
|
234
223
|
* the scale on south-north.
|
|
235
224
|
*/
|
|
236
|
-
offsetToParent(extent) {
|
|
237
|
-
let target = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : new Vector4();
|
|
225
|
+
offsetToParent(extent, target = new Vector4()) {
|
|
238
226
|
if (this.crs != extent.crs) {
|
|
239
227
|
throw new Error('unsupported mix');
|
|
240
228
|
}
|
|
@@ -314,8 +302,7 @@ class Extent {
|
|
|
314
302
|
* @param array - the source array
|
|
315
303
|
* @param offset - offset into the array. Default is 0.
|
|
316
304
|
*/
|
|
317
|
-
setFromArray(array) {
|
|
318
|
-
let offset = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
|
|
305
|
+
setFromArray(array, offset = 0) {
|
|
319
306
|
this.west = array[offset];
|
|
320
307
|
this.east = array[offset + 1];
|
|
321
308
|
this.south = array[offset + 2];
|
|
@@ -439,8 +426,7 @@ class Extent {
|
|
|
439
426
|
* Return values of extent in string, separated by the separator input.
|
|
440
427
|
* @param sep - string separator
|
|
441
428
|
*/
|
|
442
|
-
toString() {
|
|
443
|
-
let sep = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
|
|
429
|
+
toString(sep = '') {
|
|
444
430
|
return `${this.east}${sep}${this.north}${sep}${this.west}${sep}${this.south}`;
|
|
445
431
|
}
|
|
446
432
|
|
|
@@ -461,8 +447,7 @@ class Extent {
|
|
|
461
447
|
* @param scheme - The scheme to subdivise.
|
|
462
448
|
* @returns subdivised extents.
|
|
463
449
|
*/
|
|
464
|
-
subdivisionByScheme() {
|
|
465
|
-
let scheme = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : defaultScheme;
|
|
450
|
+
subdivisionByScheme(scheme = defaultScheme) {
|
|
466
451
|
const subdivisedExtents = [];
|
|
467
452
|
const dimSub = this.planarDimensions(_dim).divide(scheme);
|
|
468
453
|
for (let x = scheme.x - 1; x >= 0; x--) {
|
|
@@ -509,9 +494,7 @@ class Extent {
|
|
|
509
494
|
* @param north - The max north
|
|
510
495
|
* @returns this extent
|
|
511
496
|
*/
|
|
512
|
-
clampSouthNorth() {
|
|
513
|
-
let south = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.south;
|
|
514
|
-
let north = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.north;
|
|
497
|
+
clampSouthNorth(south = this.south, north = this.north) {
|
|
515
498
|
this.south = Math.max(this.south, south);
|
|
516
499
|
this.north = Math.min(this.north, north);
|
|
517
500
|
return this;
|
|
@@ -524,9 +507,7 @@ class Extent {
|
|
|
524
507
|
* @param east - The max east
|
|
525
508
|
* @returns this extent
|
|
526
509
|
*/
|
|
527
|
-
clampWestEast() {
|
|
528
|
-
let west = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.west;
|
|
529
|
-
let east = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.east;
|
|
510
|
+
clampWestEast(west = this.west, east = this.east) {
|
|
530
511
|
this.west = Math.max(this.west, west);
|
|
531
512
|
this.east = Math.min(this.east, east);
|
|
532
513
|
return this;
|
package/lib/OrientationUtils.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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,12 +255,8 @@ 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
|
-
|
|
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
|
|
@@ -300,13 +268,9 @@ export function quaternionFromEnuToLongLat(coordinates) {
|
|
|
300
268
|
* @returns The target quaternion if coordinates is defined, otherwise, a
|
|
301
269
|
* function to compute it from coordinates.
|
|
302
270
|
*/
|
|
303
|
-
export function quaternionUnimplemented(proj, coordinates) {
|
|
304
|
-
let target = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : new Quaternion();
|
|
271
|
+
export function quaternionUnimplemented(proj, coordinates, target = new Quaternion()) {
|
|
305
272
|
console.warn('This quaternion function is not implemented for projections of type', proj.projName);
|
|
306
|
-
return coordinates ? target.set(0, 0, 0, 1) :
|
|
307
|
-
let target = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : new Quaternion();
|
|
308
|
-
return quaternionUnimplemented(proj, coordinates, target);
|
|
309
|
-
};
|
|
273
|
+
return coordinates ? target.set(0, 0, 0, 1) : (coordinates, target = new Quaternion()) => quaternionUnimplemented(proj, coordinates, target);
|
|
310
274
|
}
|
|
311
275
|
/**
|
|
312
276
|
* Compute the quaternion that models the rotation from the local East North
|
|
@@ -319,8 +283,7 @@ export function quaternionUnimplemented(proj, coordinates) {
|
|
|
319
283
|
* @returns The target quaternion if coordinates is defined, otherwise, a
|
|
320
284
|
* function to compute it from coordinates.
|
|
321
285
|
*/
|
|
322
|
-
export function quaternionFromEnuToCRS(crsOrProj, coordinates) {
|
|
323
|
-
let target = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : new Quaternion();
|
|
286
|
+
export function quaternionFromEnuToCRS(crsOrProj, coordinates, target = new Quaternion()) {
|
|
324
287
|
if (coordinates) {
|
|
325
288
|
return quaternionFromEnuToCRS(crsOrProj)(coordinates, target);
|
|
326
289
|
}
|
|
@@ -331,6 +294,7 @@ export function quaternionFromEnuToCRS(crsOrProj, coordinates) {
|
|
|
331
294
|
return quaternionFromEnuToGeocent();
|
|
332
295
|
case 'Lambert Tangential Conformal Conic Projection':
|
|
333
296
|
return quaternionFromEnuToLCC(proj);
|
|
297
|
+
case 'Universal Transverse Mercator System':
|
|
334
298
|
case 'Fast_Transverse_Mercator':
|
|
335
299
|
return quaternionFromEnuToTMerc(proj);
|
|
336
300
|
case 'longlat':
|
|
@@ -350,8 +314,7 @@ export function quaternionFromEnuToCRS(crsOrProj, coordinates) {
|
|
|
350
314
|
* @returns The target quaternion if coordinates is defined, otherwise, a
|
|
351
315
|
* function to compute it from coordinates.
|
|
352
316
|
*/
|
|
353
|
-
export function quaternionFromCRSToEnu(crsOrProj, coordinates) {
|
|
354
|
-
let target = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : new Quaternion();
|
|
317
|
+
export function quaternionFromCRSToEnu(crsOrProj, coordinates, target = new Quaternion()) {
|
|
355
318
|
if (coordinates) {
|
|
356
319
|
return quaternionFromCRSToEnu(crsOrProj)(coordinates, target);
|
|
357
320
|
}
|
|
@@ -362,6 +325,7 @@ export function quaternionFromCRSToEnu(crsOrProj, coordinates) {
|
|
|
362
325
|
return quaternionFromGeocentToEnu();
|
|
363
326
|
case 'Lambert Tangential Conformal Conic Projection':
|
|
364
327
|
return quaternionFromLCCToEnu(proj);
|
|
328
|
+
case 'Universal Transverse Mercator System':
|
|
365
329
|
case 'Fast_Transverse_Mercator':
|
|
366
330
|
return quaternionFromTMercToEnu(proj);
|
|
367
331
|
case 'longlat':
|
|
@@ -370,6 +334,7 @@ export function quaternionFromCRSToEnu(crsOrProj, coordinates) {
|
|
|
370
334
|
return quaternionUnimplemented(proj);
|
|
371
335
|
}
|
|
372
336
|
}
|
|
337
|
+
const quaternionCrs2CrsCache = {};
|
|
373
338
|
/**
|
|
374
339
|
* Return the function that computes the quaternion that represents a
|
|
375
340
|
* rotation of coordinates between two CRS frames.
|
|
@@ -381,23 +346,25 @@ export function quaternionFromCRSToEnu(crsOrProj, coordinates) {
|
|
|
381
346
|
* @returns The target quaternion if coordinates is defined, otherwise, a
|
|
382
347
|
* function to compute it from coordinates.
|
|
383
348
|
*/
|
|
384
|
-
export function quaternionFromCRSToCRS(crsIn, crsOut, coordinates) {
|
|
385
|
-
let target = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : new Quaternion();
|
|
349
|
+
export function quaternionFromCRSToCRS(crsIn, crsOut, coordinates, target = new Quaternion()) {
|
|
386
350
|
if (coordinates) {
|
|
387
351
|
return quaternionFromCRSToCRS(crsIn, crsOut)(coordinates, target);
|
|
388
352
|
}
|
|
389
353
|
if (crsIn == crsOut) {
|
|
390
|
-
return
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
354
|
+
return (origin, target = new Quaternion()) => target.set(0, 0, 0, 1);
|
|
355
|
+
}
|
|
356
|
+
if (quaternionCrs2CrsCache[crsIn] && quaternionCrs2CrsCache[crsIn][crsOut]) {
|
|
357
|
+
return quaternionCrs2CrsCache[crsIn][crsOut];
|
|
394
358
|
}
|
|
395
359
|
|
|
396
360
|
// get rotations from the local East/North/Up (ENU) frame to both CRS.
|
|
397
361
|
const fromCrs = quaternionFromCRSToEnu(crsIn);
|
|
398
362
|
const toCrs = quaternionFromEnuToCRS(crsOut);
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
363
|
+
if (!quaternionCrs2CrsCache[crsIn]) {
|
|
364
|
+
quaternionCrs2CrsCache[crsIn] = {};
|
|
365
|
+
}
|
|
366
|
+
if (!quaternionCrs2CrsCache[crsIn][crsOut]) {
|
|
367
|
+
quaternionCrs2CrsCache[crsIn][crsOut] = (origin, target = new Quaternion()) => toCrs(origin, target).multiply(fromCrs(origin, quat));
|
|
368
|
+
}
|
|
369
|
+
return quaternionCrs2CrsCache[crsIn][crsOut];
|
|
403
370
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@itowns/geographic",
|
|
3
|
-
"version": "2.46.1-next.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "2.46.1-next.40",
|
|
4
|
+
"description": "Proj4-based three.js geodesy toolkit",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
7
7
|
"exports": {
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"lint": "eslint \"src/**/*.{js,ts,tsx}\" \"test/**/*.js\"",
|
|
15
15
|
"transpile": "tsc && cross-env BABEL_DISABLE_CACHE=1 babel src --out-dir lib --extensions .js,.ts",
|
|
16
16
|
"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",
|
|
17
|
+
"base-test-unit": "cross-env BABEL_DISABLE_CACHE=1 NODE_OPTIONS=--no-experimental-strip-types mocha --import=../../config/babel-register/register.mjs",
|
|
18
18
|
"test-with-coverage": "c8 -n src -r html cross-env npm run test-unit",
|
|
19
19
|
"test-with-coverage_lcov": "c8 -n src --reporter=lcov cross-env npm run test-unit",
|
|
20
20
|
"watch": "npm run transpile -- --watch",
|
|
@@ -35,8 +35,8 @@
|
|
|
35
35
|
"url": "https://github.com/itowns/itowns/issues"
|
|
36
36
|
},
|
|
37
37
|
"peerDependencies": {
|
|
38
|
-
"proj4": "
|
|
39
|
-
"three": "
|
|
38
|
+
"proj4": ">=2.20.0",
|
|
39
|
+
"three": ">=0.182.0"
|
|
40
40
|
},
|
|
41
41
|
"homepage": "https://itowns.github.io/"
|
|
42
42
|
}
|
package/src/Coordinates.ts
CHANGED
|
@@ -1,12 +1,10 @@
|
|
|
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();
|
|
@@ -21,18 +19,6 @@ export interface CoordinatesLike {
|
|
|
21
19
|
readonly z: number;
|
|
22
20
|
}
|
|
23
21
|
|
|
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
22
|
/**
|
|
37
23
|
* A class representing a geographic or geocentric coordinate.
|
|
38
24
|
*
|
|
@@ -348,7 +334,7 @@ class Coordinates {
|
|
|
348
334
|
this.y = MathUtils.clamp(this.y, -89.999999, 89.999999);
|
|
349
335
|
}
|
|
350
336
|
|
|
351
|
-
target.setFromArray(
|
|
337
|
+
target.setFromArray(CRS.transform(this.crs, crs)
|
|
352
338
|
.forward([this.x, this.y, this.z]));
|
|
353
339
|
}
|
|
354
340
|
|
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
|
+
type 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
|
}
|
|
@@ -62,7 +84,7 @@ function unitFromProj4Unit(proj: ProjectionDefinition) {
|
|
|
62
84
|
return UNIT.DEGREE;
|
|
63
85
|
} else if (proj.units === 'm' || proj.units === 'meter') {
|
|
64
86
|
return UNIT.METER;
|
|
65
|
-
} else if (proj.units === 'foot') {
|
|
87
|
+
} else if (proj.units === 'foot' || proj.units === 'ft') {
|
|
66
88
|
return UNIT.FOOT;
|
|
67
89
|
} else if (proj.units === undefined && proj.to_meter === undefined) {
|
|
68
90
|
// See https://proj.org/en/9.4/usage/projections.html [17/10/2024]
|
|
@@ -130,7 +152,7 @@ export function isGeocentric(crs: ProjectionLike) {
|
|
|
130
152
|
export function isValid(crs: ProjectionLike) {
|
|
131
153
|
const proj = proj4.defs(crs);
|
|
132
154
|
if (!proj) {
|
|
133
|
-
throw new Error(`Undefined crs '${crs}'. Add it with
|
|
155
|
+
throw new Error(`Undefined crs '${crs}'. Add it with itowns.CRS.defs('${crs}', wktString)`);
|
|
134
156
|
}
|
|
135
157
|
if (!unitFromProj4Unit(proj)) {
|
|
136
158
|
throw new Error(`No valid unit found for crs '${crs}', found ${proj.units}`);
|
|
@@ -166,11 +188,56 @@ export function axisOrder(crs: ProjectionLike) {
|
|
|
166
188
|
|
|
167
189
|
/**
|
|
168
190
|
* Defines a proj4 projection as a named alias.
|
|
169
|
-
* This function is
|
|
170
|
-
* [`proj4.defs`](https://github.com/proj4js/proj4js#named-projections)
|
|
171
|
-
|
|
191
|
+
* This function is an alias for the
|
|
192
|
+
* [`proj4.defs`](https://github.com/proj4js/proj4js#named-projections) function.
|
|
193
|
+
*/
|
|
194
|
+
export const defs = proj4.defs;
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Fetches a CRS definition from epsg.io and registers it with proj4.
|
|
198
|
+
* If the CRS is already defined, returns the existing definition.
|
|
172
199
|
*
|
|
173
|
-
* @param
|
|
174
|
-
* @
|
|
200
|
+
* @param crs - The EPSG code string (e.g. "EPSG:2154").
|
|
201
|
+
* @returns The proj4 projection definition.
|
|
202
|
+
*
|
|
203
|
+
* @example
|
|
204
|
+
* // Register EPSG:2154 (RGF93 / Lambert-93)
|
|
205
|
+
* await CRS.fromEPSG('EPSG:2154');
|
|
206
|
+
*
|
|
207
|
+
* // Register EPSG:4269 (NAD83)
|
|
208
|
+
* await CRS.fromEPSG('EPSG:4269');
|
|
175
209
|
*/
|
|
176
|
-
export
|
|
210
|
+
export async function fromEPSG(crs: string): Promise<ProjectionDefinition> {
|
|
211
|
+
const def = proj4.defs(crs);
|
|
212
|
+
if (def) {
|
|
213
|
+
return def;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const code = crs.replace(/^EPSG:/i, '');
|
|
217
|
+
const response = await fetch(`https://epsg.io/${code}.proj4`);
|
|
218
|
+
if (!response.ok) {
|
|
219
|
+
throw new Error(`Failed to fetch EPSG:${code} from epsg.io: ${response.status}`);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const proj4def = await response.text();
|
|
223
|
+
proj4.defs(crs, proj4def);
|
|
224
|
+
return proj4.defs(crs);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
export function defsFromWkt(wkt: string): string {
|
|
228
|
+
proj4.defs('unknown', wkt);
|
|
229
|
+
const proj4Defs = proj4.defs as unknown as proj4Def;
|
|
230
|
+
let projCS;
|
|
231
|
+
if (proj4Defs('unknown').type === 'COMPD_CS') {
|
|
232
|
+
console.warn('Compound coordinate system is not yet supported.');
|
|
233
|
+
projCS = proj4Defs('unknown').PROJCS;
|
|
234
|
+
} else {
|
|
235
|
+
projCS = proj4Defs('unknown');
|
|
236
|
+
}
|
|
237
|
+
const crsAlias = (projCS.title || projCS.name || 'EPSG:XXXX');
|
|
238
|
+
if (!(crsAlias in proj4.defs)) {
|
|
239
|
+
proj4.defs(crsAlias, projCS);
|
|
240
|
+
}
|
|
241
|
+
delete proj4Defs.unknown;
|
|
242
|
+
return crsAlias;
|
|
243
|
+
}
|
package/src/OrientationUtils.ts
CHANGED
|
@@ -32,6 +32,7 @@ type Attitude = Partial<EulerAngles> | Partial<PhotogrammetryAngles>;
|
|
|
32
32
|
|
|
33
33
|
type QuaternionFunction = (coords: Coordinates, target?: Quaternion) => Quaternion;
|
|
34
34
|
|
|
35
|
+
// To unify with Crs.js ProjectionLike type ?
|
|
35
36
|
type ProjectionLike = ProjectionDefinition | string;
|
|
36
37
|
type LCCProjection = { long0: number, lat0: number };
|
|
37
38
|
type TMercProjection = { a: number, b: number, e?: number, long0: number };
|
|
@@ -416,6 +417,7 @@ export function quaternionFromEnuToCRS(
|
|
|
416
417
|
case 'Geocentric': return quaternionFromEnuToGeocent();
|
|
417
418
|
case 'Lambert Tangential Conformal Conic Projection':
|
|
418
419
|
return quaternionFromEnuToLCC(proj as LCCProjection);
|
|
420
|
+
case 'Universal Transverse Mercator System':
|
|
419
421
|
case 'Fast_Transverse_Mercator': return quaternionFromEnuToTMerc(proj as TMercProjection);
|
|
420
422
|
case 'longlat': return quaternionFromEnuToLongLat();
|
|
421
423
|
default: return quaternionUnimplemented(proj);
|
|
@@ -453,19 +455,21 @@ export function quaternionFromCRSToEnu(
|
|
|
453
455
|
case 'Geocentric': return quaternionFromGeocentToEnu();
|
|
454
456
|
case 'Lambert Tangential Conformal Conic Projection':
|
|
455
457
|
return quaternionFromLCCToEnu(proj as LCCProjection);
|
|
458
|
+
case 'Universal Transverse Mercator System':
|
|
456
459
|
case 'Fast_Transverse_Mercator': return quaternionFromTMercToEnu(proj as TMercProjection);
|
|
457
460
|
case 'longlat': return quaternionFromLongLatToEnu();
|
|
458
461
|
default: return quaternionUnimplemented(proj);
|
|
459
462
|
}
|
|
460
463
|
}
|
|
461
464
|
|
|
465
|
+
const quaternionCrs2CrsCache: Record<string, Record<string, QuaternionFunction>> = {};
|
|
462
466
|
export function quaternionFromCRSToCRS(
|
|
463
|
-
crsIn:
|
|
464
|
-
crsOut:
|
|
467
|
+
crsIn: string,
|
|
468
|
+
crsOut: string,
|
|
465
469
|
): QuaternionFunction;
|
|
466
470
|
export function quaternionFromCRSToCRS(
|
|
467
|
-
crsIn:
|
|
468
|
-
crsOut:
|
|
471
|
+
crsIn: string,
|
|
472
|
+
crsOut: string,
|
|
469
473
|
coords: Coordinates,
|
|
470
474
|
target?: Quaternion,
|
|
471
475
|
): Quaternion;
|
|
@@ -481,8 +485,8 @@ export function quaternionFromCRSToCRS(
|
|
|
481
485
|
* function to compute it from coordinates.
|
|
482
486
|
*/
|
|
483
487
|
export function quaternionFromCRSToCRS(
|
|
484
|
-
crsIn:
|
|
485
|
-
crsOut:
|
|
488
|
+
crsIn: string,
|
|
489
|
+
crsOut: string,
|
|
486
490
|
coordinates?: Coordinates,
|
|
487
491
|
target = new Quaternion(),
|
|
488
492
|
) {
|
|
@@ -491,9 +495,23 @@ export function quaternionFromCRSToCRS(
|
|
|
491
495
|
return (origin: Coordinates, target = new Quaternion()) => target.set(0, 0, 0, 1);
|
|
492
496
|
}
|
|
493
497
|
|
|
498
|
+
if (quaternionCrs2CrsCache[crsIn] && quaternionCrs2CrsCache[crsIn][crsOut]) {
|
|
499
|
+
return quaternionCrs2CrsCache[crsIn][crsOut];
|
|
500
|
+
}
|
|
501
|
+
|
|
494
502
|
// get rotations from the local East/North/Up (ENU) frame to both CRS.
|
|
495
503
|
const fromCrs = quaternionFromCRSToEnu(crsIn);
|
|
496
504
|
const toCrs = quaternionFromEnuToCRS(crsOut);
|
|
497
|
-
|
|
498
|
-
|
|
505
|
+
|
|
506
|
+
if (!quaternionCrs2CrsCache[crsIn]) {
|
|
507
|
+
quaternionCrs2CrsCache[crsIn] = {};
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
if (!quaternionCrs2CrsCache[crsIn][crsOut]) {
|
|
511
|
+
quaternionCrs2CrsCache[crsIn][crsOut] =
|
|
512
|
+
(origin: Coordinates, target = new Quaternion()) =>
|
|
513
|
+
toCrs(origin, target).multiply(fromCrs(origin, quat));
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
return quaternionCrs2CrsCache[crsIn][crsOut];
|
|
499
517
|
}
|