@wemap/geo 5.1.1 → 5.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,2946 @@
1
+ import { wrap, deg2rad, rad2deg, Quaternion, Vector3, positiveMod, Rotations, diffAngleLines } from '@wemap/maths';
2
+ import Logger from '@wemap/logger';
3
+
4
+ const Constants = {
5
+ R_MAJOR: 6378137.0,
6
+ R_MINOR: 6356752.3142,
7
+ EARTH_GRAVITY: 9.80665,
8
+
9
+ /**
10
+ * latitude and longitude epsilon in degrees
11
+ * 1e-8° correspond to ~1mm at latitude = 0
12
+ */
13
+ EPS_DEG_MM: 1e-8,
14
+
15
+ /**
16
+ * epsilon in meters which corresponds to 1 millimeter
17
+ */
18
+ EPS_MM: 1e-3
19
+ };
20
+
21
+ Constants.ELLIPSOID_FLATNESS = (Constants.R_MAJOR - Constants.R_MINOR) / Constants.R_MAJOR;
22
+
23
+ Constants.ECCENTRICITY = Math.sqrt(Constants.ELLIPSOID_FLATNESS * (2 - Constants.ELLIPSOID_FLATNESS));
24
+
25
+ Constants.R_MAJOR_2 = Constants.R_MAJOR * Constants.R_MAJOR;
26
+ Constants.R_MAJOR_4 = Constants.R_MAJOR_2 * Constants.R_MAJOR_2;
27
+ Constants.R_MINOR_2 = Constants.R_MINOR * Constants.R_MINOR;
28
+ Constants.R_MINOR_4 = Constants.R_MINOR_2 * Constants.R_MINOR_2;
29
+ Constants.ECCENTRICITY_2 = Constants.ECCENTRICITY * Constants.ECCENTRICITY;
30
+ Constants.CIRCUMFERENCE = Constants.R_MAJOR * 2 * Math.PI;
31
+
32
+ /**
33
+ * A Level is the representation of a building floor number
34
+ * A level can be a simple number (val) or a range (low, up)
35
+ * To know if a level is a range or a number, isRange can be used
36
+ */
37
+ class Level {
38
+
39
+ /**
40
+ * Level constructor
41
+ * 1 argument: level is not a range and first argument is the level
42
+ * 2 arguments: level is a range
43
+ * @param {number} arg1 if arg2: low value, otherwise: level
44
+ * @param {number} arg2 (optional) up value
45
+ */
46
+ constructor(arg1, arg2) {
47
+ if (typeof arg1 !== 'number' || isNaN(arg1)) {
48
+ throw new Error('first argument is mandatory');
49
+ }
50
+ if (typeof arg2 === 'number' && !isNaN(arg2)) {
51
+ if (arg1 === arg2) {
52
+ this.isRange = false;
53
+ this.val = arg1;
54
+ } else {
55
+ this.isRange = true;
56
+ this.low = Math.min(arg1, arg2);
57
+ this.up = Math.max(arg1, arg2);
58
+ }
59
+ } else if (typeof arg2 === 'undefined') {
60
+ this.isRange = false;
61
+ this.val = arg1;
62
+ } else {
63
+ throw new Error('second argument is not a number');
64
+ }
65
+ }
66
+
67
+ clone() {
68
+ if (this.isRange) {
69
+ return new Level(this.low, this.up);
70
+ }
71
+ return new Level(this.val);
72
+ }
73
+
74
+ /**
75
+ * Create a level from a string
76
+ * @param {string} str level in str format (eg. 1, -2, 1;2, -2;3, 2;-1, 0.5;1 ...)
77
+ */
78
+ static fromString(str) {
79
+
80
+ if (typeof str !== 'string') {
81
+ return null;
82
+ }
83
+
84
+ if (!isNaN(Number(str))) {
85
+ return new Level(parseFloat(str));
86
+ }
87
+
88
+ const splited = str.split(';');
89
+ if (splited.length === 2) {
90
+ if (!isNaN(Number(splited[0])) && !isNaN(Number(splited[1]))) {
91
+ return new Level(parseFloat(splited[0]), parseFloat(splited[1]));
92
+ }
93
+ }
94
+
95
+ Logger.warn('Cannot parse following level: ' + str);
96
+ return null;
97
+ }
98
+
99
+ /**
100
+ * Returns if the level is inside the given level
101
+ * @param {Level} level the container level
102
+ */
103
+ isInside(level) {
104
+ return Level.contains(level, this);
105
+ }
106
+
107
+ /**
108
+ * Returns if the level is inside the given level
109
+ * @param {Level} level the container level
110
+ */
111
+ contains(level) {
112
+ return Level.contains(this, level);
113
+ }
114
+
115
+
116
+ /**
117
+ * Returns if a level is contained in another
118
+ * @param {Level} container The container level
119
+ * @param {Level} targeted The targeted level
120
+ */
121
+ static contains(container, targeted) {
122
+
123
+ if (container === targeted) {
124
+ return true;
125
+ }
126
+
127
+ if (!container || !targeted) {
128
+ return false;
129
+ }
130
+
131
+ if (!container.isRange) {
132
+ if (targeted.isRange) {
133
+ return false;
134
+ }
135
+ return container.val === targeted.val;
136
+ }
137
+
138
+ return container.up >= (targeted.isRange ? targeted.up : targeted.val)
139
+ && container.low <= (targeted.isRange ? targeted.low : targeted.val);
140
+ }
141
+
142
+ /**
143
+ * Retrieve the intersection of two levels
144
+ * @param {Level} other The other level
145
+ */
146
+ intersect(other) {
147
+ return Level.intersect(this, other);
148
+ }
149
+
150
+ /**
151
+ * Retrieve the intersection of two levels
152
+ * @param {Level} first The first level
153
+ * @param {Level} second The second level
154
+ */
155
+ static intersect(first, second) {
156
+
157
+ if (first === second) {
158
+ if (first instanceof Level) {
159
+ return first.clone();
160
+ }
161
+ return null;
162
+ }
163
+
164
+ if (!second) {
165
+ return null;
166
+ }
167
+
168
+ if (!first) {
169
+ return null;
170
+ }
171
+
172
+ if (first.isRange && !second.isRange) {
173
+ if (first.contains(second)) {
174
+ return second.clone();
175
+ }
176
+ return null;
177
+ }
178
+ if (!first.isRange && second.isRange) {
179
+ if (second.contains(first)) {
180
+ return first.clone();
181
+ }
182
+ return null;
183
+ }
184
+ if (first.isRange && second.isRange) {
185
+ const up = Math.min(first.up, second.up);
186
+ const low = Math.max(first.low, second.low);
187
+ if (up < low) {
188
+ return null;
189
+ }
190
+ return new Level(low, up);
191
+ }
192
+ return first.val === second.val ? first : null;
193
+ }
194
+
195
+ /**
196
+ * Retrieve the union of two levels
197
+ * @param {Level} other The other level
198
+ */
199
+ union(other) {
200
+ return Level.union(this, other);
201
+ }
202
+
203
+ /**
204
+ * Retrieve the union of two levels
205
+ * @param {Level} other The other level
206
+ */
207
+ static union(first, second) {
208
+
209
+ if (first === second) {
210
+ if (first instanceof Level) {
211
+ return first.clone();
212
+ }
213
+ return null;
214
+ }
215
+
216
+ if (!second) {
217
+ return first.clone();
218
+ }
219
+
220
+ if (!first) {
221
+ return second.clone();
222
+ }
223
+
224
+ let low, up;
225
+ if (!first.isRange && !second.isRange) {
226
+ low = Math.min(first.val, second.val);
227
+ up = Math.max(first.val, second.val);
228
+ } else if (first.isRange && !second.isRange) {
229
+ low = Math.min(first.low, second.val);
230
+ up = Math.max(first.up, second.val);
231
+ } else if (!first.isRange && second.isRange) {
232
+ low = Math.min(second.low, first.val);
233
+ up = Math.max(second.up, first.val);
234
+ } else {
235
+ /* if (first.isRange && second.isRange) */
236
+ low = Math.min(second.low, first.low);
237
+ up = Math.max(second.up, first.up);
238
+ }
239
+
240
+ if (low === up) {
241
+ return new Level(low);
242
+ }
243
+ return new Level(low, up);
244
+ }
245
+
246
+ multiplyBy(factor) {
247
+ if (this.isRange) {
248
+ this.low *= factor;
249
+ this.up *= factor;
250
+ } else {
251
+ this.val *= factor;
252
+ }
253
+ return this;
254
+ }
255
+
256
+ toString() {
257
+ return this.isRange ? this.low + ';' + this.up : String(this.val);
258
+ }
259
+
260
+ static equalsTo(first, second) {
261
+
262
+ if (first === second) {
263
+ return true;
264
+ }
265
+ if (!first && !second) {
266
+ return true;
267
+ }
268
+ if (!first && second) {
269
+ return false;
270
+ }
271
+ if (first && !second) {
272
+ return false;
273
+ }
274
+ if (!first.isRange && second.isRange) {
275
+ return false;
276
+ }
277
+ if (first.isRange && !second.isRange) {
278
+ return false;
279
+ }
280
+ if (first.isRange && second.isRange) {
281
+ return first.low === second.low && first.up === second.up;
282
+ }
283
+ // !first.isRange && !second.isRange
284
+ return first.val === second.val;
285
+
286
+ }
287
+
288
+ static diff(first, second) {
289
+
290
+ if (first === null || second === null) {
291
+ return null;
292
+ }
293
+
294
+ if (!first.isRange && !second.isRange) {
295
+ return second.val - first.val;
296
+ }
297
+
298
+ if (first.isRange && !second.isRange) {
299
+ if (first.low === second.val) {
300
+ return second.val - first.up;
301
+ }
302
+ if (first.up === second.val) {
303
+ return second.val - first.low;
304
+ }
305
+ return null;
306
+ }
307
+
308
+ if (second.isRange && !first.isRange) {
309
+ if (first.val === second.low) {
310
+ return second.up - first.val;
311
+ }
312
+ if (first.val === second.up) {
313
+ return second.low - first.val;
314
+ }
315
+ return null;
316
+ }
317
+
318
+ if (Level.equalsTo(first, second)) {
319
+ return 0;
320
+ }
321
+
322
+ return null;
323
+ }
324
+
325
+ }
326
+
327
+ /**
328
+ * A Coordinates position using at least latitude (lat) and longitude (lng).
329
+ * Optionnal fields are: altitude (alt) and level.
330
+ *
331
+ * Basic geo methods are directly accessibles from here:
332
+ * distanceTo, bearingTo, toEcef...
333
+ *
334
+ * /!\ This class has been adapted to use earth as a sphere and not as an ellipsoid
335
+ * /!\ So, this class does not stricly represent WGS84 coordinates anymore
336
+ * /!\ This modifications have been made for computational improvements.
337
+ */
338
+ class Coordinates {
339
+
340
+ autoWrap = true;
341
+
342
+ constructor(lat, lng, alt, level) {
343
+ this.lat = lat;
344
+ this.lng = lng;
345
+ this.alt = alt;
346
+ this.level = level;
347
+ this._ecef = null;
348
+ }
349
+
350
+ get lat() {
351
+ return this._lat;
352
+ }
353
+
354
+ get latitude() {
355
+ return this._lat;
356
+ }
357
+
358
+ get lng() {
359
+ return this._lng;
360
+ }
361
+
362
+ get longitude() {
363
+ return this._lng;
364
+ }
365
+
366
+ /**
367
+ * alt does not denote the altitude of a point but its height from
368
+ * the "level" field (if defined) or from the ground
369
+ */
370
+ get alt() {
371
+ return this._alt;
372
+ }
373
+
374
+ get level() {
375
+ return this._level;
376
+ }
377
+
378
+ set lat(lat) {
379
+ if (typeof lat === 'number' && Math.abs(lat) <= 90) {
380
+ this._lat = lat;
381
+ } else {
382
+ throw new Error('lat argument is not in [-90; 90]');
383
+ }
384
+ this._ecef = null;
385
+ }
386
+
387
+ set latitude(lat) {
388
+ throw new Error('Please use Coordinate#lat setter instead of Coordinate#latitude');
389
+ }
390
+
391
+ set lng(lng) {
392
+ if (typeof lng === 'number') {
393
+ this._lng = lng;
394
+ if (this.autoWrap) {
395
+ this.wrap();
396
+ }
397
+ } else {
398
+ throw new Error('lng argument is not a number');
399
+ }
400
+ this._ecef = null;
401
+ }
402
+
403
+ set longitude(lng) {
404
+ throw new Error('Please use Coordinate#lng setter instead of Coordinate#longitude');
405
+ }
406
+
407
+ set alt(alt) {
408
+ if (typeof alt === 'number') {
409
+ this._alt = alt;
410
+ } else {
411
+ if (typeof alt !== 'undefined' && alt !== null) {
412
+ throw new Error('alt argument is not a finite number');
413
+ }
414
+ this._alt = null;
415
+ }
416
+ this._ecef = null;
417
+ }
418
+
419
+ set level(level) {
420
+ if (level instanceof Level) {
421
+ this._level = level;
422
+ } else {
423
+ if (typeof level !== 'undefined' && level !== null) {
424
+ throw new Error('level argument is not a Level object');
425
+ }
426
+ this._level = null;
427
+ }
428
+ }
429
+
430
+ clone() {
431
+ const output = new Coordinates(this.lat, this.lng, this.alt);
432
+ if (this.level) {
433
+ output.level = this.level.clone();
434
+ }
435
+ return output;
436
+ }
437
+
438
+ wrap() {
439
+ if (this._lng <= -180 || this._lng > 180) {
440
+ this._lng = wrap(this._lng, -180, 180);
441
+ }
442
+ }
443
+
444
+ /**
445
+ * Compares two Coordinates
446
+ * @param {Coordinates} pos1 position 1
447
+ * @param {Coordinates} pos2 position 2
448
+ * @param {Number} eps latitude and longitude epsilon in degrees (default: 1e-8 [~1mm at lat=0])
449
+ * @param {Number} epsAlt altitude epsilon in meters (default: 1e-3 [= 1mm])
450
+ */
451
+ static equalsTo(pos1, pos2, eps = Constants.EPS_DEG_MM, epsAlt = Constants.EPS_MM) {
452
+
453
+ // Handle null comparison
454
+ if (pos1 === null && pos1 === pos2) {
455
+ return true;
456
+ }
457
+
458
+ if (!(pos1 instanceof Coordinates) || !(pos2 instanceof Coordinates)) {
459
+ return false;
460
+ }
461
+
462
+ return Math.abs(pos2.lat - pos1.lat) < eps
463
+ && Math.abs(pos2.lng - pos1.lng) < eps
464
+ && (pos1.alt === pos2.alt
465
+ || pos1.alt !== null && pos2.alt !== null
466
+ && Math.abs(pos2.alt - pos1.alt) < epsAlt)
467
+ && Level.equalsTo(pos1.level, pos2.level);
468
+ }
469
+
470
+ equalsTo(other) {
471
+ return Coordinates.equalsTo(this, other);
472
+ }
473
+
474
+ destinationPoint(distance, bearing, elevation) {
475
+ const newPoint = this.clone();
476
+ newPoint.move(distance, bearing, elevation);
477
+ return newPoint;
478
+ }
479
+
480
+ // Source: http://www.movable-type.co.uk/scripts/latlong.html#destPoint
481
+ move(distance, bearing, elevation) {
482
+
483
+ const dR = distance / Constants.R_MAJOR;
484
+ const cosDr = Math.cos(dR);
485
+ const sinDr = Math.sin(dR);
486
+
487
+ const phi1 = deg2rad(this.lat);
488
+ const lambda1 = deg2rad(this.lng);
489
+
490
+ const phi2 = Math.asin(
491
+ Math.sin(phi1) * cosDr
492
+ + Math.cos(phi1) * sinDr * Math.cos(bearing)
493
+ );
494
+ const lambda2 = lambda1 + Math.atan2(
495
+ Math.sin(bearing) * sinDr * Math.cos(phi1),
496
+ cosDr - Math.sin(phi1) * Math.sin(phi2)
497
+ );
498
+
499
+ this.lat = rad2deg(phi2);
500
+ this.lng = rad2deg(lambda2);
501
+
502
+ if (typeof elevation === 'number') {
503
+ if (this.alt === null) {
504
+ throw new Error('Point altitude is not defined');
505
+ }
506
+ this.alt += elevation;
507
+ }
508
+
509
+
510
+ return this;
511
+ }
512
+
513
+ /**
514
+ * Returns a distance between two points in meters
515
+ * @param {Coordinates} location2 latitude / longitude point
516
+ * @return {Number} distance in meters
517
+ */
518
+ distanceTo(location2) {
519
+ const lat1 = this.lat;
520
+ const lng1 = this.lng;
521
+
522
+ const lat2 = location2.lat;
523
+ const lng2 = location2.lng;
524
+
525
+ const dlat = deg2rad(lat2 - lat1);
526
+ const dlng = deg2rad(lng2 - lng1);
527
+
528
+ const dlngsin = Math.sin(dlng / 2);
529
+ const dlatsin = Math.sin(dlat / 2);
530
+ const lat1rad = deg2rad(lat1);
531
+ const lat1cos = Math.cos(lat1rad);
532
+ const lat2rad = deg2rad(lat2);
533
+ const lat2cos = Math.cos(lat2rad);
534
+ const angle = dlatsin * dlatsin + lat1cos * lat2cos * dlngsin * dlngsin;
535
+
536
+ // arctangent
537
+ const tangy = Math.sqrt(angle);
538
+ const tangx = Math.sqrt(1 - angle);
539
+ const cosn = 2 * Math.atan2(tangy, tangx);
540
+
541
+ return Constants.R_MAJOR * cosn;
542
+ }
543
+
544
+ static distanceBetween(point1, point2) {
545
+ return point1.distanceTo(point2);
546
+ }
547
+
548
+ bearingTo(location2) {
549
+ const lat1 = deg2rad(this.lat);
550
+ const lat2 = deg2rad(location2.lat);
551
+ const diffLng = deg2rad(location2.lng - this.lng);
552
+
553
+ return Math.atan2(Math.sin(diffLng) * Math.cos(lat2),
554
+ Math.cos(lat1) * Math.sin(lat2) - Math.sin(lat1) * Math.cos(lat2) * Math.cos(diffLng));
555
+ }
556
+
557
+ static bearingTo(point1, point2) {
558
+ return point1.bearingTo(point2);
559
+ }
560
+
561
+
562
+ /**
563
+ * ECEF Transformations
564
+ * Here we used a light version of ECEF considering earth
565
+ * as a sphere instead of an ellipse
566
+ */
567
+
568
+ get enuToEcefRotation() {
569
+ const rot1 = Quaternion.fromAxisAngle([0, 0, 1], Math.PI / 2 + deg2rad(this.lng));
570
+ const rot2 = Quaternion.fromAxisAngle([1, 0, 0], Math.PI / 2 - deg2rad(this.lat));
571
+ return Quaternion.multiply(rot1, rot2);
572
+ }
573
+
574
+ get ecefToEnuRotation() {
575
+ const rot1 = Quaternion.fromAxisAngle([1, 0, 0], deg2rad(this.lat) - Math.PI / 2);
576
+ const rot2 = Quaternion.fromAxisAngle([0, 0, 1], -deg2rad(this.lng) - Math.PI / 2);
577
+ return Quaternion.multiply(rot1, rot2);
578
+ }
579
+
580
+ // https://gist.github.com/klucar/1536194
581
+ // Adapted for spherical formula
582
+ get ecef() {
583
+
584
+ if (!this._ecef) {
585
+ const lat = deg2rad(this.lat);
586
+ const lng = deg2rad(this.lng);
587
+ const alt = this.alt || 0;
588
+
589
+ const x = (Constants.R_MAJOR + alt) * Math.cos(lat) * Math.cos(lng);
590
+ const y = (Constants.R_MAJOR + alt) * Math.cos(lat) * Math.sin(lng);
591
+ const z = (Constants.R_MAJOR + alt) * Math.sin(lat);
592
+
593
+ this._ecef = [x, y, z];
594
+ }
595
+
596
+ return this._ecef;
597
+ }
598
+
599
+ static fromECEF(ecef) {
600
+
601
+ const x = ecef[0];
602
+ const y = ecef[1];
603
+ const z = ecef[2];
604
+
605
+ const p = Math.sqrt(x ** 2 + y ** 2);
606
+
607
+ let lng = Math.atan2(y, x);
608
+ const lat = Math.atan2(z, p);
609
+ const alt = p / Math.cos(lat) - Constants.R_MAJOR;
610
+
611
+ lng = lng % (2 * Math.PI);
612
+
613
+ const newPoint = new Coordinates(rad2deg(lat), rad2deg(lng), alt);
614
+ newPoint._ecef = ecef;
615
+ return newPoint;
616
+ }
617
+
618
+
619
+ // https://stackoverflow.com/questions/1299567/how-to-calculate-distance-from-a-point-to-a-line-segment-on-a-sphere
620
+ // Adapted to ECEF
621
+ getSegmentProjection(p1, p2) {
622
+
623
+ const a = Vector3.normalize(p1.ecef);
624
+ const b = Vector3.normalize(p2.ecef);
625
+ const c = Vector3.normalize(this.ecef);
626
+
627
+ const G = Vector3.cross(a, b);
628
+ if (Vector3.norm(G) === 0) {
629
+ return null;
630
+ }
631
+
632
+ const F = Vector3.cross(c, G);
633
+ const t = Vector3.normalize(Vector3.cross(G, F));
634
+
635
+ const posECEF = Vector3.multiplyScalar(t, Constants.R_MAJOR);
636
+ const poseCoordinates = Coordinates.fromECEF(posECEF);
637
+
638
+ // poseCoordinates.alt is not 0 here due to the ECEF transformation residual.
639
+ // So if p1.alt and p2.alt are defined we take the middle elevation between p1 and p2.
640
+ // Otherwise we remove alt from projection because the residual has no sense.
641
+ let alt;
642
+ if (p1.alt !== null && p2.alt !== null) {
643
+ // This formula is maybe not the best one.
644
+ alt = (p1.alt + p2.alt) / 2;
645
+ }
646
+ const projection = new Coordinates(poseCoordinates.lat, poseCoordinates.lng,
647
+ alt, Level.intersect(p1.level, p2.level));
648
+
649
+ if (Math.abs((p1.distanceTo(p2) - p1.distanceTo(projection) - p2.distanceTo(projection))) > Constants.EPS_MM) {
650
+ return null;
651
+ }
652
+
653
+ return projection;
654
+ }
655
+
656
+ /**
657
+ * Input / Output
658
+ */
659
+
660
+ toString() {
661
+ let str = '[' + this._lat.toFixed(7) + ', ' + this._lng.toFixed(7);
662
+ if (this._alt !== null) {
663
+ str += ', ' + this._alt.toFixed(2);
664
+ }
665
+ if (this._level !== null) {
666
+ str += ', [' + this._level.toString() + ']';
667
+ }
668
+ str += ']';
669
+ return str;
670
+ }
671
+
672
+ toJson() {
673
+ const output = {
674
+ lat: this.lat,
675
+ lng: this.lng
676
+ };
677
+ if (this.alt !== null) {
678
+ output.alt = this.alt;
679
+ }
680
+ if (this.level !== null) {
681
+ output.level = this.level.toString();
682
+ }
683
+ return output;
684
+ }
685
+
686
+ static fromJson(json) {
687
+ return new Coordinates(json.lat, json.lng, json.alt, Level.fromString(json.level));
688
+ }
689
+
690
+ toCompressedJson() {
691
+ const output = [this.lat, this.lng];
692
+ if (this.alt !== null || this.level !== null) {
693
+ output.push(this.alt);
694
+ }
695
+ if (this.level !== null) {
696
+ output.push(this.level.toString());
697
+ }
698
+ return output;
699
+ }
700
+
701
+ static fromCompressedJson(json) {
702
+ const coords = new Coordinates(json[0], json[1]);
703
+ if (json.length > 2) {
704
+ coords.alt = json[2];
705
+ }
706
+ if (json.length > 3) {
707
+ coords.level = Level.fromString(json[3]);
708
+ }
709
+ return coords;
710
+ }
711
+ }
712
+
713
+ /**
714
+ * A Coordinates User Position is a Coordinates position with specific data related to user (bearing, time, accuracy)
715
+ */
716
+ class UserPosition extends Coordinates {
717
+
718
+ _time = null;
719
+ _accuracy = null;
720
+ _bearing = null;
721
+
722
+ constructor(lat, lng, alt, level, time, accuracy, bearing) {
723
+ super(lat, lng, alt, level);
724
+ this.time = time;
725
+ this.accuracy = accuracy;
726
+ this.bearing = bearing;
727
+ }
728
+
729
+ get time() {
730
+ return this._time;
731
+ }
732
+
733
+ set time(time) {
734
+ if (typeof time === 'number') {
735
+ this._time = time;
736
+ } else {
737
+ if (typeof time !== 'undefined' && time !== null) {
738
+ throw new Error('time argument is not a number');
739
+ }
740
+ this._time = null;
741
+ }
742
+ }
743
+
744
+
745
+ get accuracy() {
746
+ return this._accuracy;
747
+ }
748
+
749
+ set accuracy(accuracy) {
750
+ if (typeof accuracy === 'number' && accuracy >= 0) {
751
+ this._accuracy = accuracy;
752
+ } else {
753
+ if (typeof accuracy !== 'undefined' && accuracy !== null) {
754
+ throw new Error('accuracy argument is not a positive number');
755
+ }
756
+ this._accuracy = null;
757
+ }
758
+ }
759
+
760
+
761
+ get bearing() {
762
+ return this._bearing;
763
+ }
764
+
765
+ set bearing(bearing) {
766
+ if (typeof bearing === 'number') {
767
+ this._bearing = bearing % (2 * Math.PI);
768
+ } else {
769
+ if (typeof bearing !== 'undefined' && bearing !== null) {
770
+ throw new Error('bearing argument is not a number');
771
+ }
772
+ this._bearing = null;
773
+ }
774
+ }
775
+
776
+
777
+ // Create a UserPosition with lat, lng, alt from Coordinates coordinates and
778
+ // other fields from another UserPosition
779
+ static fromCoordinates(coordinates) {
780
+ return new UserPosition(coordinates.lat, coordinates.lng,
781
+ coordinates.alt, coordinates.level);
782
+ }
783
+
784
+ clone() {
785
+ const cloned = UserPosition.fromCoordinates(super.clone());
786
+ cloned.time = this.time;
787
+ cloned.accuracy = this.accuracy;
788
+ cloned.bearing = this.bearing;
789
+ return cloned;
790
+ }
791
+
792
+
793
+ /**
794
+ * Compares two UserPosition
795
+ * @param {UserPosition} pos1 position 1
796
+ * @param {UserPosition} pos2 position 2
797
+ * @param {Number} eps latitude and longitude epsilon in degrees (default: 1e-8 [~1mm at lat=0])
798
+ * @param {Number} epsAlt altitude epsilon in meters (default: 1e-3 [= 1mm])
799
+ */
800
+ static equalsTo(pos1, pos2, eps = Constants.EPS_DEG_MM, epsAlt = Constants.EPS_MM) {
801
+
802
+ // Handle null comparison
803
+ if (pos1 === null && pos1 === pos2) {
804
+ return true;
805
+ }
806
+
807
+ if (!(pos1 instanceof UserPosition) || !(pos2 instanceof UserPosition)) {
808
+ return false;
809
+ }
810
+
811
+ if (!super.equalsTo(pos1, pos2, eps, epsAlt)) {
812
+ return false;
813
+ }
814
+
815
+ return pos1.time === pos2.time
816
+ && pos1.accuracy === pos2.accuracy
817
+ && pos1.bearing === pos2.bearing;
818
+ }
819
+
820
+ equalsTo(other) {
821
+ return UserPosition.equalsTo(this, other);
822
+ }
823
+
824
+
825
+ toJson() {
826
+ const output = super.toJson();
827
+ if (this.time !== null) {
828
+ output.time = this.time;
829
+ }
830
+ if (this.accuracy !== null) {
831
+ output.accuracy = this.accuracy;
832
+ }
833
+ if (this.bearing !== null) {
834
+ output.bearing = this.bearing;
835
+ }
836
+ return output;
837
+ }
838
+
839
+ static fromJson(json) {
840
+ const position = UserPosition.fromCoordinates(Coordinates.fromJson(json));
841
+ position.time = json.time;
842
+ position.accuracy = json.accuracy;
843
+ position.bearing = json.bearing;
844
+ return position;
845
+ }
846
+ }
847
+
848
+ /* eslint-disable max-statements */
849
+
850
+ /**
851
+ * Sample a route of Coordinates
852
+ * @param {Array.<Coordinates>} route ordered points
853
+ * @param {*} stepSize step size to sample
854
+ * @param {*} maxLength max route length to sample
855
+ */
856
+ function sampleRoute(route, stepSize = 0.7, startSampling = 0, length = Number.MAX_VALUE) {
857
+
858
+ const endSampling = startSampling + length;
859
+
860
+ const sampledRoute = [];
861
+
862
+ let lastSample = null;
863
+
864
+ let totalDistanceTraveled = 0;
865
+ let distanceToNextSample;
866
+ let startFound = false;
867
+
868
+ for (let segmentIndex = 0; segmentIndex < route.length - 1; segmentIndex++) {
869
+
870
+ const p1 = route[segmentIndex];
871
+ const p2 = route[segmentIndex + 1];
872
+ const segmentSize = p1.distanceTo(p2);
873
+ const segmentBearing = p1.bearingTo(p2);
874
+
875
+ let distanceTraveledOnSegment = 0;
876
+
877
+ if (!startFound) {
878
+ if (startSampling < totalDistanceTraveled + segmentSize) {
879
+ startFound = true;
880
+ distanceToNextSample = startSampling - totalDistanceTraveled;
881
+ } else {
882
+ totalDistanceTraveled += segmentSize;
883
+ continue;
884
+ }
885
+ }
886
+
887
+ lastSample = p1;
888
+ while (distanceTraveledOnSegment + distanceToNextSample < segmentSize
889
+ && totalDistanceTraveled + distanceToNextSample <= endSampling) {
890
+
891
+ const newPoint = lastSample.destinationPoint(distanceToNextSample, segmentBearing);
892
+ newPoint.bearing = segmentBearing;
893
+ sampledRoute.push(newPoint);
894
+ lastSample = newPoint;
895
+
896
+ distanceTraveledOnSegment += distanceToNextSample;
897
+ totalDistanceTraveled += distanceToNextSample;
898
+ distanceToNextSample = stepSize;
899
+ }
900
+
901
+ if (totalDistanceTraveled + distanceToNextSample > endSampling) {
902
+ break;
903
+ }
904
+
905
+ const rest = segmentSize - distanceTraveledOnSegment;
906
+ totalDistanceTraveled += rest;
907
+ distanceToNextSample -= rest;
908
+ }
909
+
910
+ return sampledRoute;
911
+ }
912
+
913
+ /**
914
+ * Trim a route of Coordinates
915
+ * @param {Array.<Coordinates>} route ordered points
916
+ * @param {Coordinates} startPosition position where the trim starts. startPosition has to be on the route.
917
+ * @param {*} maxLength max route length
918
+ */
919
+ function trimRoute(route, startPosition = route[0], length = Number.MAX_VALUE) {
920
+
921
+ const newRoute = [];
922
+ let previousPoint;
923
+
924
+ let currentPointIndex;
925
+ let cumulativeDistance = 0;
926
+
927
+ for (currentPointIndex = 1; currentPointIndex < route.length; currentPointIndex++) {
928
+
929
+ const p1 = route[currentPointIndex - 1];
930
+ const p2 = route[currentPointIndex];
931
+
932
+ if (Coordinates.equalsTo(startPosition, p1)) {
933
+ newRoute.push(p1);
934
+ previousPoint = p1;
935
+ break;
936
+ }
937
+
938
+ const proj = startPosition.getSegmentProjection(p1, p2);
939
+ if (proj && Coordinates.equalsTo(startPosition, proj) && !proj.equalsTo(p2)) {
940
+ newRoute.push(proj);
941
+ previousPoint = proj;
942
+ break;
943
+ }
944
+ }
945
+
946
+ if (!newRoute.length) {
947
+ throw new Error('startPosition is not on the route');
948
+ }
949
+
950
+ while (currentPointIndex < route.length) {
951
+ const currentPoint = route[currentPointIndex];
952
+ const dist = previousPoint.distanceTo(currentPoint);
953
+ if (cumulativeDistance + dist >= length
954
+ || Math.abs(cumulativeDistance + dist - length) <= Constants.EPS_MM) {
955
+ const bearing = previousPoint.bearingTo(currentPoint);
956
+ const remainingLength = length - cumulativeDistance;
957
+ const end = previousPoint.destinationPoint(remainingLength, bearing);
958
+ newRoute.push(end);
959
+ break;
960
+ }
961
+ newRoute.push(currentPoint);
962
+ previousPoint = currentPoint;
963
+ cumulativeDistance += dist;
964
+ currentPointIndex++;
965
+ }
966
+
967
+ return newRoute;
968
+ }
969
+
970
+ /**
971
+ * @param {Coordinates[]} coords
972
+ * @param {number} precision
973
+ * @returns {Coordinates[]}
974
+ */
975
+ function simplifyRoute(coords, precisionAngle = deg2rad(5)) {
976
+
977
+ const isClosed = (coords[0].equalsTo(coords[coords.length - 1]));
978
+
979
+ let newRoute = coords.slice(0, coords.length - (isClosed ? 1 : 0));
980
+
981
+ const len = newRoute.length;
982
+ for (let i = isClosed ? 0 : 1; i < len; i++) {
983
+
984
+ const p0 = coords[positiveMod(i - 1, len)];
985
+ const p1 = coords[i];
986
+ const p2 = coords[positiveMod(i + 1, len)];
987
+
988
+ const seg1Dir = p0.bearingTo(p1);
989
+ const seg2Dir = p1.bearingTo(p2);
990
+
991
+ if (Math.abs(seg2Dir - seg1Dir) < precisionAngle) {
992
+ newRoute = newRoute.filter(coord => coord !== p1);
993
+ }
994
+ }
995
+
996
+ if (isClosed) {
997
+ newRoute.push(newRoute[0]);
998
+ }
999
+
1000
+ return newRoute;
1001
+ }
1002
+
1003
+ /**
1004
+ * @param {GeolocationPosition} geolocationPosition
1005
+ * @returns {UserPosition}
1006
+ */
1007
+ function geolocationPositionToUserPosition(geolocationPosition) {
1008
+ if (geolocationPosition === null) {
1009
+ return null;
1010
+ }
1011
+
1012
+ const { latitude, longitude, accuracy, heading } = geolocationPosition.coords;
1013
+
1014
+ const userPosition = new UserPosition(latitude, longitude);
1015
+ userPosition.time = geolocationPosition.timestamp;
1016
+ userPosition.accuracy = accuracy;
1017
+ userPosition.bearing = heading ? deg2rad(heading) : null;
1018
+ return userPosition;
1019
+ }
1020
+
1021
+ var Utils = /*#__PURE__*/Object.freeze({
1022
+ __proto__: null,
1023
+ sampleRoute: sampleRoute,
1024
+ trimRoute: trimRoute,
1025
+ simplifyRoute: simplifyRoute,
1026
+ geolocationPositionToUserPosition: geolocationPositionToUserPosition
1027
+ });
1028
+
1029
+ class BoundingBox {
1030
+
1031
+ northEast;
1032
+ southWest;
1033
+
1034
+ constructor(northEast, southWest) {
1035
+ this.northEast = northEast || null;
1036
+ this.southWest = southWest || null;
1037
+
1038
+ if (this.northEast && this.southWest && this.getNorth() < this.getSouth()) {
1039
+ throw new Error('Incorrect bounding box');
1040
+ }
1041
+ }
1042
+
1043
+ /**
1044
+ * Returns the geographical coordinate equidistant from the bounding box's corners.
1045
+ *
1046
+ * @returns {Coordinates} The bounding box's center.
1047
+ */
1048
+ get center() {
1049
+ const latCenter = (this.southWest.lat + this.northEast.lat) / 2;
1050
+ const lngCenter = (this.northEast.lng + this.southWest.lng) / 2;
1051
+ return new Coordinates(latCenter, lngCenter);
1052
+ }
1053
+
1054
+ /**
1055
+ * Check if a point is contained in the bounding box.
1056
+ *
1057
+ * @returns {Coordinates} The point to analyze.
1058
+ */
1059
+ contains(point) {
1060
+ return point.lat <= this.northEast.lat
1061
+ && point.lat >= this.southWest.lat
1062
+ && point.lng <= this.northEast.lng
1063
+ && point.lng >= this.southWest.lng;
1064
+ }
1065
+
1066
+ /**
1067
+ * Extend the bounds to include a given LngLat or LngLatBounds.
1068
+ *
1069
+ * @param {Coordinates|BoundingBox} obj object to extend to
1070
+ * @returns {BoundingBox} `this`
1071
+ */
1072
+ extend(obj) {
1073
+ const sw = this.southWest,
1074
+ ne = this.northEast;
1075
+ let sw2, ne2;
1076
+
1077
+ if (obj instanceof Coordinates) {
1078
+ sw2 = obj;
1079
+ ne2 = obj;
1080
+
1081
+ } else if (obj instanceof BoundingBox) {
1082
+ sw2 = obj.southWest;
1083
+ ne2 = obj.northEast;
1084
+
1085
+ if (!sw2 || !ne2) {
1086
+ return this;
1087
+ }
1088
+ } else {
1089
+ throw new Error('Unknown parameter');
1090
+ }
1091
+
1092
+ if (!sw && !ne) {
1093
+ this.southWest = new Coordinates(sw2.lat, sw2.lng);
1094
+ this.northEast = new Coordinates(ne2.lat, ne2.lng);
1095
+
1096
+ } else {
1097
+ this.southWest = new Coordinates(
1098
+ Math.min(sw2.lat, sw.lat),
1099
+ Math.min(sw2.lng, sw.lng)
1100
+ );
1101
+ this.northEast = new Coordinates(
1102
+ Math.max(ne2.lat, ne.lat),
1103
+ Math.max(ne2.lng, ne.lng)
1104
+ );
1105
+ }
1106
+
1107
+ return this;
1108
+ }
1109
+
1110
+ /**
1111
+ * This method extends the bounding box with a value in meters
1112
+ * /*\ This method is not precise as distance differs in function of latitude
1113
+ * @param {!number} measure in meters
1114
+ */
1115
+ extendsWithMeasure(measure) {
1116
+
1117
+ if (typeof measure !== 'number') {
1118
+ throw new Error('measure is not a number');
1119
+ }
1120
+
1121
+ this.northEast = this.northEast
1122
+ .destinationPoint(measure, 0)
1123
+ .move(measure, Math.PI / 2);
1124
+
1125
+ this.southWest = this.southWest.clone()
1126
+ .destinationPoint(measure, -Math.PI / 2)
1127
+ .destinationPoint(measure, Math.PI);
1128
+
1129
+ return this;
1130
+ }
1131
+
1132
+ /**
1133
+ * Returns bounds created by extending or retracting the current bounds by a given ratio in each direction.
1134
+ * For example, a ratio of 0.5 extends the bounds by 50% in each direction.
1135
+ * Negative values will retract the bounds.
1136
+ * @param {Number} bufferRatio
1137
+ * @returns {BoundingBox} `this`
1138
+ */
1139
+ pad(bufferRatio) {
1140
+ const sw = this.southWest;
1141
+ const ne = this.northEast;
1142
+
1143
+ const heightBuffer = Math.abs(sw.lat - ne.lat) * bufferRatio;
1144
+ const widthBuffer = Math.abs(sw.lng - ne.lng) * bufferRatio;
1145
+
1146
+ this.southWest = new Coordinates(sw.lat - heightBuffer, sw.lng - widthBuffer);
1147
+ this.northEast = new Coordinates(ne.lat + heightBuffer, ne.lng + widthBuffer);
1148
+
1149
+ return this;
1150
+ }
1151
+
1152
+ /**
1153
+ * Returns the southwest corner of the bounding box.
1154
+ *
1155
+ * @returns {Coordinates} The southwest corner of the bounding box.
1156
+ */
1157
+ getSouthWest() {
1158
+ return this.southWest;
1159
+ }
1160
+
1161
+ /**
1162
+ * Returns the northeast corner of the bounding box.
1163
+ *
1164
+ * @returns {Coordinates} The northeast corner of the bounding box.
1165
+ */
1166
+ getNorthEast() {
1167
+ return this.northEast;
1168
+ }
1169
+
1170
+ /**
1171
+ * Returns the northwest corner of the bounding box.
1172
+ *
1173
+ * @returns {Coordinates} The northwest corner of the bounding box.
1174
+ */
1175
+ getNorthWest() {
1176
+ return new Coordinates(this.getNorth(), this.getWest());
1177
+ }
1178
+
1179
+ /**
1180
+ * Returns the southeast corner of the bounding box.
1181
+ *
1182
+ * @returns {LngLat} The southeast corner of the bounding box.
1183
+ */
1184
+ getSouthEast() {
1185
+ return new Coordinates(this.getSouth(), this.getEast());
1186
+ }
1187
+
1188
+ /**
1189
+ * Returns the west edge of the bounding box.
1190
+ *
1191
+ * @returns {number} The west edge of the bounding box.
1192
+ */
1193
+ getWest() {
1194
+ return this.southWest.lng;
1195
+ }
1196
+
1197
+ /**
1198
+ * Returns the south edge of the bounding box.
1199
+ *
1200
+ * @returns {number} The south edge of the bounding box.
1201
+ */
1202
+ getSouth() {
1203
+ return this.southWest.lat;
1204
+ }
1205
+
1206
+ /**
1207
+ * Returns the east edge of the bounding box.
1208
+ *
1209
+ * @returns {number} The east edge of the bounding box.
1210
+ */
1211
+ getEast() {
1212
+ return this.northEast.lng;
1213
+ }
1214
+
1215
+ /**
1216
+ * Returns the north edge of the bounding box.
1217
+ *
1218
+ * @returns {number} The north edge of the bounding box.
1219
+ */
1220
+ getNorth() {
1221
+ return this.northEast.lat;
1222
+ }
1223
+
1224
+ static equalsTo(bb1, bb2) {
1225
+ return Coordinates.equalsTo(bb1.northEast, bb2.northEast)
1226
+ && Coordinates.equalsTo(bb1.southWest, bb2.southWest);
1227
+ }
1228
+
1229
+ equalsTo(other) {
1230
+ return BoundingBox.equalsTo(this, other);
1231
+ }
1232
+
1233
+ /**
1234
+ * Create a BoundingBox from a WSEN array
1235
+ * @param {Number[4]} bounds a WSEN array
1236
+ * @returns {BoundingBox} the corresponding BoundingBox
1237
+ */
1238
+ static fromArray(bounds) {
1239
+ return new BoundingBox(
1240
+ new Coordinates(bounds[3], bounds[2]),
1241
+ new Coordinates(bounds[1], bounds[0])
1242
+ );
1243
+ }
1244
+
1245
+ /**
1246
+ * Returns the WSEN array
1247
+ * @returns {Number[4]} the WSEN array
1248
+ */
1249
+ toArray() {
1250
+ return [this.getWest(), this.getSouth(), this.getEast(), this.getNorth()];
1251
+ }
1252
+ }
1253
+
1254
+ class RelativePosition {
1255
+
1256
+ _x = 0;
1257
+ _y = 0;
1258
+ _z = 0;
1259
+ _time = null;
1260
+ _accuracy = null;
1261
+ _bearing = null;
1262
+
1263
+ constructor(x, y, z, time, accuracy, bearing) {
1264
+ this.x = x;
1265
+ this.y = y;
1266
+ this.z = z;
1267
+ this.time = time;
1268
+ this.accuracy = accuracy;
1269
+ this.bearing = bearing;
1270
+ }
1271
+
1272
+ get x() {
1273
+ return this._x;
1274
+ }
1275
+
1276
+ set x(x) {
1277
+ if (typeof x === 'number') {
1278
+ this._x = x;
1279
+ } else {
1280
+ throw new Error('x argument is not a number');
1281
+ }
1282
+ }
1283
+
1284
+ get y() {
1285
+ return this._y;
1286
+ }
1287
+
1288
+ set y(y) {
1289
+ if (typeof y === 'number') {
1290
+ this._y = y;
1291
+ } else {
1292
+ throw new Error('y argument is not a number');
1293
+ }
1294
+ }
1295
+
1296
+ get z() {
1297
+ return this._z;
1298
+ }
1299
+
1300
+ set z(z) {
1301
+ if (typeof z === 'number') {
1302
+ this._z = z;
1303
+ } else {
1304
+ throw new Error('z argument is not a number');
1305
+ }
1306
+ }
1307
+
1308
+ get time() {
1309
+ return this._time;
1310
+ }
1311
+
1312
+ set time(time) {
1313
+ if (typeof time === 'number') {
1314
+ this._time = time;
1315
+ } else {
1316
+ if (typeof time !== 'undefined' && time !== null) {
1317
+ throw new Error('time argument is not a number');
1318
+ }
1319
+ this._time = null;
1320
+ }
1321
+ }
1322
+
1323
+
1324
+ get accuracy() {
1325
+ return this._accuracy;
1326
+ }
1327
+
1328
+ set accuracy(accuracy) {
1329
+ if (typeof accuracy === 'number' && accuracy >= 0) {
1330
+ this._accuracy = accuracy;
1331
+ } else {
1332
+ if (typeof accuracy !== 'undefined' && accuracy !== null) {
1333
+ throw new Error('accuracy argument is not a positive number');
1334
+ }
1335
+ this._accuracy = null;
1336
+ }
1337
+ }
1338
+
1339
+
1340
+ get bearing() {
1341
+ return this._bearing;
1342
+ }
1343
+
1344
+ set bearing(bearing) {
1345
+ if (typeof bearing === 'number') {
1346
+ this._bearing = bearing % (2 * Math.PI);
1347
+ } else {
1348
+ if (typeof bearing !== 'undefined' && bearing !== null) {
1349
+ throw new Error('bearing argument is not a number');
1350
+ }
1351
+ this._bearing = null;
1352
+ }
1353
+ }
1354
+
1355
+ clone() {
1356
+ return new RelativePosition(this.x, this.y, this.z, this.time, this.accuracy, this.bearing);
1357
+ }
1358
+
1359
+
1360
+ /**
1361
+ * Compares two RelativePosition
1362
+ * @param {RelativePosition} pos1 position 1
1363
+ * @param {RelativePosition} pos2 position 2
1364
+ * @param {Number} eps x, y, z epsilon in meters (default: 1e-3 [= 1mm])
1365
+ */
1366
+ static equalsTo(pos1, pos2, eps = Constants.EPS_MM) {
1367
+
1368
+ // Handle null comparison
1369
+ if (pos1 === null && pos1 === pos2) {
1370
+ return true;
1371
+ }
1372
+
1373
+ if (!(pos1 instanceof RelativePosition) || !(pos2 instanceof RelativePosition)) {
1374
+ return false;
1375
+ }
1376
+
1377
+ return Math.abs(pos2.x - pos1.x) < eps
1378
+ && Math.abs(pos2.y - pos1.y) < eps
1379
+ && Math.abs(pos2.z - pos1.z) < eps
1380
+ && pos1.time === pos2.time
1381
+ && pos1.accuracy === pos2.accuracy
1382
+ && pos1.bearing === pos2.bearing;
1383
+ }
1384
+
1385
+ equalsTo(other) {
1386
+ return RelativePosition.equalsTo(this, other);
1387
+ }
1388
+
1389
+
1390
+ toJson() {
1391
+ const output = {
1392
+ x: this.x,
1393
+ y: this.y,
1394
+ z: this.z
1395
+ };
1396
+
1397
+ if (this.time !== null) {
1398
+ output.time = this.time;
1399
+ }
1400
+ if (this.accuracy !== null) {
1401
+ output.accuracy = this.accuracy;
1402
+ }
1403
+ if (this.bearing !== null) {
1404
+ output.bearing = this.bearing;
1405
+ }
1406
+ return output;
1407
+ }
1408
+
1409
+ static fromJson(json) {
1410
+ return new RelativePosition(json.x, json.y, json.z, json.time, json.accuracy, json.bearing);
1411
+ }
1412
+ }
1413
+
1414
+ /**
1415
+ * Position is defined in EUS (East-Up-South) frame with: x pointing to East, y pointing to Up, z pointing to South
1416
+ * This frame is close to ThreeJS / OpenGL frame.
1417
+ */
1418
+ class GeoRelativePosition extends RelativePosition { }
1419
+
1420
+ class Attitude {
1421
+
1422
+ _quaternion = [1, 0, 0, 0];
1423
+ _heading = null;
1424
+ _eulerAngles = null;
1425
+ _time = null;
1426
+ _accuracy = null;
1427
+
1428
+ /**
1429
+ *
1430
+ * @param {number[]} quaternion
1431
+ * @param {number} time
1432
+ * @param {number} accuracy
1433
+ */
1434
+ constructor(quaternion, time, accuracy) {
1435
+ this.quaternion = quaternion;
1436
+ this.time = time;
1437
+ this.accuracy = accuracy;
1438
+ }
1439
+
1440
+ static unitary() {
1441
+ return new Attitude([1, 0, 0, 0]);
1442
+ }
1443
+
1444
+ /**
1445
+ * @returns {number[]}
1446
+ */
1447
+ get quaternion() {
1448
+ return this._quaternion;
1449
+ }
1450
+
1451
+ /**
1452
+ * @param {number[]} quaternion
1453
+ */
1454
+ set quaternion(quaternion) {
1455
+ if (!Array.isArray(quaternion)
1456
+ || quaternion.length !== 4
1457
+ || Math.abs(1 - Quaternion.norm(quaternion)) > 1e-4) {
1458
+ throw new Error('quaternion is not a unit quaternion');
1459
+ }
1460
+ this._quaternion = quaternion;
1461
+ this._heading = null;
1462
+ this._eulerAngles = null;
1463
+ }
1464
+
1465
+ /**
1466
+ * @returns {number}
1467
+ */
1468
+ get time() {
1469
+ return this._time;
1470
+ }
1471
+
1472
+ /**
1473
+ * @param {number} time
1474
+ */
1475
+ set time(time) {
1476
+ if (typeof time === 'number') {
1477
+ this._time = time;
1478
+ } else {
1479
+ if (typeof time !== 'undefined' && time !== null) {
1480
+ throw new Error('time argument is not a number');
1481
+ }
1482
+ this._time = null;
1483
+ }
1484
+ }
1485
+
1486
+ /**
1487
+ * @returns {number}
1488
+ */
1489
+ get accuracy() {
1490
+ return this._accuracy;
1491
+ }
1492
+
1493
+ /**
1494
+ * @param {number} accuracy
1495
+ */
1496
+ set accuracy(accuracy) {
1497
+ if (typeof accuracy === 'number' && accuracy >= 0 && accuracy <= Math.PI) {
1498
+ this._accuracy = accuracy;
1499
+ } else {
1500
+ if (typeof accuracy !== 'undefined' && accuracy !== null) {
1501
+ throw new Error('accuracy argument (' + accuracy + ') is not in range [0; PI]');
1502
+ }
1503
+ this._accuracy = null;
1504
+ }
1505
+ }
1506
+
1507
+ /** @type {number[]} */
1508
+ get eulerAngles() {
1509
+ if (this._eulerAngles === null) {
1510
+ this._eulerAngles = Rotations.quaternionToEulerZXY(this.quaternion);
1511
+ }
1512
+ return this._eulerAngles;
1513
+ }
1514
+
1515
+ /** @type {number[]} */
1516
+ get eulerAnglesDegrees() {
1517
+ return this.eulerAngles.map(x => rad2deg(x));
1518
+ }
1519
+
1520
+ /** @type {number} */
1521
+ get heading() {
1522
+ if (this._heading === null) {
1523
+ let offset = 0;
1524
+ if (typeof (window) !== 'undefined' && window && window.orientation) {
1525
+ offset = deg2rad(window.orientation);
1526
+ }
1527
+ this._heading = Rotations.getHeadingFromQuaternion(this.quaternion) + offset;
1528
+ }
1529
+ return this._heading;
1530
+ }
1531
+
1532
+ /** @type {number} */
1533
+ get headingDegrees() {
1534
+ return rad2deg(this.heading);
1535
+ }
1536
+
1537
+ /**
1538
+ * Compares two Attitude
1539
+ * @param {Attitude} att1 attitude 1
1540
+ * @param {Attitude} att2 attitude 2
1541
+ */
1542
+ static equalsTo(att1, att2) {
1543
+
1544
+ // Handle null comparison
1545
+ if (att1 === null && att1 === att2) {
1546
+ // TODO not sure to return true here.
1547
+ return true;
1548
+ }
1549
+
1550
+ if (!(att1 instanceof Attitude) || !(att2 instanceof Attitude)) {
1551
+ return false;
1552
+ }
1553
+
1554
+ if (att1 === att2) {
1555
+ return true;
1556
+ }
1557
+
1558
+ return Quaternion.equalsTo(att1.quaternion, att2.quaternion);
1559
+ }
1560
+
1561
+ /**
1562
+ * @param {Attitude} other
1563
+ * @returns {boolean}
1564
+ */
1565
+ equalsTo(other) {
1566
+ return Attitude.equalsTo(this, other);
1567
+ }
1568
+
1569
+ /**
1570
+ * @returns {object}
1571
+ */
1572
+ toJson() {
1573
+ return this.quaternion;
1574
+ }
1575
+
1576
+ /**
1577
+ * @param {object} json
1578
+ * @returns {Attitude}
1579
+ */
1580
+ static fromJson(json) {
1581
+ return new Attitude(json);
1582
+ }
1583
+
1584
+ /**
1585
+ * @returns {Attitude}
1586
+ */
1587
+ clone() {
1588
+ return new Attitude(this.quaternion.slice(0), this.time, this.accuracy);
1589
+ }
1590
+ }
1591
+
1592
+ class AbsoluteHeading {
1593
+
1594
+ _heading = null;
1595
+ _time = null;
1596
+ _accuracy = null;
1597
+
1598
+ /**
1599
+ *
1600
+ * @param {Number} heading
1601
+ * @param {Number} time
1602
+ * @param {Number} accuracy
1603
+ */
1604
+ constructor(heading, time, accuracy) {
1605
+ this.heading = heading;
1606
+ this.time = time;
1607
+ this.accuracy = accuracy;
1608
+ }
1609
+
1610
+ /**
1611
+ * @returns {Number}
1612
+ */
1613
+ get heading() {
1614
+ return this._heading;
1615
+ }
1616
+
1617
+ /**
1618
+ * @param {Number} heading
1619
+ */
1620
+ set heading(heading) {
1621
+ if (typeof heading === 'number') {
1622
+ this._heading = heading;
1623
+ } else {
1624
+ throw new Error('heading argument is not a number');
1625
+ }
1626
+ }
1627
+
1628
+ /**
1629
+ * @returns {Number}
1630
+ */
1631
+ get time() {
1632
+ return this._time;
1633
+ }
1634
+
1635
+ /**
1636
+ * @param {Number} time
1637
+ */
1638
+ set time(time) {
1639
+ if (typeof time === 'number') {
1640
+ this._time = time;
1641
+ } else {
1642
+ if (typeof time !== 'undefined' && time !== null) {
1643
+ throw new Error('time argument is not a number');
1644
+ }
1645
+ this._time = null;
1646
+ }
1647
+ }
1648
+
1649
+ /**
1650
+ * @returns {Number}
1651
+ */
1652
+ get accuracy() {
1653
+ return this._accuracy;
1654
+ }
1655
+
1656
+ /**
1657
+ * @param {Number} accuracy
1658
+ */
1659
+ set accuracy(accuracy) {
1660
+ if (typeof accuracy === 'number' && accuracy >= 0 && accuracy <= Math.PI) {
1661
+ this._accuracy = accuracy;
1662
+ } else {
1663
+ if (typeof accuracy !== 'undefined' && accuracy !== null) {
1664
+ throw new Error('accuracy argument (' + accuracy + ') is not in range [0; PI]');
1665
+ }
1666
+ this._accuracy = null;
1667
+ }
1668
+ }
1669
+
1670
+ /**
1671
+ * @returns {Attitude}
1672
+ */
1673
+ toAttitude() {
1674
+ /**
1675
+ * Heading is given around z-axis in NED frame and our attitude in ENU frame, that is why
1676
+ * -1* is applied to heading.
1677
+ */
1678
+ return new Attitude(
1679
+ Quaternion.fromAxisAngle([0, 0, 1], -this.heading),
1680
+ this.time,
1681
+ this.accuracy
1682
+ );
1683
+ }
1684
+
1685
+
1686
+ /**
1687
+ * Compares two AbsoluteHeading
1688
+ * @param {AbsoluteHeading} heading1 heading 1
1689
+ * @param {AbsoluteHeading} heading2 heading 2
1690
+ */
1691
+ static equalsTo(heading1, heading2) {
1692
+
1693
+ // Handle null comparison
1694
+ if (heading1 === null && heading1 === heading2) {
1695
+ return true;
1696
+ }
1697
+
1698
+ if (!(heading1 instanceof AbsoluteHeading) || !(heading2 instanceof AbsoluteHeading)) {
1699
+ return false;
1700
+ }
1701
+
1702
+ return Math.abs(heading1.heading - heading2.heading) < 1e-8;
1703
+ }
1704
+
1705
+ equalsTo(other) {
1706
+ return AbsoluteHeading.equalsTo(this, other);
1707
+ }
1708
+
1709
+ toJson() {
1710
+ const output = { heading: this.heading };
1711
+ if (this.accuracy !== null) {
1712
+ output.accuracy = this.accuracy;
1713
+ }
1714
+ if (this.time !== null) {
1715
+ output.time = this.time;
1716
+ }
1717
+ return output;
1718
+ }
1719
+
1720
+ static fromJson({
1721
+ heading, time, accuracy
1722
+ }) {
1723
+ return new AbsoluteHeading(heading, time, accuracy);
1724
+ }
1725
+
1726
+ clone() {
1727
+ return new AbsoluteHeading(this.heading, this.time, this.accuracy);
1728
+ }
1729
+ }
1730
+
1731
+ /**
1732
+ * @template T
1733
+ */
1734
+ class GraphNode {
1735
+
1736
+ /** @type {Coordinates} */
1737
+ _coords;
1738
+
1739
+ /** @type {Edge<T>[]} */
1740
+ edges = [];
1741
+
1742
+ /** @type {?T} */
1743
+ builtFrom = null;
1744
+
1745
+ /**
1746
+ * @param {Coordinates} coords
1747
+ * @param {T} builtFrom
1748
+ */
1749
+ constructor(coords, builtFrom = null) {
1750
+ this.coords = coords;
1751
+ this.builtFrom = builtFrom;
1752
+ this.edges = [];
1753
+ }
1754
+
1755
+ /** @type {!Coordinates} */
1756
+ get coords() {
1757
+ return this._coords;
1758
+ }
1759
+
1760
+ /** @type {!Coordinates} */
1761
+ set coords(coords) {
1762
+ if (!(coords instanceof Coordinates)) {
1763
+ throw new Error('coords is not a Coordinates');
1764
+ }
1765
+ this._coords = coords;
1766
+ }
1767
+
1768
+ /**
1769
+ * @param {GraphNode} other
1770
+ * @returns {number}
1771
+ */
1772
+ distanceTo(other) {
1773
+ return this.coords.distanceTo(other.coords);
1774
+ }
1775
+
1776
+ /**
1777
+ * @param {GraphNode} other
1778
+ * @returns {number}
1779
+ */
1780
+ bearingTo(other) {
1781
+ return this.coords.bearingTo(other.coords);
1782
+ }
1783
+
1784
+ /**
1785
+ * @param {GraphNode} other
1786
+ * @returns {boolean}
1787
+ */
1788
+ equalsTo(other) {
1789
+ return this.coords.equalsTo(other.coords)
1790
+ && this.builtFrom === other.builtFrom;
1791
+ }
1792
+
1793
+ /**
1794
+ * @returns {GraphNode}
1795
+ */
1796
+ clone() {
1797
+ const node = new GraphNode(this.coords);
1798
+ node.edges = this.edges.slice(0);
1799
+ node.builtFrom = this.builtFrom;
1800
+ return node;
1801
+ }
1802
+
1803
+ /**
1804
+ * Does not include "edges" and "builtFrom" properties
1805
+ * @returns {object}
1806
+ */
1807
+ toJson() {
1808
+ return this.coords.toCompressedJson();
1809
+ }
1810
+
1811
+ /**
1812
+ * @template T
1813
+ * @returns {GraphNode<T>}
1814
+ */
1815
+ static fromJson(json) {
1816
+ return new GraphNode(Coordinates.fromCompressedJson(json));
1817
+ }
1818
+
1819
+ generateLevelFromEdges() {
1820
+ let tmpLevel;
1821
+ for (let i = 0; i < this.edges.length; i++) {
1822
+ const edge = this.edges[i];
1823
+ if (edge.level) {
1824
+ if (!tmpLevel) {
1825
+ tmpLevel = edge.level.clone();
1826
+ } else {
1827
+ tmpLevel = tmpLevel.intersect(edge.level);
1828
+ if (!tmpLevel) {
1829
+ Logger.error('Error: Something bad happend during parsing: We cannot retrieve node level from adjacent ways: ' + this.coords);
1830
+ return false;
1831
+ }
1832
+ }
1833
+ }
1834
+ }
1835
+ this.coords.level = tmpLevel;
1836
+ return true;
1837
+ }
1838
+
1839
+
1840
+ /**
1841
+ * We suppose generateLevelFromEdges() was called before
1842
+ */
1843
+ inferNodeLevelByRecursion() {
1844
+ const { level } = this.coords;
1845
+ if (!level || !level.isRange) {
1846
+ return true;
1847
+ }
1848
+
1849
+ /**
1850
+ * We can infer node level only if this one have one edge attached
1851
+ */
1852
+ if (this.edges.length > 1) {
1853
+ return true;
1854
+ }
1855
+
1856
+ /**
1857
+ * This method looks for single level nodes recursively from a multi-level node
1858
+ * The result of this method is an union of all single level nodes found.
1859
+ * @param {GraphNode} node node to explore
1860
+ * @param {GraphNode[]} visitedNodes list of visited nodes
1861
+ */
1862
+ const lookForLevel = (node, visitedNodes) => {
1863
+
1864
+ visitedNodes.push(node);
1865
+
1866
+ if (!node.coords.level) {
1867
+ return null;
1868
+ }
1869
+
1870
+ if (!node.coords.level.isRange) {
1871
+ return node.coords.level;
1872
+ }
1873
+
1874
+ let tmpLevel = null;
1875
+ for (let i = 0; i < node.edges.length; i++) {
1876
+ const edge = node.edges[i];
1877
+ const otherNode = edge.node1 === node ? edge.node2 : edge.node1;
1878
+ if (!visitedNodes.includes(otherNode)) {
1879
+ tmpLevel = Level.union(lookForLevel(otherNode, visitedNodes), tmpLevel);
1880
+ }
1881
+ }
1882
+ return tmpLevel;
1883
+ };
1884
+
1885
+ const othersLevels = lookForLevel(this, []);
1886
+
1887
+ if (othersLevels !== null) {
1888
+ if (!othersLevels.isRange) {
1889
+ this.coords.level = new Level(othersLevels.val === level.low ? level.up : level.low);
1890
+ return true;
1891
+ }
1892
+ Logger.warn('Level of: ' + this.coords.toString() + ' cannot be decided');
1893
+ return false;
1894
+ }
1895
+
1896
+ return true;
1897
+ }
1898
+
1899
+ /**
1900
+ * We suppose generateLevelFromEdges() was called before
1901
+ */
1902
+ inferNodeLevelByNeighboors() {
1903
+ const { level } = this.coords;
1904
+ if (!level || !level.isRange) {
1905
+ return true;
1906
+ }
1907
+
1908
+ let tmpLevel = null;
1909
+ for (let i = 0; i < this.edges.length; i++) {
1910
+ const edge = this.edges[i];
1911
+ const otherNode = edge.node1 === this ? edge.node2 : edge.node1;
1912
+ tmpLevel = Level.union(otherNode.coords.level, tmpLevel);
1913
+ }
1914
+
1915
+ if (tmpLevel === null || !tmpLevel.isRange) {
1916
+ this.coords.level = new Level(tmpLevel.val === level.low ? level.up : level.low);
1917
+ }
1918
+
1919
+ return true;
1920
+ }
1921
+
1922
+ /**
1923
+ * @param {GraphNode[]} nodes
1924
+ */
1925
+ static generateNodesLevels(nodes) {
1926
+ const success = nodes.reduce((acc, node) => acc && node.generateLevelFromEdges(), true);
1927
+ if (!success) {
1928
+ return false;
1929
+ }
1930
+
1931
+ // In some cases, node levels cannot be retrieve just using adjacent edges
1932
+ // (e.g stairs without network at one of its bounds)
1933
+ // To infer this node level, we use inferNodeLevelByRecursion()
1934
+ return nodes.reduce((acc, node) => acc
1935
+ && node.inferNodeLevelByNeighboors()
1936
+ && node.inferNodeLevelByRecursion()
1937
+ , true);
1938
+ }
1939
+ }
1940
+
1941
+ /**
1942
+ * @template T
1943
+ *
1944
+ * An Edge is a segment composed of two Node
1945
+ * An edge is mostly issued from an OsmWay, but this is not always the case.
1946
+ * For example, edges created by mapmatching.
1947
+ */
1948
+ class GraphEdge {
1949
+
1950
+ /** @type {GraphNode<T>} */
1951
+ _node1 = null;
1952
+
1953
+ /** @type {GraphNode<T>} */
1954
+ _node2 = null;
1955
+
1956
+ /** @type {?Level} */
1957
+ _level = null;
1958
+
1959
+ /** @type {?number} */
1960
+ _bearing;
1961
+
1962
+ /** @type {?number} */
1963
+ _length;
1964
+
1965
+ /** @type {boolean} */
1966
+ _computedSizeAndBearing = false;
1967
+
1968
+ /** @type {?T} */
1969
+ builtFrom = null;
1970
+
1971
+ /** @type {boolean} */
1972
+ isOneway = false;
1973
+
1974
+ /**
1975
+ * @param {!GraphNode} node1
1976
+ * @param {!GraphNode} node2
1977
+ * @param {?Level} level
1978
+ * @param {?T} builtFrom
1979
+ * @param {?string} name
1980
+ */
1981
+ constructor(node1, node2, level = null, builtFrom = null) {
1982
+ this.node1 = node1;
1983
+ this.node2 = node2;
1984
+ this.level = level;
1985
+ this.builtFrom = builtFrom;
1986
+ }
1987
+
1988
+ /** @type {!GraphNode<T>} */
1989
+ get node1() {
1990
+ return this._node1;
1991
+ }
1992
+
1993
+ /** @type {!GraphNode<T>} */
1994
+ set node1(node) {
1995
+
1996
+ if (!(node instanceof GraphNode)) {
1997
+ throw new TypeError('node1 is not a GraphNode');
1998
+ }
1999
+
2000
+ if (this._node1 !== null && this._node2 !== this._node1) {
2001
+ this._node1.edges = this._node1.edges.filter(edge => edge !== this);
2002
+ }
2003
+
2004
+ node.edges.push(this);
2005
+
2006
+ this._node1 = node;
2007
+ this._computedSizeAndBearing = false;
2008
+ }
2009
+
2010
+ /** @type {!GraphNode<T>} */
2011
+ get node2() {
2012
+ return this._node2;
2013
+ }
2014
+
2015
+ /** @type {!GraphNode<T>} */
2016
+ set node2(node) {
2017
+
2018
+ if (!(node instanceof GraphNode)) {
2019
+ throw new TypeError('node2 is not a GraphNode');
2020
+ }
2021
+
2022
+ if (this._node2 !== null && this._node2 !== this._node1) {
2023
+ this._node2.edges = this._node2.edges.filter(edge => edge !== this);
2024
+ }
2025
+
2026
+ node.edges.push(this);
2027
+
2028
+ this._node2 = node;
2029
+ this._computedSizeAndBearing = false;
2030
+ }
2031
+
2032
+ /** @type {?Level} */
2033
+ get level() {
2034
+ return this._level;
2035
+ }
2036
+
2037
+ /** @type {?Level} */
2038
+ set level(level) {
2039
+ if (level instanceof Level) {
2040
+ this._level = level;
2041
+ } else {
2042
+ if (typeof level !== 'undefined' && level !== null) {
2043
+ throw new Error('level argument is not a Level object');
2044
+ }
2045
+ this._level = null;
2046
+ }
2047
+ }
2048
+
2049
+ /**
2050
+ * Get edge bearing from node1 to node2
2051
+ * @type {number}
2052
+ */
2053
+ get bearing() {
2054
+ if (!this._computedSizeAndBearing) {
2055
+ this._computeSizeAndBearing();
2056
+ }
2057
+ return this._bearing;
2058
+ }
2059
+
2060
+ /**
2061
+ * get edge length
2062
+ * @type {number}
2063
+ */
2064
+ get length() {
2065
+ if (!this._computedSizeAndBearing) {
2066
+ this._computeSizeAndBearing();
2067
+ }
2068
+ return this._length;
2069
+ }
2070
+
2071
+ _computeSizeAndBearing() {
2072
+ this._length = this.node1.distanceTo(this.node2);
2073
+ this._bearing = this.node1.bearingTo(this.node2);
2074
+ this._computedSizeAndBearing = true;
2075
+ }
2076
+
2077
+ /**
2078
+ * @param {GraphEdge<T>} other
2079
+ * @returns {boolean}
2080
+ */
2081
+ equalsTo(other) {
2082
+
2083
+ if (this === other) {
2084
+ return true;
2085
+ }
2086
+
2087
+ if (!(other instanceof GraphEdge)) {
2088
+ return false;
2089
+ }
2090
+
2091
+ return other.node1.equalsTo(this.node1)
2092
+ && other.node2.equalsTo(this.node2)
2093
+ && Level.equalsTo(other.level, this.level)
2094
+ && other.isOneway === this.isOneway
2095
+ && other.builtFrom === this.builtFrom;
2096
+ }
2097
+
2098
+ /**
2099
+ * @returns {GraphEdge<T>}
2100
+ */
2101
+ clone() {
2102
+ const edge = new GraphEdge(this.node1, this.node2);
2103
+ edge.level = this.level;
2104
+ edge.isOneway = this.isOneway;
2105
+ edge.builtFrom = this.builtFrom;
2106
+ return edge;
2107
+ }
2108
+
2109
+ }
2110
+
2111
+ /**
2112
+ * @template T
2113
+ * @param {GraphEdge<T>[]} edges
2114
+ * @param {GraphNode<T>} node1
2115
+ * @param {GraphNode<T>} node2
2116
+ * @returns {?GraphEdge<T>}
2117
+ */
2118
+ function getEdgeByNodes(edges, node1, node2) {
2119
+ return edges.find(edge =>
2120
+ node1 === edge.node1 && node2 === edge.node2
2121
+ || node2 === edge.node1 && node1 === edge.node2
2122
+ );
2123
+ }
2124
+
2125
+ var GraphUtils = /*#__PURE__*/Object.freeze({
2126
+ __proto__: null,
2127
+ getEdgeByNodes: getEdgeByNodes
2128
+ });
2129
+
2130
+ /**
2131
+ * @template T
2132
+ *
2133
+ * A typical network with nodes (Node) and edges (Edge)
2134
+ */
2135
+ class Network {
2136
+
2137
+ /** @type {GraphNode<T>[]} */
2138
+ nodes;
2139
+
2140
+ /** @type {GraphEdge<T>[]} */
2141
+ edges;
2142
+
2143
+ /**
2144
+ * @template T
2145
+ * @param {GraphNode<T>[]} nodes
2146
+ * @param {GraphEdge<T>[]} edges
2147
+ */
2148
+ constructor(nodes, edges) {
2149
+ this.nodes = Array.isArray(nodes) ? nodes : [];
2150
+ this.edges = Array.isArray(edges) ? edges : [];
2151
+ }
2152
+
2153
+ /**
2154
+ * @template T
2155
+ * @param {Coordinates} coords
2156
+ * @returns {?GraphNode<T>}
2157
+ */
2158
+ getNodeByCoords(coords) {
2159
+ return this.nodes.find(node => node.coords.equalsTo(coords));
2160
+ }
2161
+
2162
+ /**
2163
+ * @template T
2164
+ * @param {GraphNode<T>} node1
2165
+ * @param {GraphNode<T>} node2
2166
+ * @returns {?GraphEdge<T>}
2167
+ */
2168
+ getEdgeByNodes(node1, node2) {
2169
+ return getEdgeByNodes(this.edges, node1, node2);
2170
+ }
2171
+
2172
+
2173
+ /**
2174
+ * @param {?number} extendedMeasure
2175
+ * @returns {BoundingBox}
2176
+ */
2177
+ getBoundingBox(extendedMeasure) {
2178
+ const boundingBox = this.nodes.reduce(
2179
+ (acc, node) => acc.extend(node.coords), new BoundingBox()
2180
+ );
2181
+ if (extendedMeasure) {
2182
+ boundingBox.extendsWithMeasure(extendedMeasure);
2183
+ }
2184
+ return boundingBox;
2185
+ }
2186
+
2187
+ /**
2188
+ * @param {function} _nodeToStringFn
2189
+ * @param {function} _edgeToStringFn
2190
+ * @returns {string}
2191
+ */
2192
+ toDetailedString(_nodeToStringFn, _edgeToStringFn) {
2193
+
2194
+ let nodeToStringFn = _nodeToStringFn;
2195
+ if (!nodeToStringFn) {
2196
+ nodeToStringFn = node => `${node.builtFrom}`;
2197
+ }
2198
+
2199
+ let edgeToStringFn = _edgeToStringFn;
2200
+ if (!_edgeToStringFn) {
2201
+ edgeToStringFn = edge => `${edge.builtFrom}`;
2202
+ }
2203
+
2204
+ let output
2205
+ = '--- Network ---\n'
2206
+ + `Nodes: ${this.nodes.length}\n`
2207
+ + `Edges: ${this.edges.length}\n`
2208
+ + '---\n'
2209
+ + 'Nodes\n';
2210
+ this.nodes.forEach(node => {
2211
+ output += `${nodeToStringFn(node)} [edges: ${node.edges.length}]\n`;
2212
+ });
2213
+ output += '---\n'
2214
+ + 'Edges\n';
2215
+ this.edges.forEach(edge => {
2216
+ output += `${edgeToStringFn(edge)} `;
2217
+ output += `[${nodeToStringFn(edge.node1)} -- ${nodeToStringFn(edge.node2)}]\n`;
2218
+ });
2219
+ output += '---';
2220
+ return output;
2221
+ }
2222
+
2223
+ /**
2224
+ * @returns {object}
2225
+ */
2226
+ toCompressedJson() {
2227
+ return {
2228
+ nodes: this.nodes.map(node => node.toJson()),
2229
+ edges: this.edges.map(edge => {
2230
+ const output = [
2231
+ this.nodes.indexOf(edge.node1),
2232
+ this.nodes.indexOf(edge.node2)
2233
+ ];
2234
+ if (edge.level !== null) {
2235
+ output.push(edge.level.toString());
2236
+ }
2237
+ if (edge.isOneway) {
2238
+ if (edge.level === null) {
2239
+ output.push(null);
2240
+ }
2241
+ output.push(true);
2242
+ }
2243
+ return output;
2244
+ })
2245
+ };
2246
+ }
2247
+
2248
+ /**
2249
+ * @param {object} json
2250
+ * @returns {Network}
2251
+ */
2252
+ static fromCompressedJson(json) {
2253
+
2254
+ const network = new Network();
2255
+
2256
+ network.nodes = json.nodes.map(GraphNode.fromJson);
2257
+
2258
+ network.edges = json.edges.map(jsonEdge => {
2259
+ const edge = new GraphEdge(
2260
+ network.nodes[jsonEdge[0]],
2261
+ network.nodes[jsonEdge[1]]
2262
+ );
2263
+ if (jsonEdge.length > 2 && jsonEdge[2] !== null) {
2264
+ edge.level = Level.fromString(jsonEdge[2]);
2265
+ }
2266
+ if (jsonEdge.length > 3 && jsonEdge[3]) {
2267
+ edge.isOneway = true;
2268
+ }
2269
+ return edge;
2270
+ });
2271
+
2272
+ return network;
2273
+ }
2274
+
2275
+
2276
+ /**
2277
+ * Convert Array of Coordinates array to a network
2278
+ * @param {Coordinates[][]} segments
2279
+ * @returns {Network}
2280
+ */
2281
+ static fromCoordinates(segments) {
2282
+
2283
+ const network = new Network();
2284
+
2285
+ const getOrCreateNode = coords =>
2286
+ network.nodes.find(_coords => _coords.equalsTo(coords)) || new GraphNode(coords);
2287
+
2288
+ const createEdgeFromCoords = (coords1, coords2) =>
2289
+ new GraphEdge(coords1, coords2, Level.union(coords1.level, coords2.level));
2290
+
2291
+ for (const segment of segments) {
2292
+
2293
+ let previousNode = null;
2294
+ for (const coords of segment) {
2295
+ const currentNode = getOrCreateNode(coords);
2296
+
2297
+ if (previousNode) {
2298
+ const edge = createEdgeFromCoords(currentNode, previousNode);
2299
+ network.edges.push(edge);
2300
+ }
2301
+
2302
+ network.nodes.push(currentNode);
2303
+ previousNode = currentNode;
2304
+ }
2305
+ }
2306
+
2307
+ return network;
2308
+ }
2309
+
2310
+ /**
2311
+ * Create edges From MultiLevel Itinerary for a given level
2312
+ * @param {Level} targetLevel level for selection.
2313
+ * @param {Boolean} useMultiLevelEdges use segments which intersect both levels (stairs, elevators...)
2314
+ * @returns {GraphEdge[]} Ordered edges
2315
+ */
2316
+ getEdgesAtLevel(targetLevel, useMultiLevelEdges = true) {
2317
+ return this.edges.filter(
2318
+ ({ level }) => useMultiLevelEdges
2319
+ ? Level.intersect(targetLevel, level) !== null
2320
+ : Level.contains(targetLevel, level)
2321
+ );
2322
+ }
2323
+
2324
+ }
2325
+
2326
+ class GraphProjection {
2327
+
2328
+ /** @type {Coordinates} */
2329
+ origin;
2330
+
2331
+ /** @type {number} */
2332
+ distanceFromNearestElement;
2333
+
2334
+ /** @type {Coordinates} */
2335
+ projection;
2336
+
2337
+ /** @type {Node|Edge} */
2338
+ nearestElement;
2339
+
2340
+ }
2341
+
2342
+ /* eslint-disable complexity */
2343
+
2344
+ class MapMatching {
2345
+
2346
+ /** @type {Network} */
2347
+ _network = null;
2348
+
2349
+ /** @type {number} */
2350
+ _maxDistance = Number.MAX_VALUE;
2351
+
2352
+ /** @type {number} */
2353
+ _maxAngleBearing = Math.PI;
2354
+
2355
+ /**
2356
+ * Constructor of Map-matching
2357
+ * @param {?Network} network
2358
+ */
2359
+ constructor(network) {
2360
+ this.network = network;
2361
+ }
2362
+
2363
+ /** @type {!number} */
2364
+ set maxAngleBearing(maxAngleBearing) {
2365
+ this._maxAngleBearing = maxAngleBearing;
2366
+ }
2367
+
2368
+ /** @type {!number} */
2369
+ set maxDistance(maxDistance) {
2370
+ this._maxDistance = maxDistance;
2371
+ }
2372
+
2373
+ /** @type {!number} */
2374
+ get maxAngleBearing() {
2375
+ return this._maxAngleBearing;
2376
+ }
2377
+
2378
+ /** @type {!number} */
2379
+ get maxDistance() {
2380
+ return this._maxDistance;
2381
+ }
2382
+
2383
+ /** @type {?Network} */
2384
+ get network() {
2385
+ return this._network;
2386
+ }
2387
+
2388
+ /** @type {?Network} */
2389
+ set network(network) {
2390
+ if (network instanceof Network) {
2391
+ this._network = network;
2392
+ } else {
2393
+ if (typeof network !== 'undefined' && network !== null) {
2394
+ throw new Error('network argument is not a Network object');
2395
+ }
2396
+ this._network = null;
2397
+ }
2398
+ }
2399
+
2400
+ /**
2401
+ * Check if the specified edge and its nodes can be used for projection
2402
+ * @returns {boolean} an array of two elements.
2403
+ * First is true if projection will be used on the specified edge, false otherwise.
2404
+ * Second is true if projection will be used on the nodes of the specified edge, false otherwise.
2405
+ */
2406
+ _shouldProjectOnEdgeAndNodes(edge, location, useBearing, useMultiLevelSegments, acceptEdgeFn) {
2407
+
2408
+ if (!acceptEdgeFn(edge)) {
2409
+ // if edge selection is not verified
2410
+ return [false, false, false];
2411
+ }
2412
+
2413
+ let checkNode1 = true;
2414
+ let checkNode2 = true;
2415
+ let checkEdge = true;
2416
+
2417
+ if (
2418
+ // Verify if edge level only if one of both is defined
2419
+ (location.level || edge.level)
2420
+ && (
2421
+ // if edge level intersect location level
2422
+ !Level.intersect(location.level, edge.level)
2423
+ // ignore MultiLevelSegments if option used
2424
+ || (!useMultiLevelSegments && edge.level && edge.level.isRange)
2425
+ )) {
2426
+ checkEdge = false;
2427
+ }
2428
+
2429
+ if (!Level.equalsTo(location.level, edge.node1.coords.level)) {
2430
+ checkNode1 = false;
2431
+ }
2432
+
2433
+ if (!Level.equalsTo(location.level, edge.node2.coords.level)) {
2434
+ checkNode2 = false;
2435
+ }
2436
+
2437
+ if (useBearing) {
2438
+ // if mapmatching bearing is enabled do not use nodes matching
2439
+ if (checkEdge) {
2440
+ // condition for optimisation
2441
+ const diffAngle = diffAngleLines(edge.bearing, location.bearing);
2442
+ if (diffAngle > this._maxAngleBearing) {
2443
+ // Do not try to project if angle is greater than the threshold
2444
+ checkEdge = false;
2445
+ }
2446
+ }
2447
+ checkNode1 = false;
2448
+ checkNode2 = false;
2449
+ }
2450
+
2451
+ return [checkEdge, checkNode1, checkNode2];
2452
+ }
2453
+
2454
+
2455
+ /**
2456
+ * @param {Coordinates} fromCoordinates
2457
+ * @param {Coordinates} toCoordinates
2458
+ */
2459
+ static _assignLatLngLevel(fromCoordinates, toCoordinates) {
2460
+ toCoordinates.lat = fromCoordinates.lat;
2461
+ toCoordinates.lng = fromCoordinates.lng;
2462
+ toCoordinates.level = fromCoordinates.level;
2463
+ }
2464
+
2465
+ /**
2466
+ * @param {Edge} _edge
2467
+ * @param {Coordinates} _projection
2468
+ */
2469
+ static _updateProjectionLevelFromEdge = (_edge, _projection) => {
2470
+ if (_edge.level) {
2471
+ _projection.level = _edge.level.clone();
2472
+ }
2473
+ };
2474
+
2475
+ /**
2476
+ * @param {!Coordinates} location
2477
+ * @param {boolean} useDistance
2478
+ * @param {boolean} useBearing
2479
+ * @param {boolean} useMultiLevelSegments
2480
+ * @param {function} acceptEdgeFn
2481
+ * @returns {GraphProjection}
2482
+ */
2483
+ getProjection(location, useDistance = false, useBearing = false,
2484
+ useMultiLevelSegments = true, acceptEdgeFn = () => true) {
2485
+
2486
+ if (this.network === null) {
2487
+ throw new Error('Network has not been set yet');
2488
+ }
2489
+
2490
+ if (!(location instanceof Coordinates)) {
2491
+ throw new TypeError('location is not an instance of Coordinates');
2492
+ }
2493
+
2494
+ if (useBearing && (!location.bearing || !this._maxAngleBearing)) {
2495
+ return null;
2496
+ }
2497
+
2498
+ const projection = new GraphProjection();
2499
+ projection.origin = location;
2500
+ projection.distanceFromNearestElement = Number.MAX_VALUE;
2501
+ projection.projection = location.clone();
2502
+
2503
+ const isProjectionBetter = (distanceOfNewProjection) => {
2504
+ return distanceOfNewProjection < projection.distanceFromNearestElement
2505
+ && (!useDistance || distanceOfNewProjection <= this._maxDistance);
2506
+ };
2507
+
2508
+ for (let i = 0; i < this.network.edges.length; i++) {
2509
+ const edge = this.network.edges[i];
2510
+
2511
+ const [checkEdge, checkNode1, checkNode2] = this._shouldProjectOnEdgeAndNodes(
2512
+ edge, location, useBearing, useMultiLevelSegments, acceptEdgeFn);
2513
+
2514
+ if (checkNode1) {
2515
+
2516
+ const distNode1 = location.distanceTo(edge.node1.coords);
2517
+ if (isProjectionBetter(distNode1) || distNode1 <= Constants.EPS_MM) {
2518
+ projection.distanceFromNearestElement = distNode1;
2519
+ projection.nearestElement = edge.node1;
2520
+ MapMatching._assignLatLngLevel(edge.node1.coords, projection.projection);
2521
+ MapMatching._updateProjectionLevelFromEdge(edge, projection.projection);
2522
+
2523
+ if (distNode1 <= Constants.EPS_MM
2524
+ && location.level === edge.node1.coords.level) {
2525
+ break;
2526
+ }
2527
+ }
2528
+ }
2529
+
2530
+ if (checkNode2) {
2531
+
2532
+ const distNode2 = location.distanceTo(edge.node2.coords);
2533
+ if (isProjectionBetter(distNode2) || distNode2 <= Constants.EPS_MM) {
2534
+
2535
+ projection.distanceFromNearestElement = distNode2;
2536
+ projection.nearestElement = edge.node2;
2537
+ MapMatching._assignLatLngLevel(edge.node2.coords, projection.projection);
2538
+ MapMatching._updateProjectionLevelFromEdge(edge, projection.projection);
2539
+
2540
+ if (distNode2 <= Constants.EPS_MM
2541
+ && location.level === edge.node2.coords.level) {
2542
+ break;
2543
+ }
2544
+ }
2545
+ }
2546
+
2547
+ if (checkEdge) {
2548
+ const segmentProjection = location.getSegmentProjection(edge.node1.coords, edge.node2.coords);
2549
+ if (segmentProjection) {
2550
+ const distEdge = location.distanceTo(segmentProjection);
2551
+ if (isProjectionBetter(distEdge)) {
2552
+ projection.distanceFromNearestElement = distEdge;
2553
+ projection.nearestElement = edge;
2554
+ MapMatching._assignLatLngLevel(segmentProjection, projection.projection);
2555
+ MapMatching._updateProjectionLevelFromEdge(edge, projection.projection);
2556
+ }
2557
+ }
2558
+ }
2559
+ }
2560
+
2561
+
2562
+ if (!projection.nearestElement) {
2563
+ return null;
2564
+ }
2565
+
2566
+ if (projection.projection instanceof UserPosition) {
2567
+ projection.projection.accuracy += projection.distanceFromNearestElement;
2568
+ }
2569
+ return projection;
2570
+ }
2571
+
2572
+ }
2573
+
2574
+ /**
2575
+ * @template T
2576
+ */
2577
+ class GraphItinerary {
2578
+
2579
+ /** @type {Coordinates} */
2580
+ start;
2581
+
2582
+ /** @type {Coordinates} */
2583
+ end;
2584
+
2585
+ /** @type {GraphNode<T>[]} */
2586
+ nodes;
2587
+
2588
+ /** @type {GraphEdge<T>[]} */
2589
+ edges;
2590
+
2591
+ /** @type {number[]} */
2592
+ edgesWeights;
2593
+
2594
+
2595
+ /**
2596
+ * @template T
2597
+ * @param {GraphNode<T>[]} networkNodes
2598
+ * @param {number[]} edgesWeights
2599
+ * @returns {GraphItinerary<T>}
2600
+ */
2601
+ static fromNetworkNodes(networkNodes, edgesWeights) {
2602
+ const itinerary = new GraphItinerary();
2603
+ itinerary.edgesWeights = edgesWeights;
2604
+
2605
+ itinerary.nodes = networkNodes.map(node => {
2606
+ const newNode = node.clone();
2607
+ // Remove node edges, they will be added later.
2608
+ newNode.edges = [];
2609
+ return newNode;
2610
+ });
2611
+
2612
+ itinerary.edges = [];
2613
+ networkNodes.forEach((node, idx, arr) => {
2614
+ if (idx === 0) {
2615
+ return;
2616
+ }
2617
+
2618
+ // Retrieve network edge
2619
+ const prevNode = arr[idx - 1];
2620
+ const edge = getEdgeByNodes(prevNode.edges, prevNode, node);
2621
+
2622
+ // Create itinerary edge
2623
+ const newEdge = new GraphEdge(
2624
+ itinerary.nodes[idx - 1],
2625
+ itinerary.nodes[idx],
2626
+ edge.level,
2627
+ edge.builtFrom
2628
+ );
2629
+ newEdge.isOneway = edge.isOneway;
2630
+ itinerary.edges.push(newEdge);
2631
+ });
2632
+
2633
+ return itinerary;
2634
+
2635
+ }
2636
+ }
2637
+
2638
+ class NoRouteFoundError extends Error {
2639
+
2640
+ constructor(start, end, details) {
2641
+ super();
2642
+ this.start = start;
2643
+ this.end = end;
2644
+ this.details = details;
2645
+ }
2646
+
2647
+ get startStr() {
2648
+ if (this.start instanceof GraphNode) {
2649
+ return `Node ${this.start.coords.toString()}`;
2650
+ }
2651
+
2652
+ // if (this.start instanceof Coordinates) {
2653
+ return this.start.toString();
2654
+ }
2655
+
2656
+ get endStr() {
2657
+ if (this.end instanceof GraphNode) {
2658
+ return `Node ${this.end.coords.toString()}`;
2659
+ }
2660
+
2661
+ // if (this.end instanceof Coordinates) {
2662
+ return this.end.toString();
2663
+ }
2664
+
2665
+ get message() {
2666
+ let message = `No route found from ${this.startStr} to ${this.endStr}.`;
2667
+ if (this.details) {
2668
+ message += ` Details: ${this.details}`;
2669
+ }
2670
+ return message;
2671
+ }
2672
+ }
2673
+
2674
+ /**
2675
+ * @template T
2676
+ */
2677
+ class GraphRouterOptions {
2678
+
2679
+ /** @type {number} in meters */
2680
+ projectionMaxDistance = 50;
2681
+
2682
+ /** @type {function(GraphEdge<T>):boolean} */
2683
+ weightEdgeFn = edge => edge.length;
2684
+
2685
+ /** @type {function(GraphEdge<T>):boolean} */
2686
+ acceptEdgeFn = () => true;
2687
+
2688
+ }
2689
+
2690
+ /**
2691
+ * @template T
2692
+ * @abstract
2693
+ */
2694
+ class GraphRouter {
2695
+
2696
+ /** @type {!Network<T>} */
2697
+ _network;
2698
+
2699
+ /**
2700
+ * @param {!Network<T>} network
2701
+ */
2702
+ constructor(network) {
2703
+ this._network = network;
2704
+ this._mapMatching = new MapMatching(network);
2705
+ }
2706
+
2707
+ /**
2708
+ * @template T
2709
+ * @param {!GraphNode<T>|!Coordinates} start
2710
+ * @param {!GraphNode<T>|!Coordinates} end
2711
+ * @param {GraphRouterOptions} _options
2712
+ * @returns {GraphItinerary<T>}
2713
+ */
2714
+ getShortestPath(start, end, options = new GraphRouterOptions()) {
2715
+
2716
+ if (!(start instanceof GraphNode) && !(start instanceof Coordinates)) {
2717
+ throw new Error('Unknown start type');
2718
+ }
2719
+
2720
+ if (!(end instanceof GraphNode) && !(end instanceof Coordinates)) {
2721
+ throw new Error('Unknown end type');
2722
+ }
2723
+
2724
+ const { acceptEdgeFn, weightEdgeFn, projectionMaxDistance } = options;
2725
+ this._mapMatching.maxDistance = projectionMaxDistance;
2726
+
2727
+ const createdNodes = [];
2728
+
2729
+ const retrieveOrCreateNearestNode = point => {
2730
+ if (point instanceof GraphNode) {
2731
+ return point;
2732
+ }
2733
+
2734
+ const closeNode = this._network.getNodeByCoords(point);
2735
+ if (closeNode) {
2736
+ return closeNode;
2737
+ }
2738
+
2739
+ const proj = this._mapMatching.getProjection(point, true, false, false, acceptEdgeFn);
2740
+ if (!proj) {
2741
+ throw new NoRouteFoundError(start, end,
2742
+ `Point ${point.toString()} is too far from the network `
2743
+ + `> ${this._mapMatching.maxDistance.toFixed(0)} meters`
2744
+ );
2745
+ }
2746
+ if (proj.nearestElement instanceof GraphNode) {
2747
+ return proj.nearestElement;
2748
+ }
2749
+ // if (proj.nearestElement instanceof Edge)
2750
+ const nodeCreated = this.createNodeInsideEdge(
2751
+ proj.nearestElement,
2752
+ proj.projection
2753
+ );
2754
+ createdNodes.push(nodeCreated);
2755
+ return nodeCreated;
2756
+ };
2757
+
2758
+ const removeCreatedNodes = () => {
2759
+ while (createdNodes.length) {
2760
+ this.removeNodeFromPreviouslyCreatedEdge(createdNodes.pop());
2761
+ }
2762
+ };
2763
+
2764
+ const startNode = retrieveOrCreateNearestNode(start);
2765
+ const endNode = retrieveOrCreateNearestNode(end);
2766
+
2767
+ const graphItinerary = this.getShortestPathBetweenGraphNodes(
2768
+ startNode,
2769
+ endNode,
2770
+ acceptEdgeFn,
2771
+ weightEdgeFn
2772
+ );
2773
+ graphItinerary.start = start instanceof GraphNode ? start.coords : start;
2774
+ graphItinerary.end = end instanceof GraphNode ? end.coords : end;
2775
+
2776
+ removeCreatedNodes();
2777
+
2778
+ if (!graphItinerary.nodes.length) {
2779
+ throw new NoRouteFoundError(start, end);
2780
+ }
2781
+
2782
+ return graphItinerary;
2783
+
2784
+ }
2785
+
2786
+ /**
2787
+ * @param {GraphEdge<T>} edge
2788
+ * @param {Coordinates} point
2789
+ */
2790
+ createNodeInsideEdge(edge, point) {
2791
+ const a = edge.node1;
2792
+ const b = edge.node2;
2793
+
2794
+ const m = new GraphNode(point);
2795
+ m.coords.level = edge.level;
2796
+ m.builtFrom = edge.builtFrom;
2797
+
2798
+ const u = edge.clone();
2799
+ u.node1 = a;
2800
+ u.node2 = m;
2801
+
2802
+ const v = edge.clone();
2803
+ v.node1 = m;
2804
+ v.node2 = b;
2805
+
2806
+ a.edges = a.edges.filter(_edge => _edge !== edge);
2807
+ b.edges = b.edges.filter(_edge => _edge !== edge);
2808
+
2809
+ this._network.nodes.push(m);
2810
+ this._network.edges.push(u, v);
2811
+
2812
+ this._network.edges = this._network.edges.filter(
2813
+ _edge => _edge !== edge
2814
+ );
2815
+
2816
+ return m;
2817
+ }
2818
+
2819
+ /**
2820
+ * @param {GraphNode<T>} _node
2821
+ */
2822
+ removeNodeFromPreviouslyCreatedEdge(_node) {
2823
+ const u = _node.edges[0];
2824
+ const v = _node.edges[1];
2825
+
2826
+ u.node1.edges = u.node1.edges.filter(edge => edge !== u);
2827
+ v.node1.edges = v.node1.edges.filter(edge => edge !== v);
2828
+
2829
+ const oldEdge = u.clone();
2830
+ oldEdge.node1 = u.node1;
2831
+ oldEdge.node2 = v.node2;
2832
+ this._network.edges.push(oldEdge);
2833
+
2834
+ this._network.nodes = this._network.nodes.filter(node => node !== _node);
2835
+ this._network.edges = this._network.edges.filter(
2836
+ edge => edge !== u && edge !== v
2837
+ );
2838
+ }
2839
+
2840
+ getShortestPathBetweenGraphNodes(start, end, acceptEdgeFn, weightFn) {
2841
+ const distanceMap = {},
2842
+ vertexList = {},
2843
+ vertexNodes = {},
2844
+ parentVertices = {},
2845
+ path = [];
2846
+ let vertexId = 1;
2847
+
2848
+ // Initially, we assume each vertex is unreachable
2849
+ this._network.nodes.forEach(vertex => {
2850
+
2851
+ // Generate Unique Router Id
2852
+ vertex.uniqueRouterId = vertexId;
2853
+ vertexNodes[vertexId] = vertex;
2854
+
2855
+ distanceMap[vertexId] = Infinity;
2856
+ vertexList[vertexId] = true;
2857
+
2858
+ vertexId++;
2859
+ });
2860
+
2861
+ // The cost from the starting vertex to the starting vertex is 0
2862
+ distanceMap[start.uniqueRouterId] = 0;
2863
+
2864
+ // check each vertex
2865
+ while (Object.keys(vertexList).length > 0) {
2866
+ const current = Number(
2867
+ Object.keys(vertexList).reduce((_checking, vertex) => {
2868
+ return distanceMap[_checking] > distanceMap[vertex]
2869
+ ? vertex
2870
+ : _checking;
2871
+ }, Object.keys(vertexList)[0])
2872
+ );
2873
+
2874
+ // all the vertices accessible from current vertex
2875
+ this._network.edges
2876
+ .filter(edge => {
2877
+
2878
+ if (!acceptEdgeFn(edge)) {
2879
+ return false;
2880
+ }
2881
+
2882
+ const from = edge.node1.uniqueRouterId,
2883
+ to = edge.node2.uniqueRouterId;
2884
+ // are these vertices joined?
2885
+ return from === current || to === current;
2886
+ })
2887
+ // for each vertex we can reach
2888
+ .forEach(edge => {
2889
+ let to, from, reversed = false;
2890
+ // determine the direction of travel
2891
+ if (edge.node1.uniqueRouterId === current) {
2892
+ to = edge.node2.uniqueRouterId;
2893
+ from = edge.node1.uniqueRouterId;
2894
+ } else {
2895
+ to = edge.node1.uniqueRouterId;
2896
+ from = edge.node2.uniqueRouterId;
2897
+ reversed = true;
2898
+ }
2899
+
2900
+ if (edge.isOneway && reversed) {
2901
+ return;
2902
+ }
2903
+
2904
+ // distance is how far we travelled to reach the
2905
+ // current vertex, plus cost of travel the next(to)
2906
+ const distance = distanceMap[current] + weightFn(edge);
2907
+
2908
+ // if we have found a cheaper path
2909
+ // update the hash of costs
2910
+ // and record which vertex we came from
2911
+ if (distanceMap[to] > distance) {
2912
+ distanceMap[to] = distance;
2913
+ parentVertices[to] = from;
2914
+ }
2915
+ });
2916
+
2917
+ // remove vertex so we don't revisit it
2918
+ delete vertexList[current];
2919
+ }
2920
+
2921
+ const edgesWeights = [];
2922
+
2923
+ // now we have the most efficient paths for all vertices
2924
+ // build the path for the user specified vertex(end)
2925
+ let endId = end.uniqueRouterId;
2926
+ while (parentVertices[endId]) {
2927
+ path.unshift(vertexNodes[endId]);
2928
+ edgesWeights.unshift(distanceMap[endId] - distanceMap[parentVertices[endId]]);
2929
+ endId = parentVertices[endId];
2930
+ }
2931
+ if (path.length !== 0) {
2932
+ path.unshift(start);
2933
+ }
2934
+
2935
+ // Remove Unique Router Id
2936
+ this._network.nodes.forEach(vertex => {
2937
+ delete vertex.uniqueRouterId;
2938
+ });
2939
+
2940
+ // This clone the itinerary and temporary nodes
2941
+ return GraphItinerary.fromNetworkNodes(path, edgesWeights);
2942
+ }
2943
+ }
2944
+
2945
+ export { AbsoluteHeading, Attitude, BoundingBox, Constants, Coordinates, GeoRelativePosition, GraphEdge, GraphItinerary, GraphNode, GraphProjection, GraphRouter, GraphRouterOptions, GraphUtils, Level, MapMatching, Network, NoRouteFoundError, RelativePosition, UserPosition, Utils };
2946
+ //# sourceMappingURL=wemap-geo.es.js.map