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