@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.
package/lib/Extent.js ADDED
@@ -0,0 +1,531 @@
1
+ import * as THREE from 'three';
2
+ import Coordinates from "./Coordinates.js";
3
+ import * as CRS from "./Crs.js";
4
+
5
+ /**
6
+ * Extent is a SIG-area (so 2D)
7
+ * It can use explicit coordinates (e.g: lon/lat) or implicit (WMTS coordinates)
8
+ */
9
+
10
+ const _dim = new THREE.Vector2();
11
+ const _dim2 = new THREE.Vector2();
12
+ const _box = new THREE.Box3();
13
+ const defaultScheme = new THREE.Vector2(2, 2);
14
+ const cNorthWest = new Coordinates('EPSG:4326', 0, 0, 0);
15
+ const cSouthWest = new Coordinates('EPSG:4326', 0, 0, 0);
16
+ const cNorthEast = new Coordinates('EPSG:4326', 0, 0, 0);
17
+ const southWest = new THREE.Vector3();
18
+ const northEast = new THREE.Vector3();
19
+
20
+ /** @type {Extent} */
21
+ let _extent;
22
+ const cardinals = new Array(8);
23
+ for (let i = cardinals.length - 1; i >= 0; i--) {
24
+ cardinals[i] = new Coordinates('EPSG:4326', 0, 0, 0);
25
+ }
26
+ const _c = new Coordinates('EPSG:4326', 0, 0);
27
+ class Extent {
28
+ /**
29
+ * Extent is geographical bounding rectangle defined by 4 limits: west, east, south and north.
30
+ *
31
+ * Warning, using geocentric projection isn't consistent with geographical extent.
32
+ *
33
+ * @param {String} crs projection of limit values.
34
+ * @param {number|Array.<number>|Coordinates|Object} v0 west value, Array
35
+ * of values [west, east, south and north], Coordinates of west-south
36
+ * corner or object {west, east, south and north}
37
+ * @param {number|Coordinates} [v1] east value or Coordinates of
38
+ * east-north corner
39
+ * @param {number} [v2] south value
40
+ * @param {number} [v3] north value
41
+ */
42
+ constructor(crs, v0, v1, v2, v3) {
43
+ if (CRS.isGeocentric(crs)) {
44
+ throw new Error(`${crs} is a geocentric projection, it doesn't make sense with a geographical extent`);
45
+ }
46
+ this.isExtent = true;
47
+ this.crs = crs;
48
+ this.west = 0;
49
+ this.east = 0;
50
+ this.south = 0;
51
+ this.north = 0;
52
+ this.set(v0, v1, v2, v3);
53
+ }
54
+
55
+ /**
56
+ * Clone this extent
57
+ * @return {Extent} cloned extent
58
+ */
59
+ clone() {
60
+ return new Extent(this.crs, this.west, this.east, this.south, this.north);
61
+ }
62
+
63
+ /**
64
+ * Convert Extent to the specified projection.
65
+ * @param {string} crs the projection of destination.
66
+ * @param {Extent} [target] copy the destination to target.
67
+ * @return {Extent}
68
+ */
69
+ as(crs, target) {
70
+ CRS.isValid(crs);
71
+ target = target || new Extent('EPSG:4326', [0, 0, 0, 0]);
72
+ if (this.crs != crs) {
73
+ // Compute min/max in x/y by projecting 8 cardinal points,
74
+ // and then taking the min/max of each coordinates.
75
+ const center = this.center(_c);
76
+ cardinals[0].setFromValues(this.west, this.north);
77
+ cardinals[1].setFromValues(center.x, this.north);
78
+ cardinals[2].setFromValues(this.east, this.north);
79
+ cardinals[3].setFromValues(this.east, center.y);
80
+ cardinals[4].setFromValues(this.east, this.south);
81
+ cardinals[5].setFromValues(center.x, this.south);
82
+ cardinals[6].setFromValues(this.west, this.south);
83
+ cardinals[7].setFromValues(this.west, center.y);
84
+ target.set(Infinity, -Infinity, Infinity, -Infinity);
85
+
86
+ // loop over the coordinates
87
+ for (let i = 0; i < cardinals.length; i++) {
88
+ // convert the coordinate.
89
+ cardinals[i].crs = this.crs;
90
+ cardinals[i].as(crs, _c);
91
+ target.north = Math.max(target.north, _c.y);
92
+ target.south = Math.min(target.south, _c.y);
93
+ target.east = Math.max(target.east, _c.x);
94
+ target.west = Math.min(target.west, _c.x);
95
+ }
96
+ target.crs = crs;
97
+ return target;
98
+ }
99
+ target.crs = crs;
100
+ target.set(this.west, this.east, this.south, this.north);
101
+ return target;
102
+ }
103
+
104
+ /**
105
+ * Return the center of Extent
106
+ * @param {Coordinates} target copy the center to the target.
107
+ * @return {Coordinates}
108
+ */
109
+ center() {
110
+ let target = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : new Coordinates(this.crs);
111
+ this.planarDimensions(_dim);
112
+ target.crs = this.crs;
113
+ target.setFromValues(this.west + _dim.x * 0.5, this.south + _dim.y * 0.5);
114
+ return target;
115
+ }
116
+
117
+ /**
118
+ * Returns the dimension of the extent, in a `THREE.Vector2`.
119
+ *
120
+ * @param {THREE.Vector2} [target] - The target to assign the result in.
121
+ *
122
+ * @return {THREE.Vector2}
123
+ */
124
+ dimensions() {
125
+ let target = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : new THREE.Vector2();
126
+ console.warn('Extent.dimensions is deprecated, use planarDimensions, geodeticDimensions or spatialEuclideanDimensions');
127
+ target.x = Math.abs(this.east - this.west);
128
+ target.y = Math.abs(this.north - this.south);
129
+ return target;
130
+ }
131
+
132
+ /**
133
+ * Planar dimensions are two planar distances west/east and south/north.
134
+ * Planar distance straight-line Euclidean distance calculated in a 2D Cartesian coordinate system.
135
+ *
136
+ * @param {THREE.Vector2} [target=new THREE.Vector2()] The target
137
+ * @return {THREE.Vector2} Planar dimensions
138
+ */
139
+ planarDimensions() {
140
+ let target = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : new THREE.Vector2();
141
+ // Calculte the dimensions for x and y
142
+ return target.set(Math.abs(this.east - this.west), Math.abs(this.north - this.south));
143
+ }
144
+
145
+ /**
146
+ * Geodetic dimensions are two planar distances west/east and south/north.
147
+ * Geodetic distance is calculated in an ellispoid space as the distance
148
+ * across the curved surface of the world.
149
+ *
150
+ * @param {THREE.Vector2} [target=new THREE.Vector2()] The target
151
+ * @return {THREE.Vector2} geodetic dimensions
152
+ */
153
+ geodeticDimensions() {
154
+ let target = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : new THREE.Vector2();
155
+ // set 3 corners extent
156
+ cNorthWest.crs = this.crs;
157
+ cSouthWest.crs = this.crs;
158
+ cNorthEast.crs = this.crs;
159
+ cNorthWest.setFromValues(this.west, this.north, 0);
160
+ cSouthWest.setFromValues(this.west, this.south, 0);
161
+ cNorthEast.setFromValues(this.east, this.north, 0);
162
+
163
+ // calcul geodetic distance northWest/northEast and northWest/southWest
164
+ return target.set(cNorthWest.geodeticDistanceTo(cNorthEast), cNorthWest.geodeticDistanceTo(cSouthWest));
165
+ }
166
+
167
+ /**
168
+ * Spatial euclidean dimensions are two spatial euclidean distances between west/east corner and south/north corner.
169
+ * Spatial euclidean distance chord is calculated in a ellispoid space.
170
+ *
171
+ * @param {THREE.Vector2} [target=new THREE.Vector2()] The target
172
+ * @return {THREE.Vector2} spatial euclidean dimensions
173
+ */
174
+ spatialEuclideanDimensions() {
175
+ let target = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : new THREE.Vector2();
176
+ // set 3 corners extent
177
+ cNorthWest.crs = this.crs;
178
+ cSouthWest.crs = this.crs;
179
+ cNorthEast.crs = this.crs;
180
+ cNorthWest.setFromValues(this.west, this.north, 0);
181
+ cSouthWest.setFromValues(this.west, this.south, 0);
182
+ cNorthEast.setFromValues(this.east, this.north, 0);
183
+
184
+ // calcul chord distance northWest/northEast and northWest/southWest
185
+ return target.set(cNorthWest.spatialEuclideanDistanceTo(cNorthEast), cNorthWest.spatialEuclideanDistanceTo(cSouthWest));
186
+ }
187
+
188
+ /**
189
+ * Return true if `coord` is inside the bounding box.
190
+ *
191
+ * @param {Coordinates} coord
192
+ * @param {number} [epsilon=0] - to take into account when comparing to the
193
+ * point.
194
+ *
195
+ * @return {boolean}
196
+ */
197
+ isPointInside(coord) {
198
+ let epsilon = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
199
+ if (this.crs == coord.crs) {
200
+ _c.copy(coord);
201
+ } else {
202
+ coord.as(this.crs, _c);
203
+ }
204
+
205
+ // TODO this ignores altitude
206
+ return _c.x <= this.east + epsilon && _c.x >= this.west - epsilon && _c.y <= this.north + epsilon && _c.y >= this.south - epsilon;
207
+ }
208
+
209
+ /**
210
+ * Return true if `extent` is inside this extent.
211
+ *
212
+ * @param {Extent} extent the extent to check
213
+ * @param {number} epsilon to take into account when comparing to the
214
+ * point.
215
+ *
216
+ * @return {boolean}
217
+ */
218
+ isInside(extent, epsilon) {
219
+ extent.as(this.crs, _extent);
220
+ epsilon = epsilon ?? CRS.reasonableEpsilon(this.crs);
221
+ return this.east - _extent.east <= epsilon && _extent.west - this.west <= epsilon && this.north - _extent.north <= epsilon && _extent.south - this.south <= epsilon;
222
+ }
223
+
224
+ /**
225
+ * Return the translation and scale to transform this extent to input extent.
226
+ *
227
+ * @param {Extent} extent input extent
228
+ * @param {THREE.Vector4} target copy the result to target.
229
+ * @return {THREE.Vector4} {x: translation on west-east, y: translation on south-north, z: scale on west-east, w: scale on south-north}
230
+ */
231
+ offsetToParent(extent) {
232
+ let target = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : new THREE.Vector4();
233
+ if (this.crs != extent.crs) {
234
+ throw new Error('unsupported mix');
235
+ }
236
+ extent.planarDimensions(_dim);
237
+ this.planarDimensions(_dim2);
238
+ const originX = (this.west - extent.west) / _dim.x;
239
+ const originY = (extent.north - this.north) / _dim.y;
240
+ const scaleX = _dim2.x / _dim.x;
241
+ const scaleY = _dim2.y / _dim.y;
242
+ return target.set(originX, originY, scaleX, scaleY);
243
+ }
244
+
245
+ /**
246
+ * Return true if this bounding box intersect with the bouding box parameter
247
+ * @param {Extent} extent
248
+ * @returns {Boolean}
249
+ */
250
+ intersectsExtent(extent) {
251
+ return Extent.intersectsExtent(this, extent);
252
+ }
253
+ static intersectsExtent(/** @type {Extent} */extentA, /** @type {Extent} */extentB) {
254
+ // TODO don't work when is on limit
255
+ const other = extentB.crs == extentA.crs ? extentB : extentB.as(extentA.crs, _extent);
256
+ return !(extentA.west >= other.east || extentA.east <= other.west || extentA.south >= other.north || extentA.north <= other.south);
257
+ }
258
+
259
+ /**
260
+ * Return the intersection of this extent with another one
261
+ * @param {Extent} extent
262
+ * @returns {Extent}
263
+ */
264
+ intersect(extent) {
265
+ if (!this.intersectsExtent(extent)) {
266
+ return new Extent(this.crs, 0, 0, 0, 0);
267
+ }
268
+ if (extent.crs != this.crs) {
269
+ extent = extent.as(this.crs, _extent);
270
+ }
271
+ return new Extent(this.crs, Math.max(this.west, extent.west), Math.min(this.east, extent.east), Math.max(this.south, extent.south), Math.min(this.north, extent.north));
272
+ }
273
+
274
+ /**
275
+ * Set west, east, south and north values.
276
+ *
277
+ * @param {number|Array.<number>|Coordinates|Object|Extent} v0 west value,
278
+ * Array of values [west, east, south and north], Extent of same type (tiled
279
+ * or not), Coordinates of west-south corner or object {west, east, south
280
+ * and north}
281
+ * @param {number|Coordinates} [v1] east value, row value or Coordinates of
282
+ * east-north corner
283
+ * @param {number} [v2] south value or column value
284
+ * @param {number} [v3] north value
285
+ *
286
+ * @return {Extent}
287
+ */
288
+ set(v0, v1, v2, v3) {
289
+ if (v0 == undefined) {
290
+ throw new Error('No values to set in the extent');
291
+ }
292
+ if (v0.isExtent) {
293
+ v1 = v0.east;
294
+ v2 = v0.south;
295
+ v3 = v0.north;
296
+ v0 = v0.west;
297
+ }
298
+ if (v0.isCoordinates) {
299
+ // seem never used
300
+ this.west = v0.x;
301
+ this.east = v1.x;
302
+ this.south = v0.y;
303
+ this.north = v1.y;
304
+ } else if (v0.west !== undefined) {
305
+ this.west = v0.west;
306
+ this.east = v0.east;
307
+ this.south = v0.south;
308
+ this.north = v0.north;
309
+ } else if (v0.length == 4) {
310
+ this.west = v0[0];
311
+ this.east = v0[1];
312
+ this.south = v0[2];
313
+ this.north = v0[3];
314
+ } else if (v3 !== undefined) {
315
+ this.west = v0;
316
+ this.east = v1;
317
+ this.south = v2;
318
+ this.north = v3;
319
+ }
320
+ return this;
321
+ }
322
+
323
+ /**
324
+ * Copy to this extent to input extent.
325
+ * @param {Extent} extent
326
+ * @return {Extent} copied extent
327
+ */
328
+ copy(extent) {
329
+ this.crs = extent.crs;
330
+ return this.set(extent);
331
+ }
332
+
333
+ /**
334
+ * Union this extent with the input extent.
335
+ * @param {Extent} extent the extent to union.
336
+ */
337
+ union(extent) {
338
+ if (extent.crs != this.crs) {
339
+ throw new Error('unsupported union between 2 diff crs');
340
+ }
341
+ if (this.west === Infinity) {
342
+ this.copy(extent);
343
+ } else {
344
+ const west = extent.west;
345
+ if (west < this.west) {
346
+ this.west = west;
347
+ }
348
+ const east = extent.east;
349
+ if (east > this.east) {
350
+ this.east = east;
351
+ }
352
+ const south = extent.south;
353
+ if (south < this.south) {
354
+ this.south = south;
355
+ }
356
+ const north = extent.north;
357
+ if (north > this.north) {
358
+ this.north = north;
359
+ }
360
+ }
361
+ }
362
+
363
+ /**
364
+ * expandByCoordinates perfoms the minimal extension
365
+ * for the coordinates to belong to this Extent object
366
+ * @param {Coordinates} coordinates The coordinates to belong
367
+ */
368
+ expandByCoordinates(coordinates) {
369
+ const coords = coordinates.crs == this.crs ? coordinates : coordinates.as(this.crs, _c);
370
+ this.expandByValuesCoordinates(coords.x, coords.y);
371
+ }
372
+
373
+ /**
374
+ * expandByValuesCoordinates perfoms the minimal extension
375
+ * for the coordinates values to belong to this Extent object
376
+ * @param {number} we The coordinate on west-east
377
+ * @param {number} sn The coordinate on south-north
378
+ *
379
+ */
380
+ expandByValuesCoordinates(we, sn) {
381
+ if (we < this.west) {
382
+ this.west = we;
383
+ }
384
+ if (we > this.east) {
385
+ this.east = we;
386
+ }
387
+ if (sn < this.south) {
388
+ this.south = sn;
389
+ }
390
+ if (sn > this.north) {
391
+ this.north = sn;
392
+ }
393
+ }
394
+
395
+ /**
396
+ * Instance Extent with THREE.Box3.
397
+ *
398
+ * If crs is a geocentric projection, the `box3.min` and `box3.max`
399
+ * should be the geocentric coordinates of `min` and `max` of a `box3`
400
+ * in local tangent plane.
401
+ *
402
+ * @param {string} crs Projection of extent to instancied.
403
+ * @param {THREE.Box3} box
404
+ * @return {Extent}
405
+ */
406
+ static fromBox3(crs, box) {
407
+ if (CRS.isGeocentric(crs)) {
408
+ // if geocentric reproject box on 'EPSG:4326'
409
+ crs = 'EPSG:4326';
410
+ box = _box.copy(box);
411
+ cSouthWest.crs = crs;
412
+ cSouthWest.setFromVector3(box.min).as(crs, cSouthWest).toVector3(box.min);
413
+ cNorthEast.crs = crs;
414
+ cNorthEast.setFromVector3(box.max).as(crs, cNorthEast).toVector3(box.max);
415
+ }
416
+ return new Extent(crs, {
417
+ west: box.min.x,
418
+ east: box.max.x,
419
+ south: box.min.y,
420
+ north: box.max.y
421
+ });
422
+ }
423
+
424
+ /**
425
+ * Return values of extent in string, separated by the separator input.
426
+ * @param {string} separator
427
+ * @return {string}
428
+ */
429
+ toString() {
430
+ let separator = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
431
+ return `${this.east}${separator}${this.north}${separator}${this.west}${separator}${this.south}`;
432
+ }
433
+
434
+ /**
435
+ * Subdivide equally an extent from its center to return four extents:
436
+ * north-west, north-east, south-west and south-east.
437
+ *
438
+ * @returns {Extent[]} An array containing the four sections of the extent. The
439
+ * order of the sections is [NW, NE, SW, SE].
440
+ */
441
+ subdivision() {
442
+ return this.subdivisionByScheme();
443
+ }
444
+ /**
445
+ * subdivise extent by scheme.x on west-east and scheme.y on south-north.
446
+ *
447
+ * @param {THREE.Vector2} [scheme=Vector2(2,2)] The scheme to subdivise.
448
+ * @return {Array<Extent>} subdivised extents.
449
+ */
450
+ subdivisionByScheme() {
451
+ let scheme = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : defaultScheme;
452
+ const subdivisedExtents = [];
453
+ const dimSub = this.planarDimensions(_dim).divide(scheme);
454
+ for (let x = scheme.x - 1; x >= 0; x--) {
455
+ for (let y = scheme.y - 1; y >= 0; y--) {
456
+ const west = this.west + x * dimSub.x;
457
+ const south = this.south + y * dimSub.y;
458
+ subdivisedExtents.push(new Extent(this.crs, west, west + dimSub.x, south, south + dimSub.y));
459
+ }
460
+ }
461
+ return subdivisedExtents;
462
+ }
463
+
464
+ /**
465
+ * Multiplies all extent `coordinates` (with an implicit 1 in the 4th dimension) and `matrix`.
466
+ *
467
+ * @param {THREE.Matrix4} matrix The matrix
468
+ * @return {Extent} return this extent instance.
469
+ */
470
+ applyMatrix4(matrix) {
471
+ southWest.set(this.west, this.south, 0).applyMatrix4(matrix);
472
+ northEast.set(this.east, this.north, 0).applyMatrix4(matrix);
473
+ this.west = southWest.x;
474
+ this.east = northEast.x;
475
+ this.south = southWest.y;
476
+ this.north = northEast.y;
477
+ if (this.west > this.east) {
478
+ const temp = this.west;
479
+ this.west = this.east;
480
+ this.east = temp;
481
+ }
482
+ if (this.south > this.north) {
483
+ const temp = this.south;
484
+ this.south = this.north;
485
+ this.north = temp;
486
+ }
487
+ return this;
488
+ }
489
+
490
+ /**
491
+ * clamp south and north values
492
+ *
493
+ * @param {number} [south=this.south] The min south
494
+ * @param {number} [north=this.north] The max north
495
+ * @return {Extent} this extent
496
+ */
497
+ clampSouthNorth() {
498
+ let south = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.south;
499
+ let north = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.north;
500
+ this.south = Math.max(this.south, south);
501
+ this.north = Math.min(this.north, north);
502
+ return this;
503
+ }
504
+
505
+ /**
506
+ * clamp west and east values
507
+ *
508
+ * @param {number} [west=this.west] The min west
509
+ * @param {number} [east=this.east] The max east
510
+ * @return {Extent} this extent
511
+ */
512
+ clampWestEast() {
513
+ let west = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.west;
514
+ let east = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.east;
515
+ this.west = Math.max(this.west, west);
516
+ this.east = Math.min(this.east, east);
517
+ return this;
518
+ }
519
+ /**
520
+ * clamp this extent by passed extent
521
+ *
522
+ * @param {Extent} extent The maximum extent.
523
+ * @return {Extent} this extent.
524
+ */
525
+ clampByExtent(extent) {
526
+ this.clampSouthNorth(extent.south, extent.north);
527
+ return this.clampWestEast(extent.west, extent.east);
528
+ }
529
+ }
530
+ _extent = new Extent('EPSG:4326', [0, 0, 0, 0]);
531
+ export default Extent;
package/lib/Main.js ADDED
@@ -0,0 +1,7 @@
1
+ // Geodesic tools
2
+ export { default as Extent } from "./Extent.js";
3
+ export { default as Coordinates } from "./Coordinates.js";
4
+ export * as CRS from "./Crs.js";
5
+ export { default as CoordStars } from "./CoordStars.js";
6
+ export { default as OrientationUtils } from "./OrientationUtils.js";
7
+ export { default as Ellipsoid, ellipsoidSizes } from "./Ellipsoid.js";