@wemap/geo 11.5.0 → 11.8.0
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/dist/index.js +329 -28
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +327 -24
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/src/graph/GeoGraph.ts +14 -5
- package/src/graph/GeoGraphRouter.ts +233 -7
- package/src/graph/NoRouteFoundError.ts +4 -5
- package/src/types.ts +3 -2
package/dist/index.mjs
CHANGED
|
@@ -22,21 +22,21 @@ const R_MINOR_4 = R_MINOR_2 * R_MINOR_2;
|
|
|
22
22
|
const CIRCUMFERENCE = R_MAJOR * 2 * Math.PI;
|
|
23
23
|
const Constants = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
24
24
|
__proto__: null,
|
|
25
|
-
|
|
26
|
-
R_MINOR,
|
|
25
|
+
CIRCUMFERENCE,
|
|
27
26
|
EARTH_GRAVITY,
|
|
28
|
-
EPS_DEG_MM,
|
|
29
|
-
EPS_MM,
|
|
30
|
-
ELLIPSOID_FLATNESS,
|
|
31
27
|
ECCENTRICITY,
|
|
32
28
|
ECCENTRICITY_2,
|
|
29
|
+
ELLIPSOID_FLATNESS,
|
|
30
|
+
EPS_DEG_MM,
|
|
31
|
+
EPS_MM,
|
|
32
|
+
R_MAJOR,
|
|
33
33
|
R_MAJOR_2,
|
|
34
34
|
R_MAJOR_4,
|
|
35
|
+
R_MINOR,
|
|
35
36
|
R_MINOR_2,
|
|
36
|
-
R_MINOR_4
|
|
37
|
-
CIRCUMFERENCE
|
|
37
|
+
R_MINOR_4
|
|
38
38
|
}, Symbol.toStringTag, { value: "Module" }));
|
|
39
|
-
const _Level = class {
|
|
39
|
+
const _Level = class _Level {
|
|
40
40
|
static checkType(level) {
|
|
41
41
|
if (level === null) {
|
|
42
42
|
return;
|
|
@@ -55,6 +55,9 @@ const _Level = class {
|
|
|
55
55
|
}
|
|
56
56
|
throw Error(`Unknown level format: ${level}`);
|
|
57
57
|
}
|
|
58
|
+
/**
|
|
59
|
+
* Return true if the level is a range, false otherwise
|
|
60
|
+
*/
|
|
58
61
|
static isRange(level) {
|
|
59
62
|
if (_Level.VERIFY_TYPING) {
|
|
60
63
|
this.checkType(level);
|
|
@@ -73,6 +76,9 @@ const _Level = class {
|
|
|
73
76
|
}
|
|
74
77
|
return [level[0], level[1]];
|
|
75
78
|
}
|
|
79
|
+
/**
|
|
80
|
+
* Create a level from a string (eg. 1, -2, 1;2, -2;3, 2;-1, 0.5;1 ...)
|
|
81
|
+
*/
|
|
76
82
|
static fromString(str) {
|
|
77
83
|
if (str === null) {
|
|
78
84
|
return null;
|
|
@@ -101,6 +107,11 @@ const _Level = class {
|
|
|
101
107
|
}
|
|
102
108
|
throw Error(`Cannot parse following level: ${str}`);
|
|
103
109
|
}
|
|
110
|
+
/**
|
|
111
|
+
* Returns if a level is contained in another
|
|
112
|
+
* @param {null|number|[number, number]} container The container level
|
|
113
|
+
* @param {null|number|[number, number]} targeted The targeted level
|
|
114
|
+
*/
|
|
104
115
|
static contains(container, targeted) {
|
|
105
116
|
if (_Level.VERIFY_TYPING) {
|
|
106
117
|
this.checkType(container);
|
|
@@ -123,6 +134,9 @@ const _Level = class {
|
|
|
123
134
|
}
|
|
124
135
|
return container <= targeted[0] && container >= targeted[1];
|
|
125
136
|
}
|
|
137
|
+
/**
|
|
138
|
+
* Retrieve the intersection of two levels
|
|
139
|
+
*/
|
|
126
140
|
static intersection(first, second) {
|
|
127
141
|
if (_Level.VERIFY_TYPING) {
|
|
128
142
|
this.checkType(first);
|
|
@@ -156,6 +170,12 @@ const _Level = class {
|
|
|
156
170
|
}
|
|
157
171
|
return up < low ? null : [low, up];
|
|
158
172
|
}
|
|
173
|
+
/**
|
|
174
|
+
* Retrieve the intersection of two levels
|
|
175
|
+
* @param {null|number|[number, number]} first The first level
|
|
176
|
+
* @param {null|number|[number, number]} second The second level
|
|
177
|
+
* @returns {boolean}
|
|
178
|
+
*/
|
|
159
179
|
static intersect(first, second) {
|
|
160
180
|
if (_Level.VERIFY_TYPING) {
|
|
161
181
|
this.checkType(first);
|
|
@@ -166,6 +186,9 @@ const _Level = class {
|
|
|
166
186
|
}
|
|
167
187
|
return this.intersection(first, second) !== null;
|
|
168
188
|
}
|
|
189
|
+
/**
|
|
190
|
+
* Retrieve the union of two levels
|
|
191
|
+
*/
|
|
169
192
|
static union(first, second) {
|
|
170
193
|
if (_Level.VERIFY_TYPING) {
|
|
171
194
|
this.checkType(first);
|
|
@@ -199,6 +222,12 @@ const _Level = class {
|
|
|
199
222
|
}
|
|
200
223
|
return [low, up];
|
|
201
224
|
}
|
|
225
|
+
/**
|
|
226
|
+
* Multiply a level by a factor
|
|
227
|
+
* @param {null|number|[number, number]} level the level to multiply
|
|
228
|
+
* @param {number} factor
|
|
229
|
+
* @returns {null|number|[number, number]}
|
|
230
|
+
*/
|
|
202
231
|
static multiplyBy(level, factor) {
|
|
203
232
|
if (_Level.VERIFY_TYPING) {
|
|
204
233
|
this.checkType(level);
|
|
@@ -265,8 +294,8 @@ const _Level = class {
|
|
|
265
294
|
return null;
|
|
266
295
|
}
|
|
267
296
|
};
|
|
297
|
+
__publicField(_Level, "VERIFY_TYPING", false);
|
|
268
298
|
let Level = _Level;
|
|
269
|
-
__publicField(Level, "VERIFY_TYPING", false);
|
|
270
299
|
class Coordinates {
|
|
271
300
|
constructor(lat, lng, alt = null, level = null) {
|
|
272
301
|
__publicField(this, "_lat");
|
|
@@ -314,6 +343,10 @@ class Coordinates {
|
|
|
314
343
|
set longitude(_) {
|
|
315
344
|
throw new Error("Please use Coordinates#lng setter instead of Coordinate#longitude");
|
|
316
345
|
}
|
|
346
|
+
/**
|
|
347
|
+
* alt does not denote the altitude of a point but its height from
|
|
348
|
+
* the "level" field (if defined) or from the ground
|
|
349
|
+
*/
|
|
317
350
|
get alt() {
|
|
318
351
|
return this._alt;
|
|
319
352
|
}
|
|
@@ -328,6 +361,9 @@ class Coordinates {
|
|
|
328
361
|
Level.checkType(level);
|
|
329
362
|
this._level = level;
|
|
330
363
|
}
|
|
364
|
+
/**
|
|
365
|
+
* Deep clone coordinates
|
|
366
|
+
*/
|
|
331
367
|
clone() {
|
|
332
368
|
const output = new Coordinates(this.lat, this.lng, this.alt);
|
|
333
369
|
if (this.level !== null) {
|
|
@@ -352,11 +388,18 @@ class Coordinates {
|
|
|
352
388
|
equals(other) {
|
|
353
389
|
return Coordinates.equals(this, other);
|
|
354
390
|
}
|
|
391
|
+
/**
|
|
392
|
+
* @throws {Error} if elevation is defined and point altitude is not defined
|
|
393
|
+
*/
|
|
355
394
|
destinationPoint(distance, bearing, elevation = null) {
|
|
356
395
|
const newPoint = this.clone();
|
|
357
396
|
newPoint.move(distance, bearing, elevation);
|
|
358
397
|
return newPoint;
|
|
359
398
|
}
|
|
399
|
+
/**
|
|
400
|
+
* Source: http://www.movable-type.co.uk/scripts/latlong.html#destPoint
|
|
401
|
+
* @throws {Error} if elevation is defined and point altitude is not defined
|
|
402
|
+
*/
|
|
360
403
|
move(distance, bearing, elevation = null) {
|
|
361
404
|
const dR = distance / R_MAJOR;
|
|
362
405
|
const cosDr = Math.cos(dR);
|
|
@@ -380,6 +423,9 @@ class Coordinates {
|
|
|
380
423
|
}
|
|
381
424
|
return this;
|
|
382
425
|
}
|
|
426
|
+
/**
|
|
427
|
+
* Returns a distance between two points in meters
|
|
428
|
+
*/
|
|
383
429
|
distanceTo(location2) {
|
|
384
430
|
const lat1 = this.lat;
|
|
385
431
|
const lng1 = this.lng;
|
|
@@ -414,6 +460,11 @@ class Coordinates {
|
|
|
414
460
|
static bearingTo(point1, point2) {
|
|
415
461
|
return point1.bearingTo(point2);
|
|
416
462
|
}
|
|
463
|
+
/**
|
|
464
|
+
* ECEF Transformations
|
|
465
|
+
* Here we used a light version of ECEF considering earth
|
|
466
|
+
* as a sphere instead of an ellipse
|
|
467
|
+
*/
|
|
417
468
|
get enuToEcefRotation() {
|
|
418
469
|
const rot1 = Quaternion.fromAxisAngle([0, 0, 1], Math.PI / 2 + deg2rad(this.lng));
|
|
419
470
|
const rot2 = Quaternion.fromAxisAngle([1, 0, 0], Math.PI / 2 - deg2rad(this.lat));
|
|
@@ -424,6 +475,10 @@ class Coordinates {
|
|
|
424
475
|
const rot2 = Quaternion.fromAxisAngle([0, 0, 1], -deg2rad(this.lng) - Math.PI / 2);
|
|
425
476
|
return Quaternion.multiply(rot1, rot2);
|
|
426
477
|
}
|
|
478
|
+
/**
|
|
479
|
+
* https://gist.github.com/klucar/1536194
|
|
480
|
+
* Adapted for spherical formula
|
|
481
|
+
*/
|
|
427
482
|
get ecef() {
|
|
428
483
|
if (!this._ecef) {
|
|
429
484
|
const lat = deg2rad(this.lat);
|
|
@@ -449,6 +504,10 @@ class Coordinates {
|
|
|
449
504
|
newPoint._ecef = ecef;
|
|
450
505
|
return newPoint;
|
|
451
506
|
}
|
|
507
|
+
/**
|
|
508
|
+
* https://stackoverflow.com/questions/1299567/how-to-calculate-distance-from-a-point-to-a-line-segment-on-a-sphere
|
|
509
|
+
* Adapted to ECEF
|
|
510
|
+
*/
|
|
452
511
|
getSegmentProjection(p1, p2) {
|
|
453
512
|
const a = Vector3.normalize(p1.ecef);
|
|
454
513
|
const b = Vector3.normalize(p2.ecef);
|
|
@@ -476,6 +535,9 @@ class Coordinates {
|
|
|
476
535
|
}
|
|
477
536
|
return projection;
|
|
478
537
|
}
|
|
538
|
+
/**
|
|
539
|
+
* Input / Output
|
|
540
|
+
*/
|
|
479
541
|
toString() {
|
|
480
542
|
let str = "[" + this._lat.toFixed(7) + ", " + this._lng.toFixed(7);
|
|
481
543
|
if (this._alt !== null) {
|
|
@@ -567,6 +629,8 @@ class UserPosition extends Coordinates {
|
|
|
567
629
|
newPoint.move(distance, bearing, elevation);
|
|
568
630
|
return newPoint;
|
|
569
631
|
}
|
|
632
|
+
// Create a UserPosition with lat, lng, alt from Coordinates coordinates and
|
|
633
|
+
// other fields from another UserPosition
|
|
570
634
|
static fromCoordinates(coordinates) {
|
|
571
635
|
return new UserPosition(
|
|
572
636
|
coordinates.lat,
|
|
@@ -738,11 +802,11 @@ function calcDistance(coords) {
|
|
|
738
802
|
}
|
|
739
803
|
const Utils = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
740
804
|
__proto__: null,
|
|
805
|
+
calcDistance,
|
|
806
|
+
geolocationPositionToUserPosition,
|
|
741
807
|
sampleRoute,
|
|
742
|
-
trimRoute,
|
|
743
808
|
simplifyRoute,
|
|
744
|
-
|
|
745
|
-
calcDistance
|
|
809
|
+
trimRoute
|
|
746
810
|
}, Symbol.toStringTag, { value: "Module" }));
|
|
747
811
|
class BoundingBox {
|
|
748
812
|
constructor(northEast, southWest) {
|
|
@@ -754,14 +818,23 @@ class BoundingBox {
|
|
|
754
818
|
throw new Error("Incorrect bounding box");
|
|
755
819
|
}
|
|
756
820
|
}
|
|
821
|
+
/**
|
|
822
|
+
* Returns the geographical coordinate equidistant from the bounding box's corners.
|
|
823
|
+
*/
|
|
757
824
|
get center() {
|
|
758
825
|
const latCenter = (this.southWest.lat + this.northEast.lat) / 2;
|
|
759
826
|
const lngCenter = (this.northEast.lng + this.southWest.lng) / 2;
|
|
760
827
|
return new Coordinates(latCenter, lngCenter);
|
|
761
828
|
}
|
|
829
|
+
/**
|
|
830
|
+
* Check if a point is contained in the bounding box.
|
|
831
|
+
*/
|
|
762
832
|
contains(point) {
|
|
763
833
|
return point.lat <= this.northEast.lat && point.lat >= this.southWest.lat && point.lng <= this.northEast.lng && point.lng >= this.southWest.lng;
|
|
764
834
|
}
|
|
835
|
+
/**
|
|
836
|
+
* Extend the bounds to include a given LngLat or LngLatBounds.
|
|
837
|
+
*/
|
|
765
838
|
extend(obj) {
|
|
766
839
|
const sw = this.southWest, ne = this.northEast;
|
|
767
840
|
let sw2, ne2;
|
|
@@ -784,6 +857,10 @@ class BoundingBox {
|
|
|
784
857
|
);
|
|
785
858
|
return this;
|
|
786
859
|
}
|
|
860
|
+
/**
|
|
861
|
+
* This method extends the bounding box with a value in meters
|
|
862
|
+
* /*\ This method is not precise as distance differs in function of latitude
|
|
863
|
+
*/
|
|
787
864
|
extendsWithMeasure(measure) {
|
|
788
865
|
if (typeof measure !== "number") {
|
|
789
866
|
throw new Error("measure is not a number");
|
|
@@ -792,6 +869,11 @@ class BoundingBox {
|
|
|
792
869
|
this.southWest = this.southWest.clone().destinationPoint(measure, -Math.PI / 2).destinationPoint(measure, Math.PI);
|
|
793
870
|
return this;
|
|
794
871
|
}
|
|
872
|
+
/**
|
|
873
|
+
* Returns bounds created by extending or retracting the current bounds by a given ratio in each direction.
|
|
874
|
+
* For example, a ratio of 0.5 extends the bounds by 50% in each direction.
|
|
875
|
+
* Negative values will retract the bounds.
|
|
876
|
+
*/
|
|
795
877
|
pad(bufferRatio) {
|
|
796
878
|
const sw = this.southWest;
|
|
797
879
|
const ne = this.northEast;
|
|
@@ -801,27 +883,51 @@ class BoundingBox {
|
|
|
801
883
|
this.northEast = new Coordinates(ne.lat + heightBuffer, ne.lng + widthBuffer);
|
|
802
884
|
return this;
|
|
803
885
|
}
|
|
886
|
+
/**
|
|
887
|
+
* Returns the southwest corner of the bounding box.
|
|
888
|
+
*/
|
|
804
889
|
getSouthWest() {
|
|
805
890
|
return this.southWest;
|
|
806
891
|
}
|
|
892
|
+
/**
|
|
893
|
+
* Returns the northeast corner of the bounding box.
|
|
894
|
+
*/
|
|
807
895
|
getNorthEast() {
|
|
808
896
|
return this.northEast;
|
|
809
897
|
}
|
|
898
|
+
/**
|
|
899
|
+
* Returns the northwest corner of the bounding box.
|
|
900
|
+
*/
|
|
810
901
|
getNorthWest() {
|
|
811
902
|
return new Coordinates(this.getNorth(), this.getWest());
|
|
812
903
|
}
|
|
904
|
+
/**
|
|
905
|
+
* Returns the southeast corner of the bounding box.
|
|
906
|
+
*/
|
|
813
907
|
getSouthEast() {
|
|
814
908
|
return new Coordinates(this.getSouth(), this.getEast());
|
|
815
909
|
}
|
|
910
|
+
/**
|
|
911
|
+
* Returns the west edge of the bounding box.
|
|
912
|
+
*/
|
|
816
913
|
getWest() {
|
|
817
914
|
return this.southWest.lng;
|
|
818
915
|
}
|
|
916
|
+
/**
|
|
917
|
+
* Returns the south edge of the bounding box.
|
|
918
|
+
*/
|
|
819
919
|
getSouth() {
|
|
820
920
|
return this.southWest.lat;
|
|
821
921
|
}
|
|
922
|
+
/**
|
|
923
|
+
* Returns the east edge of the bounding box.
|
|
924
|
+
*/
|
|
822
925
|
getEast() {
|
|
823
926
|
return this.northEast.lng;
|
|
824
927
|
}
|
|
928
|
+
/**
|
|
929
|
+
* Returns the north edge of the bounding box.
|
|
930
|
+
*/
|
|
825
931
|
getNorth() {
|
|
826
932
|
return this.northEast.lat;
|
|
827
933
|
}
|
|
@@ -831,6 +937,9 @@ class BoundingBox {
|
|
|
831
937
|
equals(other) {
|
|
832
938
|
return BoundingBox.equals(this, other);
|
|
833
939
|
}
|
|
940
|
+
/**
|
|
941
|
+
* Create a BoundingBox from a WSEN array
|
|
942
|
+
*/
|
|
834
943
|
static fromArray(bounds) {
|
|
835
944
|
return new BoundingBox(
|
|
836
945
|
new Coordinates(bounds[3], bounds[2]),
|
|
@@ -846,6 +955,9 @@ class BoundingBox {
|
|
|
846
955
|
new BoundingBox(coords[0], coords[0])
|
|
847
956
|
);
|
|
848
957
|
}
|
|
958
|
+
/**
|
|
959
|
+
* Returns the WSEN array
|
|
960
|
+
*/
|
|
849
961
|
toArray() {
|
|
850
962
|
return [this.getWest(), this.getSouth(), this.getEast(), this.getNorth()];
|
|
851
963
|
}
|
|
@@ -883,6 +995,12 @@ class RelativePosition {
|
|
|
883
995
|
clone() {
|
|
884
996
|
return new RelativePosition(this.x, this.y, this.z, this.time, this.accuracy, this.bearing);
|
|
885
997
|
}
|
|
998
|
+
/**
|
|
999
|
+
* Compares two RelativePosition
|
|
1000
|
+
* @param {RelativePosition} pos1 position 1
|
|
1001
|
+
* @param {RelativePosition} pos2 position 2
|
|
1002
|
+
* @param {Number} eps x, y, z epsilon in meters (default: 1e-3 [= 1mm])
|
|
1003
|
+
*/
|
|
886
1004
|
static equals(pos1, pos2, eps = EPS_MM) {
|
|
887
1005
|
if (pos1 === null && pos1 === pos2) {
|
|
888
1006
|
return true;
|
|
@@ -918,6 +1036,9 @@ class GeoRef {
|
|
|
918
1036
|
__publicField(this, "heading", 0);
|
|
919
1037
|
this.origin = origin;
|
|
920
1038
|
}
|
|
1039
|
+
/**
|
|
1040
|
+
* LocalPosition in ENU frame
|
|
1041
|
+
*/
|
|
921
1042
|
localToWorld(localPosition) {
|
|
922
1043
|
const enuTranslationScaled = Vector3.multiplyScalar(localPosition, this.scale);
|
|
923
1044
|
const rotationOffset = Quaternion.fromAxisAngle([0, 0, 1], this.heading);
|
|
@@ -926,6 +1047,9 @@ class GeoRef {
|
|
|
926
1047
|
const ecef = Vector3.sum(this.origin.ecef, ecefTranslation);
|
|
927
1048
|
return Coordinates.fromECEF(ecef);
|
|
928
1049
|
}
|
|
1050
|
+
/**
|
|
1051
|
+
* LocalPosition in ENU frame
|
|
1052
|
+
*/
|
|
929
1053
|
worldToLocal(coords) {
|
|
930
1054
|
const rotationOffset = Quaternion.fromAxisAngle([0, 0, 1], -this.heading);
|
|
931
1055
|
const ecefToEnuRotationOrigin = Quaternion.multiply(this.origin.ecefToEnuRotation, rotationOffset);
|
|
@@ -1043,6 +1167,9 @@ class Attitude {
|
|
|
1043
1167
|
clone() {
|
|
1044
1168
|
return new Attitude(this.quaternion.slice(0), this.time, this.accuracy);
|
|
1045
1169
|
}
|
|
1170
|
+
/**
|
|
1171
|
+
* Calculate the relative attitude between two given attitudes
|
|
1172
|
+
*/
|
|
1046
1173
|
static diff(attitudeStart, attitudeEnd) {
|
|
1047
1174
|
const quaternionDiff = Quaternion.multiply(
|
|
1048
1175
|
Quaternion.inverse(attitudeStart.quaternion),
|
|
@@ -1084,6 +1211,11 @@ class AbsoluteHeading {
|
|
|
1084
1211
|
this.accuracy
|
|
1085
1212
|
);
|
|
1086
1213
|
}
|
|
1214
|
+
/**
|
|
1215
|
+
* Compares two AbsoluteHeading
|
|
1216
|
+
* @param {AbsoluteHeading} heading1 heading 1
|
|
1217
|
+
* @param {AbsoluteHeading} heading2 heading 2
|
|
1218
|
+
*/
|
|
1087
1219
|
static equals(heading1, heading2) {
|
|
1088
1220
|
if (heading1 === null && heading1 === heading2) {
|
|
1089
1221
|
return true;
|
|
@@ -1141,12 +1273,18 @@ class GeoGraphEdge extends GraphEdge {
|
|
|
1141
1273
|
Level.checkType(level);
|
|
1142
1274
|
this._level = level;
|
|
1143
1275
|
}
|
|
1276
|
+
/**
|
|
1277
|
+
* Get edge bearing from vertex1 to vertex2
|
|
1278
|
+
*/
|
|
1144
1279
|
get bearing() {
|
|
1145
1280
|
if (!this._computedSizeAndBearing) {
|
|
1146
1281
|
this._computeSizeAndBearing();
|
|
1147
1282
|
}
|
|
1148
1283
|
return this._bearing;
|
|
1149
1284
|
}
|
|
1285
|
+
/**
|
|
1286
|
+
* get edge length
|
|
1287
|
+
*/
|
|
1150
1288
|
get length() {
|
|
1151
1289
|
if (!this._computedSizeAndBearing) {
|
|
1152
1290
|
this._computeSizeAndBearing();
|
|
@@ -1175,6 +1313,9 @@ class GeoGraphVertex extends GraphVertex {
|
|
|
1175
1313
|
bearingTo(other) {
|
|
1176
1314
|
return this.coords.bearingTo(other.coords);
|
|
1177
1315
|
}
|
|
1316
|
+
/**
|
|
1317
|
+
* Does not include "edges" property
|
|
1318
|
+
*/
|
|
1178
1319
|
toJson() {
|
|
1179
1320
|
return this.coords.toCompressedJson();
|
|
1180
1321
|
}
|
|
@@ -1198,6 +1339,9 @@ class GeoGraphVertex extends GraphVertex {
|
|
|
1198
1339
|
}
|
|
1199
1340
|
this.coords.level = tmpLevel;
|
|
1200
1341
|
}
|
|
1342
|
+
/**
|
|
1343
|
+
* We suppose inferVertexLevelFromEdges() was called before
|
|
1344
|
+
*/
|
|
1201
1345
|
inferVertexLevelByNeighboors() {
|
|
1202
1346
|
const { level } = this.coords;
|
|
1203
1347
|
if (level === null || !Level.isRange(level)) {
|
|
@@ -1214,6 +1358,9 @@ class GeoGraphVertex extends GraphVertex {
|
|
|
1214
1358
|
}
|
|
1215
1359
|
return true;
|
|
1216
1360
|
}
|
|
1361
|
+
/**
|
|
1362
|
+
* We suppose inferVertexLevelFromEdges() and inferVertexLevelByNeighboors() were called before
|
|
1363
|
+
*/
|
|
1217
1364
|
inferVertexLevelByRecursion() {
|
|
1218
1365
|
const { level } = this.coords;
|
|
1219
1366
|
if (level === null || !Level.isRange(level)) {
|
|
@@ -1278,13 +1425,21 @@ class GeoGraph extends Graph {
|
|
|
1278
1425
|
return boundingBox;
|
|
1279
1426
|
}
|
|
1280
1427
|
toCompressedJson() {
|
|
1428
|
+
const createEdgeExtras = (edge) => {
|
|
1429
|
+
const extras = {};
|
|
1430
|
+
if (edge.isOneway) {
|
|
1431
|
+
extras.oneway = true;
|
|
1432
|
+
}
|
|
1433
|
+
return extras;
|
|
1434
|
+
};
|
|
1281
1435
|
return {
|
|
1282
1436
|
vertices: this.vertices.map((vertex) => vertex.toJson()),
|
|
1283
1437
|
edges: this.edges.map((edge) => {
|
|
1284
1438
|
const vertex1Idx = this.vertices.indexOf(edge.vertex1);
|
|
1285
1439
|
const vertex2Idx = this.vertices.indexOf(edge.vertex2);
|
|
1286
|
-
|
|
1287
|
-
|
|
1440
|
+
const edgeExtras = createEdgeExtras(edge);
|
|
1441
|
+
if (Object.keys(edgeExtras).length > 0) {
|
|
1442
|
+
return [vertex1Idx, vertex2Idx, edge.level, edgeExtras];
|
|
1288
1443
|
}
|
|
1289
1444
|
if (edge.level !== null) {
|
|
1290
1445
|
return [vertex1Idx, vertex2Idx, edge.level];
|
|
@@ -1302,8 +1457,9 @@ class GeoGraph extends Graph {
|
|
|
1302
1457
|
geograph.vertices[jsonEdge[1]],
|
|
1303
1458
|
{ level: jsonEdge.length > 2 ? jsonEdge[2] : null }
|
|
1304
1459
|
);
|
|
1305
|
-
|
|
1306
|
-
|
|
1460
|
+
const extras = jsonEdge.length > 3 ? jsonEdge[3] : {};
|
|
1461
|
+
if (typeof extras.oneway !== "undefined") {
|
|
1462
|
+
edge.isOneway = extras.oneway;
|
|
1307
1463
|
}
|
|
1308
1464
|
return edge;
|
|
1309
1465
|
});
|
|
@@ -1338,6 +1494,10 @@ class GeoGraph extends Graph {
|
|
|
1338
1494
|
}
|
|
1339
1495
|
return geograph;
|
|
1340
1496
|
}
|
|
1497
|
+
/**
|
|
1498
|
+
* Create edges From MultiLevel Itinerary for a given level
|
|
1499
|
+
* @param useMultiLevelEdges use segments which intersect both levels (stairs, elevators...)
|
|
1500
|
+
*/
|
|
1341
1501
|
getEdgesAtLevel(targetLevel, useMultiLevelEdges = true) {
|
|
1342
1502
|
return this.edges.filter(
|
|
1343
1503
|
({ level }) => useMultiLevelEdges ? Level.intersect(targetLevel, level) : Level.contains(targetLevel, level)
|
|
@@ -1365,7 +1525,7 @@ class GeoGraphProjection {
|
|
|
1365
1525
|
this.nearestElement = nearestElement;
|
|
1366
1526
|
}
|
|
1367
1527
|
}
|
|
1368
|
-
const _GeoGraphProjectionHandler = class {
|
|
1528
|
+
const _GeoGraphProjectionHandler = class _GeoGraphProjectionHandler {
|
|
1369
1529
|
constructor(graph = null) {
|
|
1370
1530
|
__publicField(this, "graph", null);
|
|
1371
1531
|
__publicField(this, "_maxDistance", Number.MAX_VALUE);
|
|
@@ -1384,6 +1544,12 @@ const _GeoGraphProjectionHandler = class {
|
|
|
1384
1544
|
get maxDistance() {
|
|
1385
1545
|
return this._maxDistance;
|
|
1386
1546
|
}
|
|
1547
|
+
/**
|
|
1548
|
+
* Check if the specified edge and its vertices can be used for projection
|
|
1549
|
+
* @returns an array of two elements.
|
|
1550
|
+
* First is true if projection will be used on the specified edge, false otherwise.
|
|
1551
|
+
* Second is true if projection will be used on the vertices of the specified edge, false otherwise.
|
|
1552
|
+
*/
|
|
1387
1553
|
_shouldProjectOnEdgeAndVertices(edge, location, useBearing, useMultiLevelSegments, acceptEdgeFn) {
|
|
1388
1554
|
if (!acceptEdgeFn(edge)) {
|
|
1389
1555
|
return [false, false, false];
|
|
@@ -1415,11 +1581,21 @@ const _GeoGraphProjectionHandler = class {
|
|
|
1415
1581
|
toCoordinates.lng = fromCoordinates.lng;
|
|
1416
1582
|
toCoordinates.level = Level.clone(fromCoordinates.level);
|
|
1417
1583
|
}
|
|
1584
|
+
/**
|
|
1585
|
+
* IO Vertices are typical because they have a non-null level but projection car works on them.
|
|
1586
|
+
* This function handles the case where the projection is on an IO vertex and a location with
|
|
1587
|
+
* a null level is required.
|
|
1588
|
+
*/
|
|
1418
1589
|
static _handleLevelsWithIOVertices(projection, location, projectionVertex) {
|
|
1419
1590
|
if (location.level === null && projectionVertex.io) {
|
|
1420
1591
|
projection.level = null;
|
|
1421
1592
|
}
|
|
1422
1593
|
}
|
|
1594
|
+
/**
|
|
1595
|
+
* Main function for map-matching, the networks have to be set before calling this function
|
|
1596
|
+
* The function will returns a GraphProjection object given a coordinates object and a set
|
|
1597
|
+
* of options (useDistance, useBearing, useMultiLevelSegments, acceptEdgeFn).
|
|
1598
|
+
*/
|
|
1423
1599
|
getProjection(location, useDistance = false, useBearing = false, useMultiLevelSegments = true, acceptEdgeFn = () => true) {
|
|
1424
1600
|
if (this.graph === null) {
|
|
1425
1601
|
throw new Error("Graph has not been set yet");
|
|
@@ -1492,10 +1668,10 @@ const _GeoGraphProjectionHandler = class {
|
|
|
1492
1668
|
);
|
|
1493
1669
|
}
|
|
1494
1670
|
};
|
|
1495
|
-
|
|
1496
|
-
__publicField(GeoGraphProjectionHandler, "_updateProjectionLevelFromEdge", (_edge, _projection) => {
|
|
1671
|
+
__publicField(_GeoGraphProjectionHandler, "_updateProjectionLevelFromEdge", (_edge, _projection) => {
|
|
1497
1672
|
_projection.level = Level.clone(_edge.level);
|
|
1498
1673
|
});
|
|
1674
|
+
let GeoGraphProjectionHandler = _GeoGraphProjectionHandler;
|
|
1499
1675
|
class GeoGraphItinerary extends GeoGraph {
|
|
1500
1676
|
constructor(start, end, vertices, edges, edgesWeights) {
|
|
1501
1677
|
super(vertices, edges);
|
|
@@ -1547,6 +1723,9 @@ class NoRouteFoundError extends Error {
|
|
|
1547
1723
|
if (this.end instanceof GeoGraphVertex) {
|
|
1548
1724
|
return `GeoGraphVertex ${this.end.coords.toString()}`;
|
|
1549
1725
|
}
|
|
1726
|
+
if (Array.isArray(this.end)) {
|
|
1727
|
+
return this.end.map((p) => p instanceof GeoGraphVertex ? p.coords.toString() : p.toString()).join(",");
|
|
1728
|
+
}
|
|
1550
1729
|
return this.end.toString();
|
|
1551
1730
|
}
|
|
1552
1731
|
get message() {
|
|
@@ -1557,7 +1736,7 @@ class NoRouteFoundError extends Error {
|
|
|
1557
1736
|
return message;
|
|
1558
1737
|
}
|
|
1559
1738
|
}
|
|
1560
|
-
const _GeoGraphRouter = class {
|
|
1739
|
+
const _GeoGraphRouter = class _GeoGraphRouter {
|
|
1561
1740
|
constructor(graph) {
|
|
1562
1741
|
__publicField(this, "_mapMatching");
|
|
1563
1742
|
__publicField(this, "_graph");
|
|
@@ -1569,7 +1748,7 @@ const _GeoGraphRouter = class {
|
|
|
1569
1748
|
const { acceptEdgeFn, weightEdgeFn, projectionMaxDistance } = options;
|
|
1570
1749
|
this._mapMatching.maxDistance = projectionMaxDistance;
|
|
1571
1750
|
const createdVertices = [];
|
|
1572
|
-
const retrieveOrCreateNearestVertex = (point) => {
|
|
1751
|
+
const retrieveOrCreateNearestVertex = (point, options2 = _GeoGraphRouter.DEFAULT_OPTIONS) => {
|
|
1573
1752
|
if (point instanceof GeoGraphVertex) {
|
|
1574
1753
|
return point;
|
|
1575
1754
|
}
|
|
@@ -1577,7 +1756,7 @@ const _GeoGraphRouter = class {
|
|
|
1577
1756
|
if (closeVertex) {
|
|
1578
1757
|
return closeVertex;
|
|
1579
1758
|
}
|
|
1580
|
-
const proj = this._mapMatching.getProjection(point, true, false, false, acceptEdgeFn);
|
|
1759
|
+
const proj = this._mapMatching.getProjection(point, true, false, false, options2.acceptEdgeFn);
|
|
1581
1760
|
if (!proj) {
|
|
1582
1761
|
let message = `Point ${point.toString()} is too far from the graph > ${this._mapMatching.maxDistance.toFixed(0)} meters.`;
|
|
1583
1762
|
if (point.level !== null) {
|
|
@@ -1628,6 +1807,71 @@ const _GeoGraphRouter = class {
|
|
|
1628
1807
|
}
|
|
1629
1808
|
return graphItinerary;
|
|
1630
1809
|
}
|
|
1810
|
+
getShortestPaths(start, ends, options = _GeoGraphRouter.DEFAULT_OPTIONS) {
|
|
1811
|
+
const { acceptEdgeFn, weightEdgeFn, projectionMaxDistance } = options;
|
|
1812
|
+
this._mapMatching.maxDistance = projectionMaxDistance;
|
|
1813
|
+
const createdVertices = [];
|
|
1814
|
+
const retrieveOrCreateNearestVertex = (point, options2 = _GeoGraphRouter.DEFAULT_OPTIONS) => {
|
|
1815
|
+
if (point instanceof GeoGraphVertex) {
|
|
1816
|
+
return point;
|
|
1817
|
+
}
|
|
1818
|
+
const closeVertex = this._graph.getVertexByCoords(point);
|
|
1819
|
+
if (closeVertex) {
|
|
1820
|
+
return closeVertex;
|
|
1821
|
+
}
|
|
1822
|
+
const proj = this._mapMatching.getProjection(point, true, false, false, options2.acceptEdgeFn);
|
|
1823
|
+
if (!proj) {
|
|
1824
|
+
return null;
|
|
1825
|
+
}
|
|
1826
|
+
if (proj.nearestElement instanceof GeoGraphVertex) {
|
|
1827
|
+
return proj.nearestElement;
|
|
1828
|
+
}
|
|
1829
|
+
const vertexCreated = this.createVertexInsideEdge(
|
|
1830
|
+
proj.nearestElement,
|
|
1831
|
+
proj.coords
|
|
1832
|
+
);
|
|
1833
|
+
createdVertices.push(vertexCreated);
|
|
1834
|
+
return vertexCreated;
|
|
1835
|
+
};
|
|
1836
|
+
const removeCreatedVertices = () => {
|
|
1837
|
+
while (createdVertices.length) {
|
|
1838
|
+
this.removeVertexFromPreviouslyCreatedEdge(createdVertices.pop());
|
|
1839
|
+
}
|
|
1840
|
+
};
|
|
1841
|
+
const startVertex = retrieveOrCreateNearestVertex(start);
|
|
1842
|
+
const endsStruct = ends.map((e) => ({
|
|
1843
|
+
input: e,
|
|
1844
|
+
vertex: retrieveOrCreateNearestVertex(e),
|
|
1845
|
+
itinerary: null
|
|
1846
|
+
}));
|
|
1847
|
+
const startCoords = start instanceof GeoGraphVertex ? start.coords : start;
|
|
1848
|
+
if (!startVertex) {
|
|
1849
|
+
let message = `Point ${startCoords.toString()} is too far from the graph > ${this._mapMatching.maxDistance.toFixed(0)} meters.`;
|
|
1850
|
+
if (startCoords.level !== null) {
|
|
1851
|
+
message += ` If it is a multi-level map, please verify if you have a graph at level ${Level.toString(startCoords.level)}.`;
|
|
1852
|
+
}
|
|
1853
|
+
throw new NoRouteFoundError(start, ends, message);
|
|
1854
|
+
}
|
|
1855
|
+
const endsStructFiltered = endsStruct.filter((v) => v.vertex != null);
|
|
1856
|
+
const graphItineraries = this.getShortestPathsBetweenGeoGraphVertices(
|
|
1857
|
+
startVertex,
|
|
1858
|
+
endsStructFiltered.map((v) => v.vertex),
|
|
1859
|
+
weightEdgeFn,
|
|
1860
|
+
acceptEdgeFn
|
|
1861
|
+
);
|
|
1862
|
+
graphItineraries.forEach((itinerary, idx) => {
|
|
1863
|
+
itinerary.start = startCoords;
|
|
1864
|
+
const endStructFiltered = endsStructFiltered[idx];
|
|
1865
|
+
endStructFiltered.itinerary = itinerary;
|
|
1866
|
+
});
|
|
1867
|
+
removeCreatedVertices();
|
|
1868
|
+
endsStructFiltered.forEach((e) => {
|
|
1869
|
+
if (e.itinerary !== null) {
|
|
1870
|
+
e.itinerary.end = e.input instanceof GeoGraphVertex ? e.input.coords : e.input;
|
|
1871
|
+
}
|
|
1872
|
+
});
|
|
1873
|
+
return endsStructFiltered.map((e) => e.itinerary);
|
|
1874
|
+
}
|
|
1631
1875
|
createVertexInsideEdge(edge, point) {
|
|
1632
1876
|
const a = edge.vertex1;
|
|
1633
1877
|
const b = edge.vertex2;
|
|
@@ -1715,12 +1959,71 @@ const _GeoGraphRouter = class {
|
|
|
1715
1959
|
}
|
|
1716
1960
|
return GeoGraphItinerary.fromGraphVertices(start.coords, end.coords, path, edgesWeights);
|
|
1717
1961
|
}
|
|
1962
|
+
getShortestPathsBetweenGeoGraphVertices(start, ends, weightFn, acceptEdgeFn) {
|
|
1963
|
+
const distanceMap = {}, checking = {}, vertexList = {}, vertices = {}, parentVertices = {};
|
|
1964
|
+
this._graph.vertices.forEach((vertex) => {
|
|
1965
|
+
vertices[vertex.id] = vertex;
|
|
1966
|
+
distanceMap[vertex.id] = Infinity;
|
|
1967
|
+
checking[vertex.id] = null;
|
|
1968
|
+
vertexList[vertex.id] = true;
|
|
1969
|
+
});
|
|
1970
|
+
distanceMap[start.id] = 0;
|
|
1971
|
+
while (Object.keys(vertexList).length > 0) {
|
|
1972
|
+
const keys = Object.keys(vertexList).map(Number);
|
|
1973
|
+
const current = Number(
|
|
1974
|
+
keys.reduce((_checking, vertexId) => {
|
|
1975
|
+
return distanceMap[_checking] > distanceMap[vertexId] ? vertexId : _checking;
|
|
1976
|
+
}, keys[0])
|
|
1977
|
+
);
|
|
1978
|
+
this._graph.edges.filter((edge) => {
|
|
1979
|
+
if (acceptEdgeFn && !acceptEdgeFn(edge) || this.disabledEdges.has(edge)) {
|
|
1980
|
+
return false;
|
|
1981
|
+
}
|
|
1982
|
+
const from = edge.vertex1.id, to = edge.vertex2.id;
|
|
1983
|
+
return from === current || to === current;
|
|
1984
|
+
}).forEach((edge) => {
|
|
1985
|
+
let to, from, reversed = false;
|
|
1986
|
+
if (edge.vertex1.id === current) {
|
|
1987
|
+
to = edge.vertex2.id;
|
|
1988
|
+
from = edge.vertex1.id;
|
|
1989
|
+
} else {
|
|
1990
|
+
to = edge.vertex1.id;
|
|
1991
|
+
from = edge.vertex2.id;
|
|
1992
|
+
reversed = true;
|
|
1993
|
+
}
|
|
1994
|
+
if (edge.isOneway && reversed) {
|
|
1995
|
+
return;
|
|
1996
|
+
}
|
|
1997
|
+
const distance = distanceMap[current] + weightFn(edge);
|
|
1998
|
+
if (distanceMap[to] > distance) {
|
|
1999
|
+
distanceMap[to] = distance;
|
|
2000
|
+
checking[to] = current;
|
|
2001
|
+
parentVertices[to] = from;
|
|
2002
|
+
}
|
|
2003
|
+
});
|
|
2004
|
+
delete vertexList[current];
|
|
2005
|
+
}
|
|
2006
|
+
return ends.map((end) => {
|
|
2007
|
+
const edgesWeights = [];
|
|
2008
|
+
const path = [];
|
|
2009
|
+
let endId = end.id;
|
|
2010
|
+
while (typeof parentVertices[endId] !== "undefined") {
|
|
2011
|
+
path.unshift(vertices[endId]);
|
|
2012
|
+
edgesWeights.unshift(distanceMap[endId] - distanceMap[parentVertices[endId]]);
|
|
2013
|
+
endId = parentVertices[endId];
|
|
2014
|
+
}
|
|
2015
|
+
if (path.length !== 0) {
|
|
2016
|
+
path.unshift(start);
|
|
2017
|
+
}
|
|
2018
|
+
return GeoGraphItinerary.fromGraphVertices(start.coords, end.coords, path, edgesWeights);
|
|
2019
|
+
});
|
|
2020
|
+
}
|
|
1718
2021
|
};
|
|
1719
|
-
|
|
1720
|
-
__publicField(GeoGraphRouter, "DEFAULT_OPTIONS", {
|
|
2022
|
+
__publicField(_GeoGraphRouter, "DEFAULT_OPTIONS", {
|
|
1721
2023
|
projectionMaxDistance: 50,
|
|
1722
2024
|
weightEdgeFn: (edge) => edge.length
|
|
1723
2025
|
});
|
|
2026
|
+
let GeoGraphRouter = _GeoGraphRouter;
|
|
1724
2027
|
export {
|
|
1725
2028
|
AbsoluteHeading,
|
|
1726
2029
|
Attitude,
|