@itowns/geographic 2.45.1-next.0 → 2.45.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/Crs.d.ts ADDED
@@ -0,0 +1,93 @@
1
+ /**
2
+ * A projection as a CRS identifier string. This identifier references a
3
+ * projection definition previously defined with
4
+ * [`proj4.defs`](https://github.com/proj4js/proj4js#named-projections).
5
+ */
6
+ export type ProjectionLike = string;
7
+ /**
8
+ * System units supported for a coordinate system. See
9
+ * [proj](https://proj4.org/en/9.5/operations/conversions/unitconvert.html#angular-units).
10
+ * Note that only degree and meters units are supported for now.
11
+ */
12
+ export declare const UNIT: {
13
+ /**
14
+ * Angular unit in degree.
15
+ */
16
+ readonly DEGREE: 1;
17
+ /**
18
+ * Distance unit in meter.
19
+ */
20
+ readonly METER: 2;
21
+ /**
22
+ * Distance unit in foot.
23
+ */
24
+ readonly FOOT: 3;
25
+ };
26
+ /**
27
+ * Checks that the CRS is EPSG:4326.
28
+ * @internal
29
+ *
30
+ * @param crs - The CRS to test.
31
+ */
32
+ export declare function is4326(crs: ProjectionLike): crs is "EPSG:4326";
33
+ /**
34
+ * Returns the horizontal coordinates system units associated with this CRS.
35
+ *
36
+ * @param crs - The CRS to extract the unit from.
37
+ * @returns Either `UNIT.METER`, `UNIT.DEGREE`, `UNIT.FOOT` or `undefined`.
38
+ */
39
+ export declare function getUnit(crs: ProjectionLike): 1 | 2 | 3 | undefined;
40
+ /**
41
+ * Asserts that the CRS is using metric units.
42
+ *
43
+ * @param crs - The CRS to check.
44
+ * @throws {@link Error} if the CRS is not valid.
45
+ */
46
+ export declare function isMetricUnit(crs: ProjectionLike): boolean;
47
+ /**
48
+ * Asserts that the CRS is geographic.
49
+ *
50
+ * @param crs - The CRS to check.
51
+ * @throws {@link Error} if the CRS is not valid.
52
+ */
53
+ export declare function isGeographic(crs: ProjectionLike): boolean;
54
+ /**
55
+ * Asserts that the CRS is geocentric.
56
+ *
57
+ * @param crs - The CRS to test.
58
+ * @returns false if the crs isn't defined.
59
+ */
60
+ export declare function isGeocentric(crs: ProjectionLike): boolean;
61
+ /**
62
+ * Asserts that the CRS is valid, meaning it has been previously defined and
63
+ * includes an unit.
64
+ *
65
+ * @param crs - The CRS to test.
66
+ * @throws {@link Error} if the crs is not valid.
67
+ */
68
+ export declare function isValid(crs: ProjectionLike): void;
69
+ /**
70
+ * Gives a reasonable epsilon for this CRS.
71
+ *
72
+ * @param crs - The CRS to use.
73
+ * @returns 0.01 if the CRS is EPSG:4326, 0.001 otherwise.
74
+ */
75
+ export declare function reasonableEpsilon(crs: ProjectionLike): 0.01 | 0.001;
76
+ /**
77
+ * Returns the axis parameter defined in proj4 for the provided crs.
78
+ * Might be undefined depending on crs definition.
79
+ *
80
+ * @param crs - The CRS to get axis from.
81
+ * @returns the matching proj4 axis string, 'enu' for instance (east, north, up)
82
+ */
83
+ export declare function axisOrder(crs: ProjectionLike): string | undefined;
84
+ /**
85
+ * Defines a proj4 projection as a named alias.
86
+ * This function is a specialized wrapper over the
87
+ * [`proj4.defs`](https://github.com/proj4js/proj4js#named-projections)
88
+ * function.
89
+ *
90
+ * @param code - Named alias of the currently defined projection.
91
+ * @param proj4def - Proj4 or WKT string of the defined projection.
92
+ */
93
+ export declare const defs: (code: string, proj4def: string) => void;
package/lib/Crs.js ADDED
@@ -0,0 +1,170 @@
1
+ import proj4 from 'proj4';
2
+ proj4.defs('EPSG:4978', '+proj=geocent +datum=WGS84 +units=m +no_defs');
3
+
4
+ // Redefining proj4 global projections to match epsg.org database axis order.
5
+ // See https://github.com/iTowns/itowns/pull/2465#issuecomment-2517024859
6
+ proj4.defs('EPSG:4326').axis = 'neu';
7
+ proj4.defs('EPSG:4269').axis = 'neu';
8
+ proj4.defs('WGS84').axis = 'neu';
9
+
10
+ /**
11
+ * A projection as a CRS identifier string. This identifier references a
12
+ * projection definition previously defined with
13
+ * [`proj4.defs`](https://github.com/proj4js/proj4js#named-projections).
14
+ */
15
+
16
+ function isString(s) {
17
+ return typeof s === 'string' || s instanceof String;
18
+ }
19
+ function mustBeString(crs) {
20
+ if (!isString(crs)) {
21
+ throw new Error(`Crs parameter value must be a string: '${crs}'`);
22
+ }
23
+ }
24
+
25
+ /**
26
+ * System units supported for a coordinate system. See
27
+ * [proj](https://proj4.org/en/9.5/operations/conversions/unitconvert.html#angular-units).
28
+ * Note that only degree and meters units are supported for now.
29
+ */
30
+ export const UNIT = {
31
+ /**
32
+ * Angular unit in degree.
33
+ */
34
+ DEGREE: 1,
35
+ /**
36
+ * Distance unit in meter.
37
+ */
38
+ METER: 2,
39
+ /**
40
+ * Distance unit in foot.
41
+ */
42
+ FOOT: 3
43
+ };
44
+
45
+ /**
46
+ * Checks that the CRS is EPSG:4326.
47
+ * @internal
48
+ *
49
+ * @param crs - The CRS to test.
50
+ */
51
+ export function is4326(crs) {
52
+ return crs === 'EPSG:4326';
53
+ }
54
+ function unitFromProj4Unit(proj) {
55
+ if (proj.units === 'degrees') {
56
+ return UNIT.DEGREE;
57
+ } else if (proj.units === 'm' || proj.units === 'meter') {
58
+ return UNIT.METER;
59
+ } else if (proj.units === 'foot') {
60
+ return UNIT.FOOT;
61
+ } else if (proj.units === undefined && proj.to_meter === undefined) {
62
+ // See https://proj.org/en/9.4/usage/projections.html [17/10/2024]
63
+ // > The default unit for projected coordinates is the meter.
64
+ return UNIT.METER;
65
+ } else {
66
+ return undefined;
67
+ }
68
+ }
69
+
70
+ /**
71
+ * Returns the horizontal coordinates system units associated with this CRS.
72
+ *
73
+ * @param crs - The CRS to extract the unit from.
74
+ * @returns Either `UNIT.METER`, `UNIT.DEGREE`, `UNIT.FOOT` or `undefined`.
75
+ */
76
+ export function getUnit(crs) {
77
+ mustBeString(crs);
78
+ const p = proj4.defs(crs);
79
+ if (!p) {
80
+ return undefined;
81
+ }
82
+ return unitFromProj4Unit(p);
83
+ }
84
+
85
+ /**
86
+ * Asserts that the CRS is using metric units.
87
+ *
88
+ * @param crs - The CRS to check.
89
+ * @throws {@link Error} if the CRS is not valid.
90
+ */
91
+ export function isMetricUnit(crs) {
92
+ return getUnit(crs) === UNIT.METER;
93
+ }
94
+
95
+ /**
96
+ * Asserts that the CRS is geographic.
97
+ *
98
+ * @param crs - The CRS to check.
99
+ * @throws {@link Error} if the CRS is not valid.
100
+ */
101
+ export function isGeographic(crs) {
102
+ return getUnit(crs) === UNIT.DEGREE;
103
+ }
104
+
105
+ /**
106
+ * Asserts that the CRS is geocentric.
107
+ *
108
+ * @param crs - The CRS to test.
109
+ * @returns false if the crs isn't defined.
110
+ */
111
+ export function isGeocentric(crs) {
112
+ mustBeString(crs);
113
+ const projection = proj4.defs(crs);
114
+ return !projection ? false : projection.projName == 'geocent';
115
+ }
116
+
117
+ /**
118
+ * Asserts that the CRS is valid, meaning it has been previously defined and
119
+ * includes an unit.
120
+ *
121
+ * @param crs - The CRS to test.
122
+ * @throws {@link Error} if the crs is not valid.
123
+ */
124
+ export function isValid(crs) {
125
+ const proj = proj4.defs(crs);
126
+ if (!proj) {
127
+ throw new Error(`Undefined crs '${crs}'. Add it with proj4.defs('${crs}', string)`);
128
+ }
129
+ if (!unitFromProj4Unit(proj)) {
130
+ throw new Error(`No valid unit found for crs '${crs}', found ${proj.units}`);
131
+ }
132
+ }
133
+
134
+ /**
135
+ * Gives a reasonable epsilon for this CRS.
136
+ *
137
+ * @param crs - The CRS to use.
138
+ * @returns 0.01 if the CRS is EPSG:4326, 0.001 otherwise.
139
+ */
140
+ export function reasonableEpsilon(crs) {
141
+ if (is4326(crs)) {
142
+ return 0.01;
143
+ } else {
144
+ return 0.001;
145
+ }
146
+ }
147
+
148
+ /**
149
+ * Returns the axis parameter defined in proj4 for the provided crs.
150
+ * Might be undefined depending on crs definition.
151
+ *
152
+ * @param crs - The CRS to get axis from.
153
+ * @returns the matching proj4 axis string, 'enu' for instance (east, north, up)
154
+ */
155
+ export function axisOrder(crs) {
156
+ mustBeString(crs);
157
+ const projection = proj4.defs(crs);
158
+ return !projection ? undefined : projection.axis;
159
+ }
160
+
161
+ /**
162
+ * Defines a proj4 projection as a named alias.
163
+ * This function is a specialized wrapper over the
164
+ * [`proj4.defs`](https://github.com/proj4js/proj4js#named-projections)
165
+ * function.
166
+ *
167
+ * @param code - Named alias of the currently defined projection.
168
+ * @param proj4def - Proj4 or WKT string of the defined projection.
169
+ */
170
+ export const defs = (code, proj4def) => proj4.defs(code, proj4def);
@@ -0,0 +1,74 @@
1
+ import * as THREE from 'three';
2
+ import Coordinates from './Coordinates';
3
+ /**
4
+ * Length of the semi-axes of the WGS84 ellipsoid.
5
+ * @internal
6
+ */
7
+ export declare const ellipsoidSizes: THREE.Vector3;
8
+ declare class Ellipsoid {
9
+ /**
10
+ * Length of the semi-axes of the ellipsoid.
11
+ */
12
+ size: THREE.Vector3;
13
+ /**
14
+ * Eccentricity of the ellipsoid.
15
+ */
16
+ eccentricity: number;
17
+ private _radiiSquared;
18
+ private _invRadiiSquared;
19
+ /**
20
+ * @param size - Length of the semi-axes of the ellipsoid. Defaults to those
21
+ * defined by the WGS84 ellipsoid.
22
+ */
23
+ constructor(size?: THREE.Vector3);
24
+ /**
25
+ * Computes the normal vector to an ellipsoid at the given cartesian
26
+ * coordinate `(x, y, z)`.
27
+ *
28
+ * @param cartesian - The given cartesian coordinate.
29
+ * @param target - An object to store this vector to. If this is not
30
+ * specified, a new vector will be created.
31
+ */
32
+ geodeticSurfaceNormal(cartesian: Coordinates, target?: THREE.Vector3): THREE.Vector3;
33
+ /**
34
+ * Computes the normal vector to an ellipsoid at the given geographic
35
+ * coordinate `(longitude, latitude, altitude)`.
36
+ *
37
+ * @param coordCarto - The given geographic coordinate.
38
+ * @param target - An object to store this vector to. If this is not
39
+ * specified, a new vector will be created.
40
+ */
41
+ geodeticSurfaceNormalCartographic(coordCarto: Coordinates, target?: THREE.Vector3): THREE.Vector3;
42
+ /**
43
+ * Sets the length of the semi-axes of this ellipsoid from a 3-dimensional
44
+ * vector-like object. The object shall have both `x`, `y` and `z`
45
+ * properties.
46
+ *
47
+ * @param size - The source vector.
48
+ */
49
+ setSize(size: THREE.Vector3Like): this;
50
+ cartographicToCartesian(coordCarto: Coordinates, target?: THREE.Vector3): THREE.Vector3;
51
+ /**
52
+ * Convert cartesian coordinates to geographic according to the current
53
+ * ellipsoid of revolution.
54
+ * @param position - The coordinate to convert
55
+ * @param target - coordinate to copy result
56
+ * @returns an object describing the coordinates on the reference ellipsoid,
57
+ * angles are in degree
58
+ */
59
+ cartesianToCartographic(position: THREE.Vector3Like, target?: Coordinates): Coordinates;
60
+ cartographicToCartesianArray(coordCartoArray: Coordinates[]): THREE.Vector3[];
61
+ intersection(ray: THREE.Ray): THREE.Vector3 | false;
62
+ /**
63
+ * Calculate the geodesic distance, between coordCarto1 and coordCarto2.
64
+ * It's most short distance on ellipsoid surface between coordCarto1 and
65
+ * coordCarto2.
66
+ * It's called orthodromy.
67
+ *
68
+ * @param coordCarto1 - The coordinate carto 1
69
+ * @param coordCarto2 - The coordinate carto 2
70
+ * @returns The orthodromic distance between the two given coordinates.
71
+ */
72
+ geodesicDistance(coordCarto1: Coordinates, coordCarto2: Coordinates): number;
73
+ }
74
+ export default Ellipsoid;
@@ -0,0 +1,186 @@
1
+ import * as THREE from 'three';
2
+ import proj4 from 'proj4';
3
+ import Coordinates from "./Coordinates.js";
4
+
5
+ /**
6
+ * Length of the semi-axes of the WGS84 ellipsoid.
7
+ * @internal
8
+ */
9
+ export const ellipsoidSizes = new THREE.Vector3(proj4.WGS84.a, proj4.WGS84.a, proj4.WGS84.b);
10
+ const normal = new THREE.Vector3();
11
+ class Ellipsoid {
12
+ /**
13
+ * Length of the semi-axes of the ellipsoid.
14
+ */
15
+
16
+ /**
17
+ * Eccentricity of the ellipsoid.
18
+ */
19
+
20
+ /**
21
+ * @param size - Length of the semi-axes of the ellipsoid. Defaults to those
22
+ * defined by the WGS84 ellipsoid.
23
+ */
24
+ constructor() {
25
+ let size = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ellipsoidSizes;
26
+ this.size = new THREE.Vector3();
27
+ this._radiiSquared = new THREE.Vector3();
28
+ this._invRadiiSquared = new THREE.Vector3();
29
+ this.eccentricity = 0;
30
+ this.setSize(size);
31
+ }
32
+
33
+ /**
34
+ * Computes the normal vector to an ellipsoid at the given cartesian
35
+ * coordinate `(x, y, z)`.
36
+ *
37
+ * @param cartesian - The given cartesian coordinate.
38
+ * @param target - An object to store this vector to. If this is not
39
+ * specified, a new vector will be created.
40
+ */
41
+ geodeticSurfaceNormal(cartesian) {
42
+ let target = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : new THREE.Vector3();
43
+ return cartesian.toVector3(target).multiply(this._invRadiiSquared).normalize();
44
+ }
45
+
46
+ /**
47
+ * Computes the normal vector to an ellipsoid at the given geographic
48
+ * coordinate `(longitude, latitude, altitude)`.
49
+ *
50
+ * @param coordCarto - The given geographic coordinate.
51
+ * @param target - An object to store this vector to. If this is not
52
+ * specified, a new vector will be created.
53
+ */
54
+ geodeticSurfaceNormalCartographic(coordCarto) {
55
+ let target = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : new THREE.Vector3();
56
+ const longitude = THREE.MathUtils.degToRad(coordCarto.longitude);
57
+ const latitude = THREE.MathUtils.degToRad(coordCarto.latitude);
58
+ const cosLatitude = Math.cos(latitude);
59
+ return target.set(cosLatitude * Math.cos(longitude), cosLatitude * Math.sin(longitude), Math.sin(latitude));
60
+ }
61
+
62
+ /**
63
+ * Sets the length of the semi-axes of this ellipsoid from a 3-dimensional
64
+ * vector-like object. The object shall have both `x`, `y` and `z`
65
+ * properties.
66
+ *
67
+ * @param size - The source vector.
68
+ */
69
+ setSize(size) {
70
+ this.size.set(size.x, size.y, size.z);
71
+ this._radiiSquared.multiplyVectors(size, size);
72
+ this._invRadiiSquared.x = size.x === 0 ? 0 : 1 / this._radiiSquared.x;
73
+ this._invRadiiSquared.y = size.y === 0 ? 0 : 1 / this._radiiSquared.y;
74
+ this._invRadiiSquared.z = size.z === 0 ? 0 : 1 / this._radiiSquared.z;
75
+ this.eccentricity = Math.sqrt(this._radiiSquared.x - this._radiiSquared.z) / this.size.x;
76
+ return this;
77
+ }
78
+ cartographicToCartesian(coordCarto) {
79
+ let target = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : new THREE.Vector3();
80
+ normal.copy(coordCarto.geodesicNormal);
81
+ target.multiplyVectors(this._radiiSquared, normal);
82
+ const gamma = Math.sqrt(normal.dot(target));
83
+ target.divideScalar(gamma);
84
+ normal.multiplyScalar(coordCarto.altitude);
85
+ return target.add(normal);
86
+ }
87
+
88
+ /**
89
+ * Convert cartesian coordinates to geographic according to the current
90
+ * ellipsoid of revolution.
91
+ * @param position - The coordinate to convert
92
+ * @param target - coordinate to copy result
93
+ * @returns an object describing the coordinates on the reference ellipsoid,
94
+ * angles are in degree
95
+ */
96
+ cartesianToCartographic(position) {
97
+ let target = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : new Coordinates('EPSG:4326', 0, 0, 0);
98
+ // for details, see for example http://www.linz.govt.nz/data/geodetic-system/coordinate-conversion/geodetic-datum-conversions/equations-used-datum
99
+ // TODO the following is only valable for oblate ellipsoid of
100
+ // revolution. do we want to support triaxial ellipsoid?
101
+ const R = Math.sqrt(position.x * position.x + position.y * position.y + position.z * position.z);
102
+ const a = this.size.x; // x
103
+ const b = this.size.z; // z
104
+ const e = Math.abs((a * a - b * b) / (a * a));
105
+ const f = 1 - Math.sqrt(1 - e);
106
+ const rsqXY = Math.sqrt(position.x * position.x + position.y * position.y);
107
+ const theta = Math.atan2(position.y, position.x);
108
+ const nu = Math.atan(position.z / rsqXY * (1 - f + e * a / R));
109
+ const sinu = Math.sin(nu);
110
+ const cosu = Math.cos(nu);
111
+ const phi = Math.atan((position.z * (1 - f) + e * a * sinu * sinu * sinu) / ((1 - f) * (rsqXY - e * a * cosu * cosu * cosu)));
112
+ const h = rsqXY * Math.cos(phi) + position.z * Math.sin(phi) - a * Math.sqrt(1 - e * Math.sin(phi) * Math.sin(phi));
113
+ return target.setFromValues(THREE.MathUtils.radToDeg(theta), THREE.MathUtils.radToDeg(phi), h);
114
+ }
115
+ cartographicToCartesianArray(coordCartoArray) {
116
+ const cartesianArray = [];
117
+ for (let i = 0; i < coordCartoArray.length; i++) {
118
+ cartesianArray.push(this.cartographicToCartesian(coordCartoArray[i]));
119
+ }
120
+ return cartesianArray;
121
+ }
122
+ intersection(ray) {
123
+ const EPSILON = 0.0001;
124
+ const O_C = ray.origin;
125
+ const dir = ray.direction;
126
+ // normalizeVector( dir );
127
+
128
+ const a = dir.x * dir.x * this._invRadiiSquared.x + dir.y * dir.y * this._invRadiiSquared.y + dir.z * dir.z * this._invRadiiSquared.z;
129
+ const b = 2 * O_C.x * dir.x * this._invRadiiSquared.x + 2 * O_C.y * dir.y * this._invRadiiSquared.y + 2 * O_C.z * dir.z * this._invRadiiSquared.z;
130
+ const c = O_C.x * O_C.x * this._invRadiiSquared.x + O_C.y * O_C.y * this._invRadiiSquared.y + O_C.z * O_C.z * this._invRadiiSquared.z - 1;
131
+ let d = b * b - 4 * a * c;
132
+ if (d < 0 || a === 0 || b === 0 || c === 0) {
133
+ return false;
134
+ }
135
+ d = Math.sqrt(d);
136
+ const t1 = (-b + d) / (2 * a);
137
+ const t2 = (-b - d) / (2 * a);
138
+ if (t1 <= EPSILON && t2 <= EPSILON) {
139
+ // both intersections are behind the ray origin
140
+ return false;
141
+ }
142
+ let t = 0;
143
+ if (t1 <= EPSILON) {
144
+ t = t2;
145
+ } else if (t2 <= EPSILON) {
146
+ t = t1;
147
+ } else {
148
+ t = t1 < t2 ? t1 : t2;
149
+ }
150
+ if (t < EPSILON) {
151
+ return false;
152
+ } // Too close to intersection
153
+
154
+ const inter = new THREE.Vector3();
155
+ inter.addVectors(ray.origin, dir.clone().setLength(t));
156
+ return inter;
157
+ }
158
+
159
+ /**
160
+ * Calculate the geodesic distance, between coordCarto1 and coordCarto2.
161
+ * It's most short distance on ellipsoid surface between coordCarto1 and
162
+ * coordCarto2.
163
+ * It's called orthodromy.
164
+ *
165
+ * @param coordCarto1 - The coordinate carto 1
166
+ * @param coordCarto2 - The coordinate carto 2
167
+ * @returns The orthodromic distance between the two given coordinates.
168
+ */
169
+ geodesicDistance(coordCarto1, coordCarto2) {
170
+ // The formula uses the distance on approximated sphere,
171
+ // with the nearest local radius of curvature of the ellipsoid
172
+ // https://geodesie.ign.fr/contenu/fichiers/Distance_longitude_latitude.pdf
173
+ const longitude1 = THREE.MathUtils.degToRad(coordCarto1.longitude);
174
+ const latitude1 = THREE.MathUtils.degToRad(coordCarto1.latitude);
175
+ const longitude2 = THREE.MathUtils.degToRad(coordCarto2.longitude);
176
+ const latitude2 = THREE.MathUtils.degToRad(coordCarto2.latitude);
177
+ const distRad = Math.acos(Math.sin(latitude1) * Math.sin(latitude2) + Math.cos(latitude1) * Math.cos(latitude2) * Math.cos(longitude2 - longitude1));
178
+ const e = this.eccentricity;
179
+ const latMoy = (latitude1 + latitude2) * 0.5;
180
+ const es = (e * Math.sin(latMoy)) ** 2;
181
+ const rho = this.size.x * (1 - e ** 2) / (1 - es) ** (3 / 2);
182
+ const N = this.size.x / Math.sqrt(1 - es);
183
+ return distRad * Math.sqrt(rho * N);
184
+ }
185
+ }
186
+ export default Ellipsoid;