@wemap/providers 5.1.12 → 5.3.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,4526 @@
1
+ import { deg2rad, Vector3, Matrix, Quaternion, Matrix3, Matrix4, Vector, Rotations, std, diffAngleLines, diffAngle, rad2deg } from '@wemap/maths';
2
+ import { BrowserUtils, Browser, PromiseUtils, TimeUtils } from '@wemap/utils';
3
+ import Logger from '@wemap/logger';
4
+ import { Attitude, RelativePosition, AbsoluteHeading, Constants as Constants$1, MapMatching, UserPosition, GraphEdge, GeoRelativePosition as GeoRelativePosition$1, Network } from '@wemap/geo';
5
+ import geomagnetism from '@wemap/geomagnetism';
6
+ import { Itinerary } from '@wemap/osm';
7
+
8
+ /**
9
+ * Event data types handled by {@link ProviderEvent}
10
+ */
11
+ var EventType = {
12
+ Unknown: 'UNKNOWN',
13
+
14
+ MagneticField: 'MAGNETIC_FIELD',
15
+ AngularRate: 'ANGULAR_RATE',
16
+ Acceleration: 'ACCELERATION',
17
+
18
+ RelativeAttitude: 'RELATIVE_ATTITUDE',
19
+ AbsoluteAttitude: 'ABSOLUTE_ATTITUDE',
20
+ Attitude: 'ATTITUDE',
21
+
22
+ Inclination: 'INCLINATION',
23
+ AbsoluteHeading: 'ABSOLUTE_HEADING',
24
+ Turn: 'TURN',
25
+ HighRotation: 'HIGH_ROTATION',
26
+
27
+ RelativePosition: 'RELATIVE_POSITION',
28
+ GeoRelativePosition: 'GEO_RELATIVE_POSITION',
29
+ AbsolutePosition: 'ABSOLUTE_POSITION',
30
+
31
+ Step: 'STEP',
32
+ StraightLine: 'STRAIGHT_LINE',
33
+
34
+ Pressure: 'PRESSURE',
35
+ BluetoothSignals: 'BLUETOOTH_SIGNALS',
36
+ WifiSignals: 'WIFI_SIGNALS',
37
+ ScanId: 'SCAN_ID',
38
+ Barcode: 'BARCODE',
39
+ CameraProjectionMatrix: 'CAMERA_PROJECTION_MATRIX',
40
+ CameraNative: 'CAMERA_NATIVE',
41
+
42
+ Itinerary: 'ITINERARY',
43
+ Network: 'NETWORK'
44
+ };
45
+
46
+ /**
47
+ * A provider event is an event which can be triggered by device sensors
48
+ * or can be computed from raw providers.
49
+ *
50
+ * @template T
51
+ */
52
+ class ProviderEvent {
53
+
54
+ dataType = EventType.Unknown;
55
+ providersStack = [];
56
+
57
+ /** @type {T} */
58
+ data = null;
59
+
60
+ /**
61
+ * Create a Provider Event with the minimum information
62
+ * @param {EventType} dataType the type of event
63
+ * @param {Object} data the event data
64
+ */
65
+ constructor(dataType, data) {
66
+ this.dataType = dataType;
67
+ this.data = data;
68
+ }
69
+
70
+ /**
71
+ * @return {ProviderEvent<T>}
72
+ */
73
+ clone() {
74
+ const evt = new ProviderEvent(this.dataType, this.data);
75
+ evt.providersStack = this.providersStack.slice(0);
76
+ return evt;
77
+ }
78
+ }
79
+
80
+ class ProvidersLoggerOld {
81
+
82
+ _enabled = false;
83
+
84
+ currentId = 0;
85
+ objectsIdMap = new WeakMap();
86
+
87
+ pushEvents = {};
88
+ pushEventsRef = {};
89
+
90
+ interval;
91
+ initDate;
92
+
93
+
94
+ get enabled() {
95
+ return this._enabled;
96
+ }
97
+
98
+ set enabled(_newVal) {
99
+ this._enabled = _newVal;
100
+ }
101
+
102
+ initializeInterval() {
103
+
104
+ if (this.interval) {
105
+ return;
106
+ }
107
+
108
+ this.interval = setInterval(() => {
109
+
110
+ for (const [key, value] of Object.entries(this.pushEvents)) {
111
+ Logger.debug('Received ' + value + ' notifications from ' + this.pushEventsRef[key].pname + ' last second');
112
+ }
113
+
114
+ this.pushEvents = {};
115
+ this.pushEventsRef = {};
116
+ }, 1000);
117
+ }
118
+
119
+ getObjectId(object) {
120
+ if (!this.objectsIdMap.has(object)) {
121
+ this.objectsIdMap.set(object, ++this.currentId);
122
+ }
123
+ return this.objectsIdMap.get(object);
124
+ }
125
+
126
+ addEvent(object, method) {
127
+
128
+ if (!this.enabled) {
129
+ return;
130
+ }
131
+
132
+ if (!this.initDate) {
133
+ this.initDate = Date.now();
134
+ }
135
+
136
+ this.initializeInterval();
137
+
138
+ const objectId = this.getObjectId(object);
139
+ const objectClassName = object.pname;
140
+
141
+ Logger.debug(objectClassName + '[' + objectId + '].' + method);
142
+ }
143
+
144
+ incrementNotifications(object) {
145
+
146
+ if (!this.enabled) {
147
+ return;
148
+ }
149
+
150
+ const objectId = this.getObjectId(object);
151
+
152
+ let counter = this.pushEvents[objectId];
153
+ if (!counter) {
154
+ counter = 0;
155
+ this.pushEventsRef[objectId] = object;
156
+ }
157
+ this.pushEvents[objectId] = counter + 1;
158
+ }
159
+
160
+ }
161
+ var ProvidersLoggerOld$1 = new ProvidersLoggerOld();
162
+
163
+ class ContainsIgnoredProviderError extends Error {
164
+
165
+ static DEFAULT_MESSAGE = 'Contains ignored provider';
166
+
167
+ constructor(message) {
168
+ super(message || ContainsIgnoredProviderError.DEFAULT_MESSAGE);
169
+ }
170
+ }
171
+
172
+ const ProvidersOptions = {
173
+
174
+ /**
175
+ * Does provider will use map to
176
+ */
177
+ useMapMatching: true,
178
+
179
+ /**
180
+ * Providers listed here will not be used by the system
181
+ * List of {@link Provider#name}
182
+ */
183
+ ignoreProviders: [],
184
+
185
+ /**
186
+ * Define the list of EventType that are optionals
187
+ * List of {@link EventType}
188
+ */
189
+ optionalEvents: [],
190
+
191
+ stopOnError: true,
192
+
193
+ checkAvailabilityOnStart: true,
194
+
195
+ stepdetectionlocker: true,
196
+
197
+ smoother: true
198
+ };
199
+
200
+ var ProviderState = {
201
+ STARTING: 0,
202
+ STARTED: 1,
203
+ STOPPPED: 2
204
+ };
205
+
206
+ /**
207
+ * A provider is a meta class to define an entity which can be
208
+ * started / stopped and provides a data of {@link ProviderEvent#DataType}
209
+ */
210
+ class Provider {
211
+
212
+ /** @type {number} */
213
+ static _callbackUniqueId = 0;
214
+
215
+ /** @type {number} */
216
+ static _uniqueId = 1;
217
+
218
+
219
+ /** @type {number} */
220
+ id;
221
+
222
+ /** @type {number} */
223
+ state = ProviderState.STOPPPED;
224
+
225
+ /** @type {ProviderEvent[]} */
226
+ _lastEvents = null;
227
+
228
+ _eventsCallbacks = [];
229
+ _monitoringCallbacks = [];
230
+
231
+ /**
232
+ * Provider constructor
233
+ */
234
+ constructor() {
235
+
236
+ if (this.constructor === Provider) {
237
+ throw new Error('Can\'t instantiate Provider directly');
238
+ }
239
+
240
+ this.id = Provider._uniqueId++;
241
+
242
+ ProvidersLoggerOld$1.addEvent(this, 'constructor');
243
+ }
244
+
245
+ /**
246
+ * Get the provider name
247
+ * @type {String} the provider name
248
+ */
249
+ static get pname() {
250
+ return 'Unknown';
251
+ }
252
+
253
+ /** @type {String} */
254
+ get pname() {
255
+ return this.constructor.pname;
256
+ }
257
+
258
+ /**
259
+ * Get the list of events type which can be returned by the provider
260
+ * @returns {EventType[]} the list of events type
261
+ * @public
262
+ * @abstract
263
+ */
264
+ get eventsType() {
265
+ return [];
266
+ }
267
+
268
+ /**
269
+ * @type {Promise} returns an availability promise
270
+ */
271
+ get availability() {
272
+ if (ProvidersOptions.ignoreProviders.includes(this.pname)) {
273
+ return Promise.reject(new ContainsIgnoredProviderError());
274
+ }
275
+
276
+ return this._availability;
277
+ }
278
+
279
+ /** @type {Promise} */
280
+ get _availability() {
281
+ return Promise.resolve();
282
+ }
283
+
284
+ /**
285
+ * Create an event from current provider
286
+ * @param {String} dataType type of ProviderEvent (from EventType)
287
+ * @param {Object} data data exported to ProviderEvent
288
+ * @returns {ProviderEvent}
289
+ * @protected
290
+ */
291
+ static createEvent(dataType, data, fromEvents = []) {
292
+ const event = new ProviderEvent(dataType, data);
293
+ const newStack = fromEvents.reduce((acc, _event) => acc.concat(_event.providersStack), []);
294
+ // Remove duplicates and keep lasts
295
+ event.providersStack = [...new Set(newStack.reverse())].reverse();
296
+ return event;
297
+ }
298
+
299
+ /**
300
+ * Create an event from current provider
301
+ * @param {EventType} dataType type of ProviderEvent
302
+ * @param {Object} data data exported to ProviderEvent
303
+ * @param {ProviderEvent[]} fromEvents events used for the creation of the new one
304
+ * @returns {ProviderEvent}
305
+ * @protected
306
+ */
307
+ createEvent(dataType, data, fromEvents) {
308
+ return this.constructor.createEvent(dataType, data, fromEvents);
309
+ }
310
+
311
+ get hasNativeInterface() {
312
+ return Boolean(this.nativeInterface);
313
+ }
314
+
315
+ get nativeInterface() {
316
+ return typeof window !== 'undefined' ? (window.__nativeProviders || null) : null;
317
+ }
318
+
319
+ get useCameraNatively() {
320
+ return false;
321
+ }
322
+
323
+ /**
324
+ *
325
+ * @param {Function} onEvents
326
+ * @param {Function} onError
327
+ * @param {Boolean} startIfNecessary
328
+ * @returns {Number}
329
+ */
330
+ addEventListener(onEvents = () => {}, onError = () => {}, startIfNecessary = true) {
331
+ const id = ++Provider._callbackUniqueId;
332
+
333
+ /**
334
+ * Build callback
335
+ */
336
+ this._eventsCallbacks.push({
337
+ id,
338
+ onEvents,
339
+ onError,
340
+ optional: !startIfNecessary
341
+ });
342
+
343
+
344
+ // The caller just want to have callbacks without starting the provider,
345
+ // the routine can be stopped here
346
+ if (!startIfNecessary) {
347
+ return id;
348
+ }
349
+
350
+
351
+ // If the provider is already started do not go further
352
+ if (this.state !== ProviderState.STOPPPED) {
353
+ return id;
354
+ }
355
+ this.state = ProviderState.STARTING;
356
+
357
+ // Check availability on start if defined in options
358
+ let availabilityPromise = Promise.resolve();
359
+ if (ProvidersOptions.checkAvailabilityOnStart) {
360
+ availabilityPromise = this.availability;
361
+ }
362
+
363
+ availabilityPromise
364
+ .then(() => {
365
+
366
+ ProvidersLoggerOld$1.addEvent(this, 'start');
367
+ // Call the child start function
368
+ this.start();
369
+
370
+ this.state = ProviderState.STARTED;
371
+
372
+ this._monitoringCallbacks.forEach(({ onStarted }) => onStarted());
373
+ })
374
+ .catch(e => {
375
+ this.state = ProviderState.STOPPPED;
376
+ this.notifyError(e);
377
+ })
378
+ // notifyError can throw an error if onStop is not defined
379
+ .catch(() => {});
380
+
381
+ return id;
382
+ }
383
+
384
+ /**
385
+ *
386
+ * @param {Number} callbackUniqueId
387
+ */
388
+ removeEventListener(callbackUniqueId) {
389
+
390
+ // Search the caller object in callbacks list
391
+ const callback = this._eventsCallbacks.find(_callback => _callback.id === callbackUniqueId);
392
+ if (!callback) {
393
+ // The callback object is not found. Maybe it is already stopped.
394
+ return;
395
+ }
396
+
397
+ // Remove the current callback from the list of the callbacks
398
+ this._eventsCallbacks = this._eventsCallbacks.filter(_callback => _callback !== callback);
399
+
400
+ // If this callback was optional, do not go further to stop the provider
401
+ if (callback.optional) {
402
+ return;
403
+ }
404
+
405
+ // If there is callbacks that are not optionals for start, do not stop the provider
406
+ if (this._eventsCallbacks.find(_callback => !_callback.optional)) {
407
+ return;
408
+ }
409
+
410
+ // If the provider is already stopped, do not stop it again.
411
+ // This condition can be true if checkAvailabilityOnStart is true and returns an error
412
+ if (this.state === ProviderState.STOPPPED) {
413
+ return;
414
+ }
415
+
416
+ ProvidersLoggerOld$1.addEvent(this, 'stop');
417
+ // Call the child stop function
418
+ this.stop();
419
+
420
+ this.state = ProviderState.STOPPPED;
421
+
422
+ this._monitoringCallbacks.forEach(({ onStopped }) => onStopped());
423
+ }
424
+
425
+
426
+ /**
427
+ *
428
+ * @param {Function} onStarted
429
+ * @param {Function} onStopped
430
+ * @returns {Number}
431
+ */
432
+ addMonitoringListener(onStarted = () => {}, onStopped = () => {}) {
433
+ const id = ++this.constructor._callbackUniqueId;
434
+ this._monitoringCallbacks.push({
435
+ id,
436
+ onStarted,
437
+ onStopped
438
+ });
439
+ return id;
440
+ }
441
+
442
+ /**
443
+ *
444
+ * @param {Number} callbackUniqueId
445
+ */
446
+ removeMonitoringListener(callbackUniqueId) {
447
+ this._monitoringCallbacks = this._monitoringCallbacks.filter(
448
+ _callback => _callback.id !== callbackUniqueId
449
+ );
450
+ }
451
+
452
+ /**
453
+ * @abstract
454
+ */
455
+ start() {
456
+ throw new Error('Provider "' + this.pname + '" does not @override start()');
457
+ }
458
+
459
+ /**
460
+ * @abstract
461
+ */
462
+ stop() {
463
+ throw new Error('Provider "' + this.pname + '" does not @override stop()');
464
+ }
465
+
466
+
467
+ /**
468
+ * Notify the subscriber defined in {@link addEventListener}
469
+ * @param {ProviderEvent[]} events events to send to subscriber
470
+ */
471
+ notify(...events) {
472
+ // Logging
473
+ ProvidersLoggerOld$1.incrementNotifications(this);
474
+
475
+ // Add current provider to the list of providers for this event.
476
+ events.forEach(event => event.providersStack.unshift(this.pname));
477
+
478
+ // Notify callbacks
479
+ this._eventsCallbacks.forEach(({ onEvents }) => onEvents(events));
480
+
481
+ // Keep a trace of the last events.
482
+ this._lastEvents = events;
483
+ }
484
+
485
+ /**
486
+ * Notify the subscriber defined in {@link addEventListener}
487
+ * @param {Error} error The error raised
488
+ */
489
+ notifyError(error) {
490
+ this._eventsCallbacks.forEach(({
491
+ id, onError
492
+ }) => {
493
+ if (ProvidersOptions.stopOnError) {
494
+ this.removeEventListener(id);
495
+ }
496
+ onError(error);
497
+ });
498
+ }
499
+
500
+ /** @type {ProviderEvent} */
501
+ get lastEvent() {
502
+ return this._lastEvents ? this._lastEvents[0] : null;
503
+ }
504
+
505
+ /** @type {ProviderEvent[]} */
506
+ get lastEvents() {
507
+ return this._lastEvents;
508
+ }
509
+
510
+ /**
511
+ * Input data from external interface
512
+ * @param {ProviderEvent} event
513
+ * @param {EventType} eventType
514
+ */
515
+ // eslint-disable-next-line no-unused-vars
516
+ feed(event, eventType) {
517
+ throw new Error('Not implemented');
518
+ }
519
+ }
520
+
521
+ class AskImuOnDesktopError extends Error {
522
+
523
+ static DEFAULT_MESSAGE = 'It seems that you ask for IMU events on a desktop browser';
524
+
525
+ constructor(message) {
526
+ super(message || AskImuOnDesktopError.DEFAULT_MESSAGE);
527
+ }
528
+ }
529
+
530
+ /**
531
+ * Imu (Inertial Measurement Unit) provider retrieve acceleration data
532
+ * and/or angular rate data from inertial sensors.
533
+ *
534
+ * -----------------------------------
535
+ * Overview of compatibilities:
536
+ * -----------------------------------
537
+ *
538
+ * Chrome Android (v72.0.3626): YES (via devicemotion)
539
+ * Safari iOS (v12.0): YES (via devicemotion)
540
+ * Opera Android (v50.2.2426): NO {@link https://developer.mozilla.org/en-US/docs/Web/API/DeviceMotionEvent}
541
+ * Firefox Android (v65.0.1): YES (via devicemotion)
542
+ *
543
+ * -----------------------------------
544
+ */
545
+ class Imu extends Provider {
546
+
547
+ /**
548
+ * @override
549
+ */
550
+ static get pname() {
551
+ return 'IMU';
552
+ }
553
+
554
+ /**
555
+ * @override
556
+ */
557
+ static get eventsType() {
558
+ return [EventType.AngularRate, EventType.Acceleration];
559
+ }
560
+
561
+ /**
562
+ * @override
563
+ */
564
+ static get _availability() {
565
+ return BrowserUtils.isMobile
566
+ ? Promise.resolve()
567
+ : Promise.reject(new AskImuOnDesktopError());
568
+ }
569
+
570
+ /**
571
+ * @override
572
+ */
573
+ start() {
574
+ const subscribe = () => window.addEventListener('devicemotion', this.parseDeviceMotionEvent, true);
575
+
576
+ if (typeof (DeviceMotionEvent) !== 'undefined' && typeof (DeviceMotionEvent.requestPermission) === 'function') {
577
+ DeviceMotionEvent
578
+ .requestPermission()
579
+ .then(response => {
580
+ if (response !== 'granted') {
581
+ throw new Error('Permission not granted');
582
+ }
583
+ subscribe();
584
+ })
585
+ .catch(error => this.notifyError(error));
586
+ } else {
587
+ subscribe();
588
+ }
589
+ }
590
+
591
+ /**
592
+ * @override
593
+ */
594
+ stop() {
595
+ window.removeEventListener('devicemotion', this.parseDeviceMotionEvent, true);
596
+ }
597
+
598
+ /**
599
+ * devicemotion callback
600
+ * @param {DeviceMotionEvent} e device motion event
601
+ * @returns {ProviderEvent[]} an array of provider events
602
+ * @private
603
+ */
604
+ parseDeviceMotionEvent = e => {
605
+ const events = [];
606
+
607
+ const timestamp = e.timeStamp / 1e3;
608
+
609
+ let acc;
610
+ if (e.accelerationIncludingGravity) {
611
+ const {
612
+ x, y, z
613
+ } = e.accelerationIncludingGravity;
614
+
615
+ if (typeof x === 'number' && typeof y === 'number' && typeof z === 'number') {
616
+ acc = [x, y, z];
617
+
618
+ if (BrowserUtils.name === Browser.SAFARI
619
+ || BrowserUtils.name === Browser.IOS_WEBVIEW) {
620
+ acc[0] *= -1;
621
+ acc[1] *= -1;
622
+ acc[2] *= -1;
623
+ }
624
+ }
625
+ }
626
+
627
+ if (acc) {
628
+ events.push(this.createEvent(EventType.Acceleration, {
629
+ timestamp,
630
+ values: acc
631
+ }));
632
+ }
633
+
634
+ let gyr;
635
+ if (e.rotationRate) {
636
+ const {
637
+ alpha, beta, gamma
638
+ } = e.rotationRate;
639
+
640
+ if (typeof alpha === 'number' && typeof beta === 'number' && typeof gamma === 'number') {
641
+ gyr = [deg2rad(alpha), deg2rad(beta), deg2rad(gamma)];
642
+ }
643
+ }
644
+
645
+ if (gyr) {
646
+ events.push(this.createEvent(EventType.AngularRate, {
647
+ timestamp,
648
+ values: gyr
649
+ }));
650
+ }
651
+
652
+ if (events.length !== 0) {
653
+ this.notify(...events);
654
+ }
655
+ }
656
+
657
+ }
658
+
659
+ var Imu$1 = new Imu();
660
+
661
+ class MissingSensorError extends Error {
662
+
663
+ static DEFAULT_MESSAGE = 'Impossible to retrieve events, a sensor is missing';
664
+
665
+ constructor(message) {
666
+ super(message || MissingSensorError.DEFAULT_MESSAGE);
667
+ }
668
+
669
+ from(fromMessage) {
670
+ this.message += ' from ' + fromMessage;
671
+ return this;
672
+ }
673
+ }
674
+
675
+ class MissingAccelerometerError extends MissingSensorError {
676
+
677
+ static DEFAULT_MESSAGE = 'Impossible to retrieve Acceleration data';
678
+
679
+ constructor(message) {
680
+ super(message || MissingAccelerometerError.DEFAULT_MESSAGE);
681
+ }
682
+ }
683
+
684
+ class Accelerometer extends Provider {
685
+
686
+ /**
687
+ * @override
688
+ */
689
+ static get pname() {
690
+ return 'Accelerometer';
691
+ }
692
+
693
+ /**
694
+ * @override
695
+ */
696
+ static get eventsType() {
697
+ return [EventType.Acceleration];
698
+ }
699
+
700
+ /**
701
+ * @override
702
+ */
703
+ get _availability() {
704
+ return Imu$1.availability;
705
+ }
706
+
707
+ /**
708
+ * @override
709
+ */
710
+ start() {
711
+ this.providerId = Imu$1.addEventListener(
712
+ events => this.parseImuEvents(events),
713
+ error => this.notifyError(error)
714
+ );
715
+ }
716
+
717
+ /**
718
+ * @override
719
+ */
720
+ stop() {
721
+ Imu$1.removeEventListener(this.providerId);
722
+ }
723
+
724
+ /**
725
+ * @private
726
+ */
727
+ parseImuEvents = events => {
728
+
729
+ const accelerationEvent = events.find(event => event.dataType === EventType.Acceleration);
730
+ if (!accelerationEvent) {
731
+ this.notifyError(new MissingAccelerometerError().from('devicemotion'));
732
+ return;
733
+ }
734
+ this.notify(accelerationEvent.clone());
735
+ }
736
+
737
+ }
738
+
739
+ var Accelerometer$1 = new Accelerometer();
740
+
741
+ class MissingGyroscopeError extends MissingSensorError {
742
+
743
+ static DEFAULT_MESSAGE = 'Impossible to retrieve Angular Rate data';
744
+
745
+ constructor(message) {
746
+ super(message || MissingSensorError.DEFAULT_MESSAGE);
747
+ }
748
+ }
749
+
750
+ class Gyroscope extends Provider {
751
+
752
+ /**
753
+ * @override
754
+ */
755
+ static get pname() {
756
+ return 'Gyroscope';
757
+ }
758
+
759
+ /**
760
+ * @override
761
+ */
762
+ static get eventsType() {
763
+ return [EventType.AngularRate];
764
+ }
765
+
766
+ /**
767
+ * @override
768
+ */
769
+ get _availability() {
770
+ return Imu$1.availability;
771
+ }
772
+
773
+ /**
774
+ * @override
775
+ */
776
+ start() {
777
+ this.providerId = Imu$1.addEventListener(
778
+ events => this.parseImuEvents(events),
779
+ error => this.notifyError(error)
780
+ );
781
+ }
782
+
783
+ /**
784
+ * @override
785
+ */
786
+ stop() {
787
+ Imu$1.removeEventListener(this.providerId);
788
+ }
789
+
790
+ /**
791
+ * @private
792
+ */
793
+ parseImuEvents = events => {
794
+
795
+ const gyroscopeEvent = events.find(event => event.dataType === EventType.AngularRate);
796
+ if (!gyroscopeEvent) {
797
+ this.notifyError(new MissingGyroscopeError().from('devicemotion'));
798
+ return;
799
+ }
800
+ this.notify(gyroscopeEvent.clone());
801
+ }
802
+
803
+ }
804
+
805
+ var Gyroscope$1 = new Gyroscope();
806
+
807
+ class HighRotationsDetector extends Provider {
808
+
809
+ // in radians by second
810
+ static THRESHOLD = 10;
811
+
812
+ /** @type {number} in seconds */
813
+ static DELAY_CONSIDERATION = 3;
814
+
815
+ /**
816
+ * @override
817
+ */
818
+ static get pname() {
819
+ return 'HighRotationsDetector';
820
+ }
821
+
822
+
823
+ /**
824
+ * @override
825
+ */
826
+ static get eventsType() {
827
+ return [EventType.HighRotation];
828
+ }
829
+
830
+ /**
831
+ * @override
832
+ */
833
+ get _availability() {
834
+ return Gyroscope$1.availability;
835
+ }
836
+
837
+ /**
838
+ * @override
839
+ */
840
+ start() {
841
+ this.providerId = Gyroscope$1.addEventListener(
842
+ events => this._parseGyroscopeEvent(events[0]),
843
+ error => this.notifyError(error)
844
+ );
845
+ }
846
+
847
+ /**
848
+ * @override
849
+ */
850
+ stop() {
851
+ Gyroscope$1.removeEventListener(this.providerId);
852
+ }
853
+
854
+ isInProgress() {
855
+ if (!this.lastEvent || !Gyroscope$1.lastEvent) {
856
+ return false;
857
+ }
858
+
859
+ const diffTime = Gyroscope$1.lastEvent.data.timestamp - this.lastEvent.data.timestamp;
860
+ return diffTime < HighRotationsDetector.DELAY_CONSIDERATION;
861
+ }
862
+
863
+ /**
864
+ * @private
865
+ */
866
+ _parseGyroscopeEvent = gyroscopeEvent => {
867
+
868
+ const { values, timestamp } = gyroscopeEvent.data;
869
+ const speed = Vector3.norm(values);
870
+ if (speed > HighRotationsDetector.THRESHOLD) {
871
+ this.notify(this.createEvent(EventType.HighRotation, { timestamp }, [gyroscopeEvent]));
872
+ }
873
+
874
+ }
875
+ }
876
+
877
+ var HighRotationsDetector$1 = new HighRotationsDetector();
878
+
879
+ const DEFAULT_RELATIVE_NOISES = {
880
+ acc: 0.5,
881
+ gyr: 0.3
882
+ };
883
+
884
+ const DEFAULT_ABSOLUTE_NOISES = {
885
+ acc: 0.5,
886
+ gyr: 0.3,
887
+ yc: 2
888
+ };
889
+
890
+ class EkfAttitude {
891
+
892
+ constructor(accRef = [0, 0, 1], ycRef = [-1, 0, 0]) {
893
+
894
+ this.accRef = accRef;
895
+ this.cRef = ycRef;
896
+
897
+ this.P = Matrix.diag(Array(4).fill(0.1 ** 2));
898
+
899
+ this.quaternion = null;
900
+
901
+
902
+ this.noises = {
903
+ relative: null,
904
+ absolute: null
905
+ };
906
+ this.setRelativeNoises(DEFAULT_RELATIVE_NOISES);
907
+ this.setAbsoluteNoises(DEFAULT_ABSOLUTE_NOISES);
908
+ }
909
+
910
+ setRelativeNoises(relativeNoises) {
911
+ this.noises.relative = {
912
+ accelerometer: Matrix.diag(Array(3).fill(relativeNoises.acc ** 2)),
913
+ gyroscope: Matrix.diag(Array(3).fill(relativeNoises.gyr ** 2))
914
+ };
915
+ }
916
+
917
+
918
+ setAbsoluteNoises(absoluteNoises) {
919
+ this.noises.absolute = {
920
+ accelerometer: Matrix.diag(Array(3).fill(absoluteNoises.acc ** 2)),
921
+ gyroscope: Matrix.diag(Array(3).fill(absoluteNoises.gyr ** 2)),
922
+ yc: Matrix.diag(Array(3).fill(absoluteNoises.yc ** 2))
923
+ };
924
+ }
925
+
926
+ /**
927
+ * Try to initialize filter.
928
+ * To initialize, we need at least current acceleration (acc)
929
+ */
930
+ tryInitialize(acc, mag) {
931
+
932
+ const accNormalized = Vector3.normalize(acc);
933
+
934
+ if (mag) {
935
+ const magNormalized = Vector3.normalize(mag);
936
+
937
+ const H = Vector3.normalize(Vector3.cross(magNormalized, accNormalized));
938
+ const M = Vector3.cross(accNormalized, H);
939
+
940
+ const R = [
941
+ [H[0], M[0], accNormalized[0]],
942
+ [H[1], M[1], accNormalized[1]],
943
+ [H[2], M[2], accNormalized[2]]
944
+ ];
945
+
946
+ this.quaternion = Quaternion.fromMatrix3(R);
947
+
948
+ } else {
949
+
950
+ const r = Vector3.dot(accNormalized, this.accRef) + 1;
951
+ const v = Vector3.cross(accNormalized, this.accRef);
952
+
953
+ let quaternion = [r, v[0], v[1], v[2]];
954
+ quaternion = Quaternion.normalize(quaternion);
955
+
956
+ this.quaternion = quaternion;
957
+ }
958
+
959
+ return this.quaternion;
960
+ }
961
+
962
+ update(diffTime, acc, gyr, mag) {
963
+
964
+ if (!this.quaternion) {
965
+ return this.tryInitialize(acc, mag);
966
+ }
967
+
968
+ let q = this.quaternion;
969
+
970
+ /* ------------
971
+ * ESTIMATION
972
+ * ------------*/
973
+
974
+ const qArray = q;
975
+ const gyrInt = Vector3.multiplyScalar(gyr, 0.5 * diffTime);
976
+ const F = this.computeC([1, gyrInt[0], gyrInt[1], gyrInt[2]]);
977
+ const qAPriori = Matrix.multiplyVector(F, q);
978
+ const E1 = Matrix.diag([qArray[0], qArray[0], qArray[0]]);
979
+ const eSkew = Matrix3.skew([qArray[1], qArray[2], qArray[3]]);
980
+
981
+ const qPart = [-1 * qArray[1], -1 * qArray[2], -1 * qArray[3]];
982
+ const E = Matrix.concatRow([qPart], Matrix3.add(eSkew, E1));
983
+
984
+ const Qk = Matrix.multiplyScalar(
985
+ Matrix.multiply(
986
+ Matrix.multiply(E, this.noises[mag ? 'absolute' : 'relative'].gyroscope),
987
+ Matrix.transpose(E)
988
+ ),
989
+ (diffTime / 2) ** 2
990
+ );
991
+
992
+ const pAPriori = Matrix4.add(
993
+ Matrix.multiply(
994
+ Matrix.multiply(F, this.P),
995
+ Matrix.transpose(F)
996
+ ),
997
+ Qk
998
+ );
999
+
1000
+ /* ------------
1001
+ * CORRECTION
1002
+ * ------------*/
1003
+
1004
+ const accNormalized = Vector3.normalize(acc);
1005
+ let dz, K, H;
1006
+
1007
+ if (mag) {
1008
+
1009
+ const magNormalized = Vector3.normalize(mag);
1010
+ const yc = Vector3.cross(accNormalized, magNormalized);
1011
+ const ycNormalized = Vector3.normalize(yc);
1012
+
1013
+ const dzYc = Vector3.subtract(ycNormalized, Quaternion.rotate(qAPriori, this.cRef));
1014
+ const dzAcc = Vector3.subtract(accNormalized, Quaternion.rotate(qAPriori, this.accRef));
1015
+ dz = Vector.concat(dzYc, dzAcc);
1016
+
1017
+ const HYc = this.jacobianES(qAPriori, this.cRef);
1018
+ const HAcc = this.jacobianES(qAPriori, this.accRef);
1019
+ H = Matrix.concatRow(HYc, HAcc);
1020
+
1021
+ const RYc = Matrix.concatLine(this.noises.absolute.yc, Matrix3.zeros);
1022
+ const RAcc = Matrix.concatLine(Matrix3.zeros, this.noises.absolute.accelerometer);
1023
+ const R = Matrix.concatRow(RYc, RAcc);
1024
+
1025
+ K = Matrix.multiply(
1026
+ Matrix.multiply(pAPriori, Matrix.transpose(H)),
1027
+ Matrix.inverse(
1028
+ Matrix.add(
1029
+ Matrix.multiply(
1030
+ Matrix.multiply(H, pAPriori),
1031
+ Matrix.transpose(H)
1032
+ ),
1033
+ R
1034
+ )
1035
+ )
1036
+ );
1037
+ } else {
1038
+ dz = Vector3.subtract(accNormalized, Quaternion.rotate(qAPriori, this.accRef));
1039
+ H = this.jacobianES(qAPriori, this.accRef);
1040
+ const R = this.noises.relative.accelerometer;
1041
+
1042
+ K = Matrix.multiply(
1043
+ Matrix.multiply(pAPriori, Matrix.transpose(H)),
1044
+ Matrix3.inverse(
1045
+ Matrix3.add(
1046
+ Matrix.multiply(
1047
+ Matrix.multiply(H, pAPriori),
1048
+ Matrix.transpose(H)
1049
+ ),
1050
+ R
1051
+ )
1052
+ )
1053
+ );
1054
+ }
1055
+
1056
+ q = Quaternion.add(
1057
+ qAPriori,
1058
+ Matrix.multiplyVector(K, dz)
1059
+ );
1060
+ const P = Matrix.multiply(
1061
+ Matrix4.subtract(
1062
+ Matrix4.identity,
1063
+ Matrix.multiply(K, H)
1064
+ ),
1065
+ pAPriori
1066
+ );
1067
+
1068
+ q = Quaternion.normalize(q);
1069
+ this.quaternion = q;
1070
+ this.P = P;
1071
+
1072
+ return q;
1073
+ }
1074
+
1075
+ computeC(b) {
1076
+ return [
1077
+ [b[0], -b[1], -b[2], -b[3]],
1078
+ [b[1], b[0], b[3], -b[2]],
1079
+ [b[2], -b[3], b[0], b[1]],
1080
+ [b[3], b[2], -b[1], b[0]]
1081
+ ];
1082
+ }
1083
+
1084
+ jacobianES(q, v) {
1085
+
1086
+ const [qw, qx, qy, qz] = q;
1087
+ const [vx, vy, vz] = v;
1088
+
1089
+ return [
1090
+ [2 * qz * vy - 2 * qy * vz, 2 * qy * vy + 2 * qz * vz, 2 * qx * vy - 2 * qw * vz - 4 * qy * vx, 2 * qw * vy + 2 * qx * vz - 4 * qz * vx],
1091
+ [2 * qx * vz - 2 * qz * vx, 2 * qw * vz - 4 * qx * vy + 2 * qy * vx, 2 * qx * vx + 2 * qz * vz, 2 * qy * vz - 2 * qw * vx - 4 * qz * vy],
1092
+ [2 * qy * vx - 2 * qx * vy, 2 * qz * vx - 4 * qx * vz - 2 * qw * vy, 2 * qw * vx - 4 * qy * vz + 2 * qz * vy, 2 * qx * vx + 2 * qy * vy]
1093
+ ];
1094
+ }
1095
+ }
1096
+
1097
+ /**
1098
+ * Relative attitude provider gives the device attitude in East-North-Up (ENU) frame using
1099
+ * browser deviceorientation
1100
+ * The provider does not work until an offset is given.
1101
+ */
1102
+ class RelativeAttitudeFromEkf extends Provider {
1103
+
1104
+ lastTimestamp = 0;
1105
+
1106
+ /**
1107
+ * @override
1108
+ */
1109
+ constructor(context) {
1110
+ super(context);
1111
+ this.ekfAttitude = new EkfAttitude();
1112
+ }
1113
+
1114
+ /**
1115
+ * @override
1116
+ */
1117
+ static get pname() {
1118
+ return 'RelativeAttitudeFromEkf';
1119
+ }
1120
+
1121
+ /**
1122
+ * @override
1123
+ */
1124
+ static get eventsType() {
1125
+ return [EventType.RelativeAttitude];
1126
+ }
1127
+
1128
+ /**
1129
+ * @override
1130
+ */
1131
+ get _availability() {
1132
+ return Promise.all([
1133
+ Accelerometer$1.availability,
1134
+ Gyroscope$1.availability
1135
+ ]);
1136
+ }
1137
+
1138
+ /**
1139
+ * @override
1140
+ */
1141
+ start() {
1142
+ this.accelerometerProviderId = Accelerometer$1.addEventListener(
1143
+ events => this.onAccelerometerEvent(events[0]),
1144
+ error => this.notifyError(error)
1145
+ );
1146
+
1147
+ this.gyroscopeProviderId = Gyroscope$1.addEventListener(
1148
+ events => (this.gyroscopeEvent = events[0]),
1149
+ error => this.notifyError(error)
1150
+ );
1151
+ }
1152
+
1153
+ /**
1154
+ * @override
1155
+ */
1156
+ stop() {
1157
+ Accelerometer$1.removeEventListener(this.accelerometerProviderId);
1158
+ Gyroscope$1.removeEventListener(this.gyroscopeProviderId);
1159
+ }
1160
+
1161
+ /**
1162
+ * @private
1163
+ */
1164
+ onAccelerometerEvent = accelerationEvent => {
1165
+
1166
+ if (!this.gyroscopeEvent) {
1167
+ return;
1168
+ }
1169
+
1170
+ const {
1171
+ values: acceleration, timestamp
1172
+ } = accelerationEvent.data;
1173
+
1174
+ // Handle timestamps and dt
1175
+ if (this.lastTimestamp === 0) {
1176
+ this.lastTimestamp = timestamp;
1177
+ return;
1178
+ }
1179
+ const diffTime = timestamp - this.lastTimestamp;
1180
+ this.lastTimestamp = timestamp;
1181
+
1182
+ const quaternion = this.ekfAttitude.update(diffTime, acceleration, this.gyroscopeEvent.data.values);
1183
+
1184
+ if (quaternion) {
1185
+ const attitude = new Attitude(quaternion,
1186
+ timestamp,
1187
+ RelativeAttitudeFromInertial$1.DEFAULT_DRIFT
1188
+ );
1189
+ this.notify(this.createEvent(
1190
+ EventType.RelativeAttitude,
1191
+ attitude,
1192
+ [accelerationEvent, this.gyroscopeEvent]));
1193
+ }
1194
+ };
1195
+ }
1196
+
1197
+ var RelativeAttitudeFromEkf$1 = new RelativeAttitudeFromEkf();
1198
+
1199
+ class RelativeAttitudeFromInertial extends Provider {
1200
+
1201
+ /**
1202
+ * default relative attitude drift in rad.second-1
1203
+ */
1204
+ DEFAULT_DRIFT = deg2rad(5) / 60;
1205
+
1206
+
1207
+ /**
1208
+ * @override
1209
+ */
1210
+ static get pname() {
1211
+ return 'RelativeAttitudeFromInertial';
1212
+ }
1213
+
1214
+ /**
1215
+ * @override
1216
+ */
1217
+ static get eventsType() {
1218
+ return [EventType.RelativeAttitude];
1219
+ }
1220
+
1221
+ /**
1222
+ * @override
1223
+ */
1224
+ get _availability() {
1225
+ return PromiseUtils.any([
1226
+ RelativeAttitudeFromEkf$1.availability,
1227
+ RelativeAttitudeFromBrowser$1.availability,
1228
+ HighRotationsDetector$1.availability
1229
+ ]);
1230
+ }
1231
+
1232
+ /**
1233
+ * @override
1234
+ */
1235
+ start() {
1236
+
1237
+ RelativeAttitudeFromEkf$1.availability
1238
+ .then(() => (this.provider = RelativeAttitudeFromEkf$1))
1239
+ .catch(() => (this.provider = RelativeAttitudeFromBrowser$1))
1240
+ .finally(() => {
1241
+ this.listenerId = this.provider.addEventListener(
1242
+ events => this._parseEvent(events[0]),
1243
+ error => this.notifyError(error)
1244
+ );
1245
+ this._highRotationsDetector = HighRotationsDetector$1.addEventListener(
1246
+ () => {},
1247
+ error => this.notifyError(error)
1248
+ );
1249
+ });
1250
+ }
1251
+
1252
+ /**
1253
+ * @param {ProviderEvent<Attitude>} event
1254
+ */
1255
+ _parseEvent(event) {
1256
+ const relativeAttitudeEvent = event.clone();
1257
+ if (HighRotationsDetector$1.isInProgress()) {
1258
+ let accuracy = relativeAttitudeEvent.data.accuracy + (this.DEFAULT_DRIFT * 100);
1259
+ accuracy = Math.min(accuracy, Math.PI);
1260
+ relativeAttitudeEvent.data.accuracy = accuracy;
1261
+ }
1262
+ this.notify(relativeAttitudeEvent);
1263
+ }
1264
+
1265
+ /**
1266
+ * @override
1267
+ */
1268
+ stop() {
1269
+ if (this.provider) {
1270
+ this.provider.removeEventListener(this.listenerId);
1271
+ this.provider = null;
1272
+ }
1273
+ HighRotationsDetector$1.removeEventListener(this._highRotationsDetector);
1274
+ }
1275
+ }
1276
+
1277
+ var RelativeAttitudeFromInertial$1 = new RelativeAttitudeFromInertial();
1278
+
1279
+ /**
1280
+ * Relative attitude provider gives the device attitude in a custom frame (x-right, y-front, z-up) using
1281
+ * browser deviceorientation
1282
+ *
1283
+ * -----------------------------------
1284
+ * Overview of compatibilities:
1285
+ * -----------------------------------
1286
+ *
1287
+ * Chrome Android (v72.0.3626): YES (via deviceorientation but deviceorientation.alpha is unreliable! Sometimes it starts at 0°, sometimes at 270°)
1288
+ * Safari iOS (v12.0): YES (via deviceorientation)
1289
+ * Opera Android (v50.2.2426): NO {@link https://developer.mozilla.org/en-US/docs/Web/API/Window/ondeviceorientation}
1290
+ * Firefox Android (v65.0.1): YES (via deviceorientation)
1291
+ *
1292
+ * -----------------------------------
1293
+ */
1294
+ class RelativeAttitudeFromBrowser extends Provider {
1295
+
1296
+ /**
1297
+ * @override
1298
+ */
1299
+ static get pname() {
1300
+ return 'RelativeAttitudeFromBrowser';
1301
+ }
1302
+
1303
+ /**
1304
+ * @override
1305
+ */
1306
+ static get eventsType() {
1307
+ return [EventType.RelativeAttitude];
1308
+ }
1309
+
1310
+ /**
1311
+ * @override
1312
+ */
1313
+ get _availability() {
1314
+ return BrowserUtils.isMobile
1315
+ ? Promise.resolve()
1316
+ : Promise.reject(new AskImuOnDesktopError());
1317
+ }
1318
+
1319
+ /**
1320
+ * @override
1321
+ */
1322
+ start() {
1323
+ const subscribe = () => window.addEventListener('deviceorientation', this.onDeviceOrientationEvent, true);
1324
+
1325
+ if (typeof (DeviceOrientationEvent) !== 'undefined' && typeof (DeviceOrientationEvent.requestPermission) === 'function') {
1326
+ DeviceOrientationEvent
1327
+ .requestPermission()
1328
+ .then(response => {
1329
+ if (response !== 'granted') {
1330
+ throw new Error('Permission not granted');
1331
+ }
1332
+ subscribe();
1333
+ })
1334
+ .catch(error => this.notifyError(error));
1335
+ } else {
1336
+ subscribe();
1337
+ }
1338
+ }
1339
+
1340
+ /**
1341
+ * @override
1342
+ */
1343
+ stop() {
1344
+ window.removeEventListener('deviceorientation', this.onDeviceOrientationEvent, true);
1345
+ }
1346
+
1347
+
1348
+ onDeviceOrientationEvent = e => {
1349
+
1350
+ if (typeof e.alpha !== 'number' || typeof e.beta !== 'number' || typeof e.gamma !== 'number') {
1351
+ this.notifyError(new MissingSensorError().from('deviceorientation'));
1352
+ return;
1353
+ }
1354
+
1355
+ const quaternion = Rotations.eulerToQuaternionZXYDegrees([e.alpha, e.beta, e.gamma]);
1356
+ const attitude = new Attitude(quaternion,
1357
+ e.timeStamp / 1e3,
1358
+ RelativeAttitudeFromInertial$1.DEFAULT_DRIFT
1359
+ );
1360
+ this.notify(this.createEvent(EventType.RelativeAttitude, attitude));
1361
+ };
1362
+ }
1363
+
1364
+ var RelativeAttitudeFromBrowser$1 = new RelativeAttitudeFromBrowser();
1365
+
1366
+ class MissingArCoreError extends Error {
1367
+
1368
+ static DEFAULT_MESSAGE = 'ARCore is missing';
1369
+
1370
+ constructor(message) {
1371
+ super(message || MissingArCoreError.DEFAULT_MESSAGE);
1372
+ }
1373
+ }
1374
+
1375
+ class MissingNativeInterfaceError extends Error {
1376
+
1377
+ static DEFAULT_MESSAGE = 'Native interface is missing';
1378
+
1379
+ constructor(message) {
1380
+ super(message || MissingNativeInterfaceError.DEFAULT_MESSAGE);
1381
+ }
1382
+ }
1383
+
1384
+ /* eslint-disable no-bitwise */
1385
+ class ArCore extends Provider {
1386
+
1387
+ static Payload = {
1388
+ Pose: {
1389
+ ref: 2 ** 0,
1390
+ size: 7
1391
+ },
1392
+ Barcode: {
1393
+ ref: 2 ** 1,
1394
+ size: 1
1395
+ },
1396
+ ProjMat: {
1397
+ ref: 2 ** 2,
1398
+ size: 16
1399
+ },
1400
+ ImageRef: {
1401
+ ref: 2 ** 3,
1402
+ size: 1
1403
+ }
1404
+ };
1405
+
1406
+ /**
1407
+ * default relative attitude drift in rad.second-1
1408
+ */
1409
+ static RELATIVE_ATTITUDE_DRIFT = deg2rad(3) / 60;
1410
+
1411
+ previousPosition = [0, 0, 0];
1412
+
1413
+ /**
1414
+ * @override
1415
+ */
1416
+ static get pname() {
1417
+ return 'ArCore';
1418
+ }
1419
+
1420
+ /**
1421
+ * @override
1422
+ */
1423
+ static get eventsType() {
1424
+ return [EventType.RelativeAttitude, EventType.RelativePosition, EventType.Barcode];
1425
+ }
1426
+
1427
+
1428
+ /**
1429
+ * @override
1430
+ */
1431
+ start() {
1432
+ if (this.nativeProvider) {
1433
+ this.nativeProvider.start();
1434
+ }
1435
+
1436
+ this.pullDataLoop();
1437
+ }
1438
+
1439
+ /**
1440
+ * @override
1441
+ */
1442
+ stop() {
1443
+ if (this.nativeProvider) {
1444
+ this.nativeProvider.stop();
1445
+ }
1446
+ }
1447
+
1448
+ pullDataLoop = () => {
1449
+
1450
+ if (this.state === ProviderState.STOPPPED) {
1451
+ return;
1452
+ }
1453
+
1454
+ const payload = JSON.parse(this.nativeProvider.getInfo());
1455
+ if (payload.length > 1) {
1456
+ this.parsePayload(payload);
1457
+ }
1458
+ requestAnimationFrame(this.pullDataLoop);
1459
+ }
1460
+
1461
+ parsePayload(payload) {
1462
+
1463
+ const ref = payload[0];
1464
+ let bufferIndex = 1;
1465
+
1466
+ const events = [];
1467
+
1468
+ const time = TimeUtils.preciseTime() / 1e3;
1469
+
1470
+ if (ref & ArCore.Payload.Pose.ref) {
1471
+
1472
+ const attitude = new Attitude(
1473
+ payload.slice(bufferIndex, bufferIndex + 4),
1474
+ time,
1475
+ this.constructor.RELATIVE_ATTITUDE_DRIFT,
1476
+ );
1477
+ events.push(this.createEvent(EventType.RelativeAttitude, attitude));
1478
+
1479
+ const newPosition = [payload[bufferIndex + 4], payload[bufferIndex + 5], payload[bufferIndex + 6]];
1480
+ const position = new RelativePosition(
1481
+ newPosition[0] - this.previousPosition[0],
1482
+ newPosition[1] - this.previousPosition[1],
1483
+ newPosition[2] - this.previousPosition[2],
1484
+ time,
1485
+ 1e-4
1486
+ );
1487
+ this.previousPosition = newPosition;
1488
+ events.push(this.createEvent(EventType.RelativePosition, position));
1489
+
1490
+ bufferIndex += ArCore.Payload.Pose.size;
1491
+ }
1492
+
1493
+ if (ref & ArCore.Payload.Barcode.ref) {
1494
+ events.push(this.createEvent(EventType.Barcode, payload[bufferIndex]));
1495
+ bufferIndex += ArCore.Payload.Barcode.size;
1496
+ }
1497
+
1498
+ if (ref & ArCore.Payload.ProjMat.ref) {
1499
+ const projMatrix = payload.slice(bufferIndex, bufferIndex + ArCore.Payload.ProjMat.size);
1500
+ events.push(this.createEvent(EventType.CameraProjectionMatrix, projMatrix));
1501
+ bufferIndex += ArCore.Payload.ProjMat.size;
1502
+ }
1503
+
1504
+ if (events.length !== 0) {
1505
+ this.notify(...events);
1506
+ }
1507
+ }
1508
+
1509
+
1510
+ get nativeProvider() {
1511
+
1512
+ if (!this._nativeProvider) {
1513
+
1514
+ if (!this.nativeInterface) {
1515
+ throw new MissingNativeInterfaceError();
1516
+ }
1517
+
1518
+ this._nativeProvider = this.nativeInterface.getArCoreProvider();
1519
+ if (!this._nativeProvider) {
1520
+ throw new MissingArCoreError();
1521
+ }
1522
+
1523
+ }
1524
+
1525
+ return this._nativeProvider;
1526
+ }
1527
+
1528
+ /**
1529
+ * @override
1530
+ */
1531
+ get _availability() {
1532
+ try {
1533
+ const nativeProvider = this.nativeProvider;
1534
+
1535
+ if (!nativeProvider.checkAvailability()) {
1536
+ return Promise.reject(new MissingArCoreError());
1537
+ }
1538
+
1539
+ } catch (e) {
1540
+ return Promise.reject(e);
1541
+ }
1542
+
1543
+ return Promise.resolve();
1544
+ }
1545
+
1546
+ enableBarcodeScanner() {
1547
+ try {
1548
+ this.nativeProvider.enableBarcodeScanner();
1549
+ } catch (e) {
1550
+ this.notifyError(e);
1551
+ }
1552
+ }
1553
+
1554
+ disableBarcodeScanner() {
1555
+ try {
1556
+ this.nativeProvider.disableBarcodeScanner();
1557
+ } catch (e) {
1558
+ this.notifyError(e);
1559
+ }
1560
+ }
1561
+
1562
+ /**
1563
+ * @override
1564
+ */
1565
+ static get useCameraNatively() {
1566
+ return true;
1567
+ }
1568
+ }
1569
+
1570
+ var ArCore$1 = new ArCore();
1571
+
1572
+ /**
1573
+ * Relative attitude provider gives the device attitude in East-North-Up (ENU) frame using
1574
+ * browser deviceorientation
1575
+ * The provider does not work until an offset is given.
1576
+ */
1577
+ class RelativeAttitude extends Provider {
1578
+
1579
+ /**
1580
+ * @override
1581
+ */
1582
+ static get pname() {
1583
+ return 'RelativeAttitude';
1584
+ }
1585
+
1586
+ /**
1587
+ * @override
1588
+ */
1589
+ static get eventsType() {
1590
+ return [EventType.RelativeAttitude];
1591
+ }
1592
+
1593
+ /**
1594
+ * @override
1595
+ */
1596
+ get _availability() {
1597
+ return RelativeAttitudeFromInertial$1.availability;
1598
+ }
1599
+
1600
+ /**
1601
+ * @override
1602
+ */
1603
+ start() {
1604
+ this.arCoreMonitoringId = ArCore$1.addMonitoringListener(
1605
+ () => {
1606
+ this.listenArCore();
1607
+ this.unlistenInertial();
1608
+ }, () => {
1609
+ this.unlistenArCore();
1610
+ this.listenInertial();
1611
+ });
1612
+
1613
+ if (ArCore$1.state === ProviderState.STARTED) {
1614
+ this.listenArCore();
1615
+ } else {
1616
+ this.listenInertial();
1617
+ }
1618
+ }
1619
+
1620
+ /**
1621
+ * @override
1622
+ */
1623
+ stop() {
1624
+ ArCore$1.removeMonitoringListener(this.arCoreMonitoringId);
1625
+ this.unlistenArCore();
1626
+ this.unlistenInertial();
1627
+ }
1628
+
1629
+
1630
+ listenInertial = () => {
1631
+ this.inertialProviderId = RelativeAttitudeFromInertial$1.addEventListener(
1632
+ events => this.notify(events[0].clone()),
1633
+ error => this.notifyError(error)
1634
+ );
1635
+ }
1636
+
1637
+ unlistenInertial = () => {
1638
+ RelativeAttitudeFromInertial$1.removeEventListener(this.inertialProviderId);
1639
+ }
1640
+
1641
+ listenArCore = () => {
1642
+ this.arCoreProviderId = ArCore$1.addEventListener(
1643
+ events => {
1644
+ const relativeAttitudeEvent = events.find(event => event.dataType === EventType.RelativeAttitude);
1645
+ if (relativeAttitudeEvent) {
1646
+ this.notify(relativeAttitudeEvent.clone());
1647
+ }
1648
+ },
1649
+ () => {},
1650
+ false
1651
+ );
1652
+ };
1653
+
1654
+ unlistenArCore = () => {
1655
+ ArCore$1.removeEventListener(this.arCoreProviderId);
1656
+ }
1657
+ }
1658
+
1659
+ var RelativeAttitude$1 = new RelativeAttitude();
1660
+
1661
+ class MissingMagnetometerError extends MissingSensorError {
1662
+ constructor(message) {
1663
+ super(message);
1664
+ }
1665
+ }
1666
+
1667
+ /**
1668
+ * Absolute attitude provider gives the device attitude in East-North-Up (ENU) frame
1669
+ */
1670
+ class AbsoluteAttitude extends Provider {
1671
+
1672
+ /** @type {boolean} */
1673
+ _attitudeFromBrowserErrored = false;
1674
+
1675
+ /** @type {boolean} */
1676
+ _attitudeFromRelativeErrored = false;
1677
+
1678
+ /*
1679
+ * RELATIVE
1680
+ */
1681
+
1682
+ /** @type {Attitude} */
1683
+ _relativeAttitude = null;
1684
+
1685
+ /** @type {number} in radians/s */
1686
+ _relativeAccuracy = 0;
1687
+
1688
+ /** @type {ProviderEvent<Attitude|AbsoluteHeading>} */
1689
+ _lastForcedHeadingEvent;
1690
+
1691
+ /** @type {number[]} quaternion offset from relative to absolute */
1692
+ _relAbsQuat;
1693
+
1694
+
1695
+ constructor() {
1696
+ super();
1697
+
1698
+ this._attitudeFromBrowserErrored = false;
1699
+ this._attitudeFromRelativeErrored = false;
1700
+ }
1701
+
1702
+ /**
1703
+ * @override
1704
+ */
1705
+ static get pname() {
1706
+ return 'AbsoluteAttitude';
1707
+ }
1708
+
1709
+ /**
1710
+ * @override
1711
+ */
1712
+ static get eventsType() {
1713
+ return [EventType.AbsoluteAttitude];
1714
+ }
1715
+
1716
+ /**
1717
+ * @override
1718
+ */
1719
+ get _availability() {
1720
+ return PromiseUtils.any([
1721
+ AbsoluteAttitudeFromBrowser$1.availability,
1722
+ RelativeAttitude$1.availability
1723
+ ]);
1724
+ }
1725
+
1726
+ /**
1727
+ * @override
1728
+ */
1729
+ start() {
1730
+
1731
+ this.fromBrowserProviderId = AbsoluteAttitudeFromBrowser$1.addEventListener(
1732
+ events => this._onAttitudeFromBrowser(events[0]),
1733
+ error => {
1734
+ this._attitudeFromBrowserErrored = true;
1735
+ this.onError(error);
1736
+ }
1737
+ );
1738
+
1739
+ this.relativeAttitudeProviderId = RelativeAttitude$1.addEventListener(
1740
+ events => this._onRelativeAttitudeEvent(events[0]),
1741
+ error => {
1742
+ this._attitudeFromRelativeErrored = true;
1743
+ this.onError(error);
1744
+ }
1745
+ );
1746
+
1747
+ }
1748
+
1749
+ /**
1750
+ * @override
1751
+ */
1752
+ stop() {
1753
+ AbsoluteAttitudeFromBrowser$1.removeEventListener(this.fromBrowserProviderId);
1754
+ RelativeAttitude$1.removeEventListener(this.relativeAttitudeProviderId);
1755
+ }
1756
+
1757
+ onError(error) {
1758
+ if (this._attitudeFromBrowserErrored && this._attitudeFromRelativeErrored) {
1759
+ this.notifyError(error);
1760
+ }
1761
+ }
1762
+
1763
+
1764
+ /**
1765
+ * @param {ProviderEvent<Attitude|AbsoluteHeading>} absoluteHeadingEvent
1766
+ */
1767
+ _forceHeadingForRelative = (absoluteHeadingEvent) => {
1768
+
1769
+ if (this.lastEvent && absoluteHeadingEvent.data.accuracy > this.lastEvent.data.accuracy) {
1770
+ return;
1771
+ }
1772
+
1773
+
1774
+ /**
1775
+ * @param {Attitude} relativeAttitude
1776
+ */
1777
+ const calcForceHeading = (relativeAttitude) => {
1778
+ const currentRelativeHeading = relativeAttitude.heading;
1779
+
1780
+ this._relAbsQuat = Quaternion.fromAxisAngle(
1781
+ [0, 0, 1],
1782
+ currentRelativeHeading - absoluteHeadingEvent.data.heading
1783
+ );
1784
+
1785
+ this._relativeAccuracy = 0;
1786
+ this._lastForcedHeadingEvent = absoluteHeadingEvent;
1787
+ };
1788
+
1789
+ // If relativeAttitude does not exist yet, wait the next
1790
+ if (this._relativeAttitude) {
1791
+ calcForceHeading(this._relativeAttitude);
1792
+ return;
1793
+ }
1794
+
1795
+ // If this._relativeAttitude does not exist, wait the first value
1796
+ // /!\ This works only if the relative attitude event is provided rapidly.
1797
+ const providerId = RelativeAttitude$1.addEventListener(
1798
+ events => {
1799
+ calcForceHeading(events[0].data);
1800
+ RelativeAttitude$1.removeEventListener(providerId);
1801
+ }
1802
+ );
1803
+
1804
+ };
1805
+
1806
+ /**
1807
+ * @param {ProviderEvent<Attitude>} event
1808
+ */
1809
+ _onRelativeAttitudeEvent(event) {
1810
+
1811
+ const { quaternion, accuracy, time } = event.data;
1812
+
1813
+ // Calculate relative accuracy
1814
+ if (this._relativeAttitude) {
1815
+ this._relativeAccuracy += (time - this._relativeAttitude.time) * accuracy;
1816
+ }
1817
+
1818
+ // Keep the relative attitude event for the calculation of relAbsQuat
1819
+ // /!\ Keep this even if forced heading is not set /!\
1820
+ this._relativeAttitude = event.data;
1821
+
1822
+
1823
+ if (!this._lastForcedHeadingEvent) {
1824
+ return;
1825
+ }
1826
+
1827
+ const accuracyWithRelative = Math.min(
1828
+ this._lastForcedHeadingEvent.data.accuracy + this._relativeAccuracy,
1829
+ Math.PI
1830
+ );
1831
+
1832
+ let newAccuracy = accuracyWithRelative;
1833
+ let relAbsQuat = this._relAbsQuat;
1834
+
1835
+ if (this._eventFromBrowser) {
1836
+
1837
+ const {
1838
+ accuracy: accuracyWithAbsolute,
1839
+ heading: headingFromAbsolute
1840
+ } = this._eventFromBrowser.data;
1841
+
1842
+ if (accuracyWithAbsolute < accuracyWithRelative) {
1843
+ newAccuracy = accuracyWithAbsolute;
1844
+ relAbsQuat = Quaternion.fromAxisAngle([0, 0, 1], event.data.heading - headingFromAbsolute);
1845
+ }
1846
+ }
1847
+
1848
+ const absoluteQuat = Quaternion.multiply(relAbsQuat, quaternion);
1849
+ const attitude = new Attitude(absoluteQuat, time, newAccuracy);
1850
+
1851
+ this.notify(this.createEvent(
1852
+ EventType.AbsoluteAttitude,
1853
+ attitude,
1854
+ [event, this._lastForcedHeadingEvent]
1855
+ ));
1856
+ }
1857
+
1858
+
1859
+ /**
1860
+ * @param {ProviderEvent<Attitude>} event
1861
+ */
1862
+ _onAttitudeFromBrowser(event) {
1863
+ this._eventFromBrowser = event;
1864
+
1865
+ if (!this._lastForcedHeadingEvent) {
1866
+ this._forceHeadingForRelative(event);
1867
+ return;
1868
+ }
1869
+
1870
+ // Absolute attitude from browser is not used anymore due to magnetometer inaccuracy on pitch and roll
1871
+ }
1872
+
1873
+ /**
1874
+ * @override
1875
+ * @param {AbsoluteHeading|Attitude|ProviderEvent} data
1876
+ */
1877
+ feed(data) {
1878
+
1879
+ if (data instanceof AbsoluteHeading) {
1880
+
1881
+ if (data.time === null) {
1882
+ throw Error('the time of the absolute heading is not defined');
1883
+ }
1884
+ if (data.accuracy === null) {
1885
+ throw Error('the accuracy of the absolute heading is not defined');
1886
+ }
1887
+
1888
+ this._forceHeadingForRelative(new ProviderEvent(EventType.AbsoluteHeading, data));
1889
+
1890
+ } else if (data instanceof Attitude) {
1891
+
1892
+ if (data.time === null) {
1893
+ throw Error('the time of the attitude is not defined');
1894
+ }
1895
+ if (data.accuracy === null) {
1896
+ throw Error('the accuracy of the attitude is not defined');
1897
+ }
1898
+
1899
+ this._forceHeadingForRelative(new ProviderEvent(EventType.AbsoluteHeading, data));
1900
+
1901
+ } else if (data instanceof ProviderEvent) {
1902
+
1903
+ if (data.dataType !== EventType.AbsoluteHeading
1904
+ || !(data.data instanceof AbsoluteHeading)) {
1905
+ throw Error('the provider event is not an AbsoluteHeading');
1906
+ }
1907
+
1908
+ this._forceHeadingForRelative(data);
1909
+
1910
+ } else {
1911
+ throw new Error('data is nor an AbsoluteHeading or an Attitude object');
1912
+ }
1913
+ }
1914
+ }
1915
+
1916
+ var AbsoluteAttitude$1 = new AbsoluteAttitude();
1917
+
1918
+ class TurnDetector extends Provider {
1919
+
1920
+ // in seconds
1921
+ static SLIDING_WINDOW_TIME = 0.3;
1922
+
1923
+ static STD_THRESHOLD = 0.075;
1924
+
1925
+ /** @type {number} in seconds */
1926
+ static CONSIDER_TURN_UNTIL = 1;
1927
+
1928
+ /** @type {number[]} */
1929
+ slidingWindow = [];
1930
+
1931
+ /**
1932
+ * @override
1933
+ */
1934
+ static get pname() {
1935
+ return 'TurnDetector';
1936
+ }
1937
+
1938
+ /**
1939
+ * @override
1940
+ */
1941
+ static get eventsType() {
1942
+ return [EventType.Turn];
1943
+ }
1944
+
1945
+ /**
1946
+ * @override
1947
+ */
1948
+ get _availability() {
1949
+ return RelativeAttitude$1.availability;
1950
+ }
1951
+
1952
+ /**
1953
+ * @override
1954
+ */
1955
+ start() {
1956
+ this.providerId = RelativeAttitude$1.addEventListener(
1957
+ events => this._parseRelativeAttitude(events[0]),
1958
+ error => this.notifyError(error)
1959
+ );
1960
+ }
1961
+
1962
+ /**
1963
+ * @override
1964
+ */
1965
+ stop() {
1966
+ RelativeAttitude$1.removeEventListener(this.providerId);
1967
+ }
1968
+
1969
+ isTurning() {
1970
+ if (!this.lastEvent || !RelativeAttitude$1.lastEvent) {
1971
+ return false;
1972
+ }
1973
+
1974
+ const diffTime = RelativeAttitude$1.lastEvent.data.time - this.lastEvent.data.timestamp;
1975
+ return diffTime < TurnDetector.CONSIDER_TURN_UNTIL;
1976
+ }
1977
+
1978
+ /**
1979
+ * @private
1980
+ */
1981
+ _parseRelativeAttitude = relativeAttitudeEvent => {
1982
+
1983
+ const { heading, time: timestamp } = relativeAttitudeEvent.data;
1984
+
1985
+ this.slidingWindow = this.slidingWindow.filter(item => item[0] >= timestamp - TurnDetector.SLIDING_WINDOW_TIME);
1986
+ this.slidingWindow.push([timestamp, heading]);
1987
+
1988
+ const stdVal = std(this.slidingWindow.map(item => item[1]));
1989
+ if (stdVal > TurnDetector.STD_THRESHOLD) {
1990
+ this.notify(this.createEvent(EventType.Turn, { timestamp }, [relativeAttitudeEvent]));
1991
+ }
1992
+
1993
+ }
1994
+ }
1995
+
1996
+ var TurnDectector = new TurnDetector();
1997
+
1998
+ const Constants = {
1999
+ DEFAULT_ALTITUDE: 1.6
2000
+ };
2001
+
2002
+ class StepDetectionMinMaxPeaks2 {
2003
+
2004
+ // in seconds
2005
+ static WINDOW_TIME = 0.3;
2006
+
2007
+ // in seconds
2008
+ static MIN_TIME_BETWEEN_STEPS = 0.4;
2009
+
2010
+ // in Hz
2011
+ static MAX_FRENQUENCY = 4;
2012
+ static MIN_FRENQUENCY = 1;
2013
+
2014
+ // in m.s-2
2015
+ static VERTICAL_ACC_POSITIVE_PEAK_THRESHOLD = 0.75;
2016
+ static VERTICAL_ACC_NEGATIVE_PEAK_THRESHOLD = -0.3;
2017
+
2018
+
2019
+ constructor() {
2020
+ this.slidingWindow = [];
2021
+
2022
+ this.lastStepTimestamp = -StepDetectionMinMaxPeaks2.MIN_TIME_BETWEEN_STEPS;
2023
+ this.previousVerticalAcc = 0;
2024
+ this.influence = 0.2;
2025
+ }
2026
+
2027
+
2028
+ compute(timestamp, linearAcc, angularRate) {
2029
+
2030
+ const verticalAcc = this.influence * (linearAcc[2] * 2) + (1 - this.influence) * this.previousVerticalAcc;
2031
+ this.previousVerticalAcc = verticalAcc;
2032
+
2033
+
2034
+ if (Math.sqrt(angularRate[0] ** 2 + angularRate[1] ** 2 + angularRate[2] ** 2) > 0.75) {
2035
+ return false;
2036
+ }
2037
+
2038
+ if (this.lastStepTimestamp && this.lastStepTimestamp + StepDetectionMinMaxPeaks2.MIN_TIME_BETWEEN_STEPS > timestamp) {
2039
+ return false;
2040
+ }
2041
+
2042
+ let maxValue = Number.MIN_SAFE_INTEGER;
2043
+ let minValue = Number.MAX_SAFE_INTEGER;
2044
+
2045
+ this.slidingWindow.forEach(function(item, index, object) {
2046
+ if (item.timestamp < timestamp - StepDetectionMinMaxPeaks2.WINDOW_TIME) {
2047
+ object.splice(index, 1);
2048
+ } else {
2049
+ maxValue = Math.max(item.verticalAcc, maxValue);
2050
+ minValue = Math.min(item.verticalAcc, minValue);
2051
+ }
2052
+ });
2053
+ this.slidingWindow.push({
2054
+ timestamp: timestamp,
2055
+ verticalAcc: verticalAcc
2056
+ });
2057
+
2058
+
2059
+ if (maxValue > StepDetectionMinMaxPeaks2.VERTICAL_ACC_POSITIVE_PEAK_THRESHOLD
2060
+ && minValue < StepDetectionMinMaxPeaks2.VERTICAL_ACC_NEGATIVE_PEAK_THRESHOLD) {
2061
+
2062
+ const timeInterval = this.lastStepTimestamp ? timestamp - this.lastStepTimestamp : 1;
2063
+ this.frequency = Math.min(Math.max((1 / timeInterval), StepDetectionMinMaxPeaks2.MIN_FRENQUENCY), StepDetectionMinMaxPeaks2.MAX_FRENQUENCY);
2064
+
2065
+ this.lastStepTimestamp = timestamp;
2066
+ return true;
2067
+ }
2068
+
2069
+ return false;
2070
+ }
2071
+
2072
+ get lastStepSize() {
2073
+
2074
+ if (!this.frequency) {
2075
+ return 0;
2076
+ }
2077
+
2078
+ const kParamA = 0.45;
2079
+ const kParamB = 0.2;
2080
+ return kParamA + kParamB * this.frequency;
2081
+ }
2082
+
2083
+ get speed() {
2084
+ return this.lastStepSize && this.frequency ? this.lastStepSize * this.frequency : 0;
2085
+ }
2086
+
2087
+ mean(data) {
2088
+ let sum = 0.0, mean = 0.0;
2089
+
2090
+ for (let i = 0; i < data.length; ++i) {
2091
+ sum += data[i].verticalAcc;
2092
+ }
2093
+ mean = sum / data.length;
2094
+ return mean;
2095
+ }
2096
+
2097
+ stddev(data) {
2098
+ const theMean = this.mean(data);
2099
+ let standardDeviation = 0;
2100
+ for (let i = 0; i < data.length; ++i) {
2101
+ standardDeviation += (data[i].verticalAcc - theMean) ** 2;
2102
+ }
2103
+
2104
+ return Math.sqrt(standardDeviation / data.length);
2105
+ }
2106
+ }
2107
+
2108
+ class StepDetector extends Provider {
2109
+
2110
+ constructor() {
2111
+ super();
2112
+ this.stepDetector = new StepDetectionMinMaxPeaks2();
2113
+ }
2114
+
2115
+ /**
2116
+ * @override
2117
+ */
2118
+ static get pname() {
2119
+ return 'StepDetector';
2120
+ }
2121
+
2122
+ /**
2123
+ * @override
2124
+ */
2125
+ get _availability() {
2126
+ return Promise.all([
2127
+ Accelerometer$1.availability,
2128
+ Gyroscope$1.availability,
2129
+ RelativeAttitudeFromInertial$1.availability
2130
+ ]);
2131
+ }
2132
+
2133
+ /**
2134
+ * @override
2135
+ */
2136
+ start() {
2137
+
2138
+ this.numOfSteps = 0;
2139
+
2140
+ this.accelerometerProviderId = Accelerometer$1.addEventListener(
2141
+ events => this.onAccelerometerEvent(events[0]),
2142
+ error => this.notifyError(error)
2143
+ );
2144
+
2145
+ this.gyroscopeProviderId = Gyroscope$1.addEventListener(
2146
+ events => (this.angularRateEvent = events[0]),
2147
+ error => this.notifyError(error)
2148
+ );
2149
+
2150
+ this.attitudeProviderId = RelativeAttitudeFromInertial$1.addEventListener(
2151
+ events => (this.attitudeEvent = events[0]),
2152
+ error => this.notifyError(error)
2153
+ );
2154
+ }
2155
+
2156
+ /**
2157
+ * @override
2158
+ */
2159
+ stop() {
2160
+ Accelerometer$1.removeEventListener(this.accelerometerProviderId);
2161
+ Gyroscope$1.removeEventListener(this.gyroscopeProviderId);
2162
+ RelativeAttitudeFromInertial$1.removeEventListener(this.attitudeProviderId);
2163
+ }
2164
+
2165
+ onAccelerometerEvent(accelerationEvent) {
2166
+
2167
+ if (!this.attitudeEvent || !this.angularRateEvent) {
2168
+ return;
2169
+ }
2170
+
2171
+ const {
2172
+ values: acceleration, timestamp
2173
+ } = accelerationEvent.data;
2174
+
2175
+ /**
2176
+ * Step Detection and Step Size Detection
2177
+ */
2178
+ const linearAcc = this.constructor.computeLinearAcceleration(
2179
+ this.attitudeEvent.data.quaternion, acceleration);
2180
+ const stepDetected = this.stepDetector.compute(timestamp, linearAcc, this.angularRateEvent.data.values);
2181
+
2182
+ if (stepDetected) {
2183
+ const size = this.stepDetector.lastStepSize;
2184
+ this.numOfSteps++;
2185
+ this.notify(this.createEvent(
2186
+ EventType.Step, {
2187
+ size,
2188
+ number: this.numOfSteps
2189
+ },
2190
+ [accelerationEvent, this.angularRateEvent, this.attitudeEvent]
2191
+ ));
2192
+ }
2193
+ }
2194
+
2195
+ // Linear acceleration in ENU
2196
+ static computeLinearAcceleration(quaternion, acc) {
2197
+ const linearAcc = Quaternion.rotate(Quaternion.inverse(quaternion), acc);
2198
+ linearAcc[2] -= Constants$1.EARTH_GRAVITY;
2199
+ return linearAcc;
2200
+ }
2201
+ }
2202
+
2203
+ var StepDetector$1 = new StepDetector();
2204
+
2205
+ class StraightLineDetector extends Provider {
2206
+
2207
+ /** @type {number} */
2208
+ static STEPS_CONSIDERED_FOR_STRAIGHT_LINE = 2;
2209
+
2210
+ /** @type {number?} */
2211
+ _turnDetectorProviderId = null;
2212
+
2213
+ /** @type {number?} */
2214
+ _stepDetectorProviderId = null;
2215
+
2216
+ /** @type {number} */
2217
+ _countSteps = 0;
2218
+
2219
+ /**
2220
+ * @override
2221
+ */
2222
+ static get pname() {
2223
+ return 'StraightLineDetector';
2224
+ }
2225
+
2226
+ /**
2227
+ * @override
2228
+ */
2229
+ start() {
2230
+ this._turnDetectorProviderId = TurnDectector.addEventListener(this._onTurn);
2231
+ this._stepDetectorProviderId = StepDetector$1.addEventListener(this._onStep);
2232
+ }
2233
+
2234
+ /**
2235
+ * @override
2236
+ */
2237
+ stop() {
2238
+ TurnDectector.removeEventListener(this._turnDetectorProviderId);
2239
+ StepDetector$1.removeEventListener(this._stepDetectorProviderId);
2240
+ }
2241
+
2242
+ _onTurn = (event) => {
2243
+ if (this._countSteps >= StraightLineDetector.STEPS_CONSIDERED_FOR_STRAIGHT_LINE) {
2244
+
2245
+ const fromEvents = [event];
2246
+ if (StepDetector$1.lastEvent !== null) {
2247
+ fromEvents.push(StepDetector$1.lastEvent);
2248
+ }
2249
+
2250
+ this.notify(this.createEvent(EventType.StraightLine, false, fromEvents));
2251
+
2252
+ }
2253
+ this._countSteps = 0;
2254
+ }
2255
+
2256
+ _onStep = (event) => {
2257
+ this._countSteps++;
2258
+
2259
+ if (this._countSteps === StraightLineDetector.STEPS_CONSIDERED_FOR_STRAIGHT_LINE) {
2260
+
2261
+ const fromEvents = [event];
2262
+ if (TurnDectector.lastEvent !== null) {
2263
+ fromEvents.push(TurnDectector.lastEvent);
2264
+ }
2265
+ this.notify(this.createEvent(EventType.StraightLine, true, fromEvents));
2266
+
2267
+ }
2268
+
2269
+ }
2270
+
2271
+ /**
2272
+ * @returns {boolean}
2273
+ */
2274
+ isStraight() {
2275
+ return this._countSteps >= StraightLineDetector.STEPS_CONSIDERED_FOR_STRAIGHT_LINE;
2276
+ }
2277
+
2278
+ get numStepsDetectedFromLastTurn() {
2279
+ return this._countSteps;
2280
+ }
2281
+ }
2282
+
2283
+ var StraightLineDetector$1 = new StraightLineDetector();
2284
+
2285
+ /* eslint-disable max-statements */
2286
+
2287
+ class MapMatchingHandler extends Provider {
2288
+
2289
+ /** @type {number} in radians */
2290
+ static MM_MAX_ANGLE = deg2rad(30);
2291
+
2292
+ /** @type {number} in meters */
2293
+ static MM_MAX_DIST = 30;
2294
+
2295
+ /** @type {number} in meters */
2296
+ static MM_MIN_DIST = 0;
2297
+
2298
+ /** @type {boolean} */
2299
+ static ORIENTATION_MATCHING = true;
2300
+
2301
+ /** @type {boolean} */
2302
+ static USE_ITINERARY_START_AS_POSITION = true;
2303
+
2304
+ /** @type {number} in meters */
2305
+ static MM_HUGE_JUMP_DISTANCE = 3;
2306
+
2307
+ /** @type {number} */
2308
+ static MIN_STEPS_BETWEEN_ORIENTATION_MATCHING = 3;
2309
+
2310
+ /** @type {number} */
2311
+ static MIN_STEPS_FOR_ORIENTATION_MATCHING = 5;
2312
+
2313
+ /** @type {number} */
2314
+ static LAST_PROJECTIONS_WINDOW_SIZE = 3;
2315
+
2316
+ /** @type {number} */
2317
+ static LAST_PROJECTIONS_EDGE_ANGLE_THRESHOLD = deg2rad(3);
2318
+
2319
+ /** @type {MapMatching} */
2320
+ _mapMatching;
2321
+
2322
+ /** @type {number} */
2323
+ _mapMatchingMinDistance;
2324
+
2325
+ /** @type {boolean} */
2326
+ _internalProvidersStarted = false;
2327
+
2328
+ /** @type {number?} */
2329
+ _straightLineDetectorProviderId;
2330
+
2331
+ /** @type {number?} */
2332
+ _turnDetectorProviderId;
2333
+
2334
+ /** @type {number?} */
2335
+ _stepDetectorProviderId;
2336
+
2337
+ /** @type {GraphProjection[]} */
2338
+ _projectionsWithAbsAndWithoutRelAttitudeInARow = [];
2339
+
2340
+ /** @type {number} */
2341
+ _countStepsFromLastMatching = 0;
2342
+
2343
+ /** @type {GraphProjection[]} */
2344
+ _lastProjections = [];
2345
+
2346
+
2347
+ constructor() {
2348
+ super();
2349
+
2350
+ this._mapMatching = new MapMatching();
2351
+ this._mapMatching.maxDistance = MapMatchingHandler.MM_MAX_DIST;
2352
+ this._mapMatching.maxAngleBearing = MapMatchingHandler.MM_MAX_ANGLE;
2353
+ this._mapMatchingMinDistance = MapMatchingHandler.MM_MIN_DIST;
2354
+ }
2355
+
2356
+ /**
2357
+ * @override
2358
+ */
2359
+ start() {
2360
+ if (this.network) {
2361
+ this._startInternalProviders();
2362
+ }
2363
+ }
2364
+
2365
+ /**
2366
+ * @override
2367
+ */
2368
+ stop() {
2369
+ this._stopInternalProviders();
2370
+ }
2371
+
2372
+ _manageStartStop = () => {
2373
+ if (this.network && !this._internalProvidersStarted) {
2374
+ this._startInternalProviders();
2375
+ } else if (!this.network && this._internalProvidersStarted) {
2376
+ this._stopInternalProviders();
2377
+ }
2378
+ }
2379
+
2380
+ _startInternalProviders() {
2381
+
2382
+ if (this.enabled && this._internalProvidersStarted) {
2383
+ return;
2384
+ }
2385
+
2386
+ this._straightLineDetectorProviderId = StraightLineDetector$1.addEventListener();
2387
+ this._turnDetectorProviderId = TurnDectector.addEventListener();
2388
+ this._stepDetectorProviderId = StepDetector$1.addEventListener(() => (this._countStepsFromLastMatching++));
2389
+
2390
+ this._internalProvidersStarted = true;
2391
+ }
2392
+
2393
+ _stopInternalProviders() {
2394
+
2395
+ if (this.enabled && !this._internalProvidersStarted) {
2396
+ return;
2397
+ }
2398
+
2399
+ StraightLineDetector$1.removeEventListener(this._straightLineDetectorProviderId);
2400
+ TurnDectector.removeEventListener(this._turnDetectorProviderId);
2401
+ StepDetector$1.removeEventListener(this._stepDetectorProviderId);
2402
+
2403
+ this._internalProvidersStarted = false;
2404
+ }
2405
+
2406
+ get enabled() {
2407
+ return ProvidersOptions.useMapMatching;
2408
+ }
2409
+
2410
+ /** @type {?Network} */
2411
+ get network() {
2412
+ return this._mapMatching.network;
2413
+ }
2414
+
2415
+ /** @type {?Network} */
2416
+ set network(network) {
2417
+
2418
+ this._mapMatching.network = network;
2419
+ this.notify(this.createEvent(EventType.Network, network));
2420
+
2421
+ this._manageStartStop();
2422
+
2423
+ if (this.canUseMapMatching()) ;
2424
+ }
2425
+
2426
+ /** @type {?Itinerary} */
2427
+ set itinerary(itinerary) {
2428
+
2429
+ this._mapMatching.network = itinerary ? itinerary.toNetwork() : null;
2430
+ this.notify(this.createEvent(EventType.Itinerary, itinerary));
2431
+
2432
+ this._manageStartStop();
2433
+
2434
+ if (this.canUseMapMatching()) {
2435
+ this._notifyPositionFromItineraryInput(itinerary);
2436
+ }
2437
+ }
2438
+
2439
+ /**
2440
+ * @returns {boolean}
2441
+ */
2442
+ canUseMapMatching() {
2443
+ return this.enabled && this.network;
2444
+ }
2445
+
2446
+ /**
2447
+ * @param {Itinerary} itinerary
2448
+ */
2449
+ _notifyPositionFromItineraryInput(itinerary) {
2450
+
2451
+ if (!MapMatchingHandler.USE_ITINERARY_START_AS_POSITION || itinerary.start) {
2452
+ return;
2453
+ }
2454
+
2455
+ const lastEvent = AbsolutePosition$1.lastEvent;
2456
+ const lastPosition = lastEvent ? lastEvent.data : null;
2457
+
2458
+ const notify = (pos) => {
2459
+ const newEvent = lastEvent
2460
+ ? lastEvent.clone()
2461
+ : new ProviderEvent(EventType.AbsolutePosition);
2462
+ newEvent.data = pos;
2463
+ AbsolutePosition$1.notify(newEvent);
2464
+ };
2465
+
2466
+ // In case of an itinerary, use itinerary start as new position,
2467
+ // but add the distance between lastPosition and itinerary start for the accuracy
2468
+ const newPosition = UserPosition.fromCoordinates(itinerary.from);
2469
+
2470
+ if (lastPosition) {
2471
+ newPosition.alt = lastPosition.alt;
2472
+ newPosition.time = lastPosition.time;
2473
+ newPosition.accuracy = lastPosition.accuracy + newPosition.distanceTo(lastPosition);
2474
+ newPosition.bearing = lastPosition.bearing;
2475
+ } else if (itinerary.coords >= 2) {
2476
+ newPosition.alt = Constants.DEFAULT_ALTITUDE;
2477
+ newPosition.time = TimeUtils.preciseTime();
2478
+ newPosition.accuracy = 0;
2479
+ newPosition.bearing = itinerary.coords[0].bearingTo(itinerary.coords[1]);
2480
+ } else {
2481
+ return;
2482
+ }
2483
+
2484
+ // if the distance between itinerary.start and itinerary.nodes[0] is less than MM_MAX_DIST,
2485
+ // projection.projection is itinerary.nodes[0]
2486
+ const projection = this.getProjection(newPosition, true);
2487
+ if (projection) {
2488
+ notify(projection.projection);
2489
+ // Do not notify for attitude projection because bearing has not been used.
2490
+ } else {
2491
+ // This means the first position is far from the network and the user has
2492
+ // to reach itinerary.nodes[0] before to continue
2493
+ notify(newPosition);
2494
+ }
2495
+ return;
2496
+ }
2497
+
2498
+ /**
2499
+ * @param {ProviderEvent<UserPosition>} newPositionEvent
2500
+ */
2501
+ notifyPositionFromFeed(newPositionEvent) {
2502
+
2503
+ const projection = this.getProjection(newPositionEvent.data, true, false);
2504
+ if (!projection) {
2505
+ AbsolutePosition$1.notify(newPositionEvent);
2506
+ return;
2507
+ }
2508
+
2509
+ AbsolutePosition$1.notify(this.createEvent(
2510
+ EventType.AbsolutePosition,
2511
+ projection.projection,
2512
+ [newPositionEvent]
2513
+ ));
2514
+ }
2515
+
2516
+
2517
+ /**
2518
+ * @param {ProviderEvent<UserPosition>} positionEvent
2519
+ */
2520
+ notifyPositionFromAbsolute(positionEvent) {
2521
+
2522
+ const newPosition = positionEvent.data;
2523
+
2524
+ let projectionWithBearing = null;
2525
+ if (newPosition.bearing !== null) {
2526
+ projectionWithBearing = this.getProjection(newPosition, true, true);
2527
+ }
2528
+
2529
+ if (!projectionWithBearing) {
2530
+ // Verify if the newPosition is far enough from the network to be used by the system.
2531
+ const projectionWithoutBearing = this.getProjection(newPosition, true, false);
2532
+ if (!projectionWithoutBearing) {
2533
+ // In this case, the newPosition is far enough and can be used safely.
2534
+ AbsolutePosition$1.notify(positionEvent);
2535
+ }
2536
+ return;
2537
+ }
2538
+
2539
+ // newPosition must not be used after this line
2540
+
2541
+ const thisWillBeAHugeJump = projectionWithBearing.distanceFromNearestElement > MapMatchingHandler.MM_HUGE_JUMP_DISTANCE;
2542
+
2543
+ // In case of a huge jump, be sure the user is in a straight line
2544
+ if (thisWillBeAHugeJump && !StraightLineDetector$1.isStraight()) {
2545
+ return;
2546
+ }
2547
+
2548
+ // Detector to avoid big jumps in the wrong direction
2549
+ if (thisWillBeAHugeJump && this._detectWrongBigJump(projectionWithBearing)) {
2550
+ return;
2551
+ }
2552
+
2553
+ AbsolutePosition$1.notify(this.createEvent(
2554
+ EventType.AbsolutePosition,
2555
+ projectionWithBearing.projection,
2556
+ [positionEvent]
2557
+ ));
2558
+
2559
+ this.tryOrientationMatching(projectionWithBearing);
2560
+
2561
+ }
2562
+
2563
+
2564
+ /**
2565
+ * @param {ProviderEvent<UserPosition>} positionEvent
2566
+ */
2567
+ notifyPositionFromRelative(positionEvent) {
2568
+
2569
+ if (TurnDectector.isTurning()) {
2570
+ this._projectionsWithAbsAndWithoutRelAttitudeInARow = [];
2571
+ this._lastProjections = [];
2572
+ AbsolutePosition$1.notify(positionEvent);
2573
+ return;
2574
+ }
2575
+
2576
+ const newPosition = positionEvent.data;
2577
+ const projection = this.getProjection(newPosition, true, true);
2578
+
2579
+ if (projection) {
2580
+ this._projectionsWithAbsAndWithoutRelAttitudeInARow = [];
2581
+ this._lastProjections.push(projection);
2582
+ if (this._lastProjections.length > MapMatchingHandler.LAST_PROJECTIONS_WINDOW_SIZE) {
2583
+ this._lastProjections.shift();
2584
+ }
2585
+
2586
+ const thisWillBeAHugeJump = projection.distanceFromNearestElement > MapMatchingHandler.MM_HUGE_JUMP_DISTANCE;
2587
+
2588
+ // In case of a huge jump, be sure the user is in a straight line
2589
+ if (thisWillBeAHugeJump && !StraightLineDetector$1.isStraight()) {
2590
+ AbsolutePosition$1.notify(positionEvent);
2591
+ return;
2592
+ }
2593
+
2594
+ // Detector to avoid big jumps in the wrong direction
2595
+ if (thisWillBeAHugeJump && this._detectWrongBigJump(projection)) {
2596
+ AbsolutePosition$1.notify(positionEvent);
2597
+ return;
2598
+ }
2599
+
2600
+ // Do not use projection if the neareast element is not the same direction than previous
2601
+ if (!this._areLastProjectionsInTheSameDirection()) {
2602
+ AbsolutePosition$1.notify(positionEvent);
2603
+ return;
2604
+ }
2605
+
2606
+ AbsolutePosition$1.notify(this.createEvent(
2607
+ EventType.AbsolutePosition,
2608
+ projection.projection,
2609
+ [positionEvent]
2610
+ ));
2611
+ this.tryOrientationMatching(projection);
2612
+
2613
+ } else {
2614
+
2615
+ // Sometimes, the newPosition.bearing diverges due to the Absolute Attitude offset
2616
+ // and the Orientation Matching. So, we created this detector to check if projection
2617
+ // with bearing from "AbsoluteAttitudeFromBrowser" is better than the current bearing.
2618
+ // /!\ This works only if the user is waking in the same direction than the smartphone orientation /!\
2619
+ if (StraightLineDetector$1.isStraight()) {
2620
+
2621
+ const testedPosition = newPosition.clone();
2622
+ testedPosition.bearing = AbsoluteAttitudeFromBrowser$1.lastEvent.data.heading;
2623
+ const projectionWithAbs = this.getProjection(testedPosition, true, true);
2624
+
2625
+ if (projectionWithAbs) {
2626
+
2627
+ this._projectionsWithAbsAndWithoutRelAttitudeInARow.push(projectionWithAbs);
2628
+ if (this._projectionsWithAbsAndWithoutRelAttitudeInARow.length < 3) {
2629
+ AbsolutePosition$1.notify(positionEvent);
2630
+ return;
2631
+ }
2632
+
2633
+ this._lastProjections.push(projectionWithAbs);
2634
+ if (this._lastProjections.length > MapMatchingHandler.LAST_PROJECTIONS_WINDOW_SIZE) {
2635
+ this._lastProjections.shift();
2636
+ }
2637
+
2638
+
2639
+ if (!this._areLastProjectionsInTheSameDirection()) {
2640
+ AbsolutePosition$1.notify(positionEvent);
2641
+ return;
2642
+ }
2643
+
2644
+ AbsolutePosition$1.notify(this.createEvent(
2645
+ EventType.AbsolutePosition,
2646
+ projectionWithAbs.projection,
2647
+ [positionEvent]
2648
+ ));
2649
+
2650
+ this.tryOrientationMatching(projectionWithAbs);
2651
+ return;
2652
+ }
2653
+ }
2654
+
2655
+ this._projectionsWithAbsAndWithoutRelAttitudeInARow = [];
2656
+ this._lastProjections = [];
2657
+
2658
+ // If no projection found with both projection methods, simply use the newPosition.
2659
+ AbsolutePosition$1.notify(positionEvent);
2660
+ }
2661
+
2662
+ }
2663
+
2664
+ /**
2665
+ * @param {Projection} projection
2666
+ */
2667
+ _detectWrongBigJump(projection) {
2668
+
2669
+ if (this.network instanceof Itinerary && AbsolutePosition$1.lastEvent) {
2670
+ const itinerary = this.network;
2671
+ const infoPrevious = itinerary.getInfo(AbsolutePosition$1.lastEvent.data);
2672
+ const infoProjection = itinerary.getInfo(projection.projection);
2673
+ if (infoPrevious
2674
+ && infoProjection
2675
+ && infoPrevious.traveledDistance > infoProjection.traveledDistance
2676
+ && (infoPrevious.traveledDistance - infoProjection.traveledDistance) > projection.origin.accuracy
2677
+ && projection.distanceFromNearestElement > projection.origin.accuracy
2678
+ && projection.origin.distanceTo(AbsolutePosition$1.lastEvent.data) < projection.origin.accuracy + AbsolutePosition$1.lastEvent.data.accuracy) {
2679
+
2680
+ return true;
2681
+ }
2682
+ }
2683
+
2684
+ return false;
2685
+ }
2686
+
2687
+ _areLastProjectionsInTheSameDirection() {
2688
+
2689
+ if (this._lastProjections.length === 0) {
2690
+ return false;
2691
+ }
2692
+
2693
+ const firstProjection = this._lastProjections[0];
2694
+ return !this._lastProjections.some(projection =>
2695
+ !(projection.nearestElement instanceof GraphEdge)
2696
+ || (diffAngleLines(projection.nearestElement.bearing, firstProjection.nearestElement.bearing)
2697
+ > MapMatchingHandler.LAST_PROJECTIONS_EDGE_ANGLE_THRESHOLD)
2698
+ );
2699
+ }
2700
+
2701
+ /**
2702
+ * @param {Projection} projection
2703
+ */
2704
+ tryOrientationMatching(projection) {
2705
+
2706
+ if (!MapMatchingHandler.ORIENTATION_MATCHING) {
2707
+ return;
2708
+ }
2709
+
2710
+ if (this.state !== ProviderState.STARTED
2711
+ || this._countStepsFromLastMatching < MapMatchingHandler.MIN_STEPS_BETWEEN_ORIENTATION_MATCHING
2712
+ || StraightLineDetector$1.numStepsDetectedFromLastTurn < MapMatchingHandler.MIN_STEPS_FOR_ORIENTATION_MATCHING) {
2713
+ return;
2714
+ }
2715
+
2716
+ const { nearestElement, origin } = projection;
2717
+ if (!(nearestElement instanceof GraphEdge)) {
2718
+ return;
2719
+ }
2720
+
2721
+ let matchingDirection;
2722
+ const matchingDirectionAngle1 = diffAngle(nearestElement.bearing, origin.bearing);
2723
+ const matchingDirectionAngle2 = diffAngle(nearestElement.bearing + Math.PI, origin.bearing);
2724
+
2725
+ if (Math.abs(matchingDirectionAngle1) < Math.abs(matchingDirectionAngle2)) {
2726
+ matchingDirection = nearestElement.bearing;
2727
+ } else {
2728
+ matchingDirection = (nearestElement.bearing + Math.PI) % (2 * Math.PI);
2729
+ }
2730
+
2731
+ const matchedHeading = new AbsoluteHeading(
2732
+ matchingDirection,
2733
+ origin.time,
2734
+ 0
2735
+ // Math.min(Math.abs(matchingDirectionAngle1), Math.abs(matchingDirectionAngle2))
2736
+ );
2737
+
2738
+ AbsoluteAttitude$1._forceHeadingForRelative(
2739
+ new ProviderEvent(EventType.AbsoluteHeading, matchedHeading)
2740
+ );
2741
+
2742
+ this._countStepsFromLastMatching = 0;
2743
+ }
2744
+
2745
+ getProjection(position, useDistance, useBearing) {
2746
+ return this._mapMatching.getProjection(position, useDistance, useBearing);
2747
+ }
2748
+
2749
+ get maxDistance() {
2750
+ return this._mapMatching.maxDistance;
2751
+ }
2752
+
2753
+ set maxDistance(maxDistance) {
2754
+ this._mapMatching.maxDistance = maxDistance;
2755
+ }
2756
+
2757
+ get minDistance() {
2758
+ return this._mapMatchingMinDistance;
2759
+ }
2760
+
2761
+ set minDistance(minDistance) {
2762
+ this._mapMatchingMinDistance = minDistance;
2763
+ }
2764
+
2765
+ get maxAngleBearing() {
2766
+ return this._mapMatching.maxAngleBearing;
2767
+ }
2768
+
2769
+ set maxAngleBearing(maxAngleBearing) {
2770
+ this._mapMatching.maxAngleBearing = maxAngleBearing;
2771
+ }
2772
+ }
2773
+
2774
+ var MapMatchingHandler$1 = new MapMatchingHandler();
2775
+
2776
+ class GeolocationApiMissingError extends Error {
2777
+
2778
+ static DEFAULT_MESSAGE = 'Geolocation api is missing';
2779
+
2780
+ constructor(message) {
2781
+ super(message || GeolocationApiMissingError.DEFAULT_MESSAGE);
2782
+ }
2783
+ }
2784
+
2785
+ class GeolocationPermissionDeniedError extends Error {
2786
+
2787
+ static DEFAULT_MESSAGE = 'Geolocation permission denied';
2788
+
2789
+ constructor(message) {
2790
+ super(message || GeolocationPermissionDeniedError.DEFAULT_MESSAGE);
2791
+ }
2792
+ }
2793
+
2794
+ class GeolocationPositionUnavailableError extends Error {
2795
+
2796
+ static DEFAULT_MESSAGE = 'Geolocation position unavailable';
2797
+
2798
+ constructor(message) {
2799
+ super(message || GeolocationPositionUnavailableError.DEFAULT_MESSAGE);
2800
+ }
2801
+ }
2802
+
2803
+ /**
2804
+ * GnssWifiProvider is a provider based on navigator.geolocation.
2805
+ * This API does not allow us to know if the position returned is provided
2806
+ * by Wifi Fingerprinting algorithms or by GNSS. That is why the name is
2807
+ * "GnssWifi".
2808
+ */
2809
+ class GnssWifi extends Provider {
2810
+
2811
+ static POSITION_OPTIONS = {
2812
+ enableHighAccuracy: true,
2813
+ timeout: Infinity,
2814
+ maximumAge: 0
2815
+ };
2816
+
2817
+ /**
2818
+ * @override
2819
+ */
2820
+ static get pname() {
2821
+ return 'GnssWifi';
2822
+ }
2823
+
2824
+ /**
2825
+ * @override
2826
+ */
2827
+ static get eventsType() {
2828
+ return [EventType.AbsolutePosition];
2829
+ }
2830
+
2831
+ /**
2832
+ * @override
2833
+ */
2834
+ get _availability() {
2835
+ return typeof (navigator) === 'object' && navigator.geolocation
2836
+ ? Promise.resolve()
2837
+ : Promise.reject(new GeolocationApiMissingError());
2838
+ }
2839
+
2840
+ /**
2841
+ * @override
2842
+ */
2843
+ start() {
2844
+
2845
+ this.geoLocationId = navigator.geolocation.watchPosition(
2846
+ this.onNewPosition,
2847
+ this.onPositionError,
2848
+ GnssWifi.POSITION_OPTIONS
2849
+ );
2850
+
2851
+ }
2852
+
2853
+ /**
2854
+ * @override
2855
+ */
2856
+ stop() {
2857
+
2858
+ navigator.geolocation.clearWatch(this.geoLocationId);
2859
+
2860
+ }
2861
+
2862
+ /**
2863
+ * @private
2864
+ */
2865
+ onNewPosition = geolocation => {
2866
+
2867
+ const { coords } = geolocation;
2868
+ if (!coords) {
2869
+ return;
2870
+ }
2871
+
2872
+ let bearing;
2873
+ if (coords.heading) {
2874
+ bearing = deg2rad(coords.heading);
2875
+ }
2876
+
2877
+ const timestamp = TimeUtils.unixTimestampToPreciseTime(geolocation.timestamp) / 1e3;
2878
+
2879
+ const position = new UserPosition(
2880
+ coords.latitude,
2881
+ coords.longitude,
2882
+ Constants.DEFAULT_ALTITUDE,
2883
+ null,
2884
+ timestamp,
2885
+ coords.accuracy,
2886
+ bearing,
2887
+ this.pname);
2888
+
2889
+ this.notify(this.createEvent(
2890
+ EventType.AbsolutePosition, position
2891
+ ));
2892
+
2893
+ };
2894
+
2895
+ onPositionError = error => {
2896
+
2897
+ Logger.warn(`[Providers] watchPosition error: [${error.code}] ${error.message}`);
2898
+
2899
+ let customError;
2900
+ switch (error.code) {
2901
+ case 1:
2902
+ customError = new GeolocationPermissionDeniedError(error.message);
2903
+ break;
2904
+ case 2:
2905
+ customError = new GeolocationPositionUnavailableError(error.message);
2906
+ break;
2907
+ default:
2908
+ customError = new Error(error.message);
2909
+ }
2910
+
2911
+ this.notifyError(customError);
2912
+ };
2913
+ }
2914
+
2915
+ var GnssWifi$1 = new GnssWifi();
2916
+
2917
+ class GeoRelativePositionFromArCore extends Provider {
2918
+
2919
+ /**
2920
+ * @override
2921
+ */
2922
+ static get pname() {
2923
+ return 'GeoRelativePositionFromArCore';
2924
+ }
2925
+
2926
+ /**
2927
+ * @override
2928
+ */
2929
+ get _availability() {
2930
+ return ArCore$1.availability;
2931
+ }
2932
+
2933
+ /**
2934
+ * @override
2935
+ */
2936
+ start() {
2937
+
2938
+ this.arCoreProviderId = ArCore$1.addEventListener(
2939
+ this.onArCoreEvents,
2940
+ error => this.notifyError(error)
2941
+ );
2942
+
2943
+ this.absoluteAttitudeProviderId = AbsoluteAttitude$1.addEventListener(
2944
+ events => (this.absoluteAttitudeEvent = events[0]),
2945
+ error => this.notifyError(error)
2946
+ );
2947
+ }
2948
+
2949
+ /**
2950
+ * @override
2951
+ */
2952
+ stop() {
2953
+ ArCore$1.removeEventListener(this.arCoreProviderId);
2954
+ AbsoluteAttitude$1.removeEventListener(this.absoluteAttitudeProviderId);
2955
+ }
2956
+
2957
+ onArCoreEvents = events => {
2958
+ const relativeAttitudeEvent = events.find(event => event.dataType === EventType.RelativeAttitude);
2959
+ const relativePositionEvent = events.find(event => event.dataType === EventType.RelativePosition);
2960
+
2961
+ if (relativeAttitudeEvent && relativePositionEvent && this.absoluteAttitudeEvent) {
2962
+ this.compute(relativePositionEvent, relativeAttitudeEvent, this.absoluteAttitudeEvent);
2963
+ }
2964
+ }
2965
+
2966
+ compute(relativePositionEvent, relativeAttitudeEvent, absoluteAttitudeEvent) {
2967
+ const relativePosition = relativePositionEvent.data;
2968
+ const relativeAttitude = relativeAttitudeEvent.data;
2969
+ const absoluteAttitude = absoluteAttitudeEvent.data;
2970
+
2971
+ const rotation = absoluteAttitude.heading - relativeAttitude.heading;
2972
+
2973
+ /**
2974
+ * Here, we transform "relativePosition" which is defined in ArCore frame to a relative "position" defined in ENU frame
2975
+ */
2976
+ const east = Math.cos(rotation) * relativePosition.x - Math.sin(rotation) * relativePosition.z;
2977
+ const north = -Math.sin(rotation) * relativePosition.x - Math.cos(rotation) * relativePosition.z;
2978
+ const up = relativePosition.y;
2979
+
2980
+ /**
2981
+ * Relative position is defined in ENU frame
2982
+ */
2983
+ const position = new GeoRelativePosition$1(
2984
+ east,
2985
+ north,
2986
+ up,
2987
+ relativePosition.time,
2988
+ 0,
2989
+ absoluteAttitude.heading
2990
+ );
2991
+
2992
+ this.notify(this.createEvent(
2993
+ EventType.GeoRelativePosition,
2994
+ position,
2995
+ [relativePositionEvent, relativeAttitudeEvent, absoluteAttitudeEvent]
2996
+ ));
2997
+ }
2998
+ }
2999
+
3000
+ var GeoRelativePositionFromArCore$1 = new GeoRelativePositionFromArCore();
3001
+
3002
+ class Pdr extends Provider {
3003
+
3004
+ // https://ieeexplore.ieee.org/document/7346766
3005
+ misalignment = [1, 0, 0, 0];
3006
+ misalignmentError = deg2rad(3);
3007
+
3008
+ /**
3009
+ * @override
3010
+ */
3011
+ static get pname() {
3012
+ return 'PDR';
3013
+ }
3014
+
3015
+ /**
3016
+ * @override
3017
+ */
3018
+ static get eventsType() {
3019
+ return [EventType.RelativePosition];
3020
+ }
3021
+
3022
+ /**
3023
+ * @override
3024
+ */
3025
+ get _availability() {
3026
+ return Promise.all([
3027
+ StepDetector$1.availability,
3028
+ AbsoluteAttitude$1.availability
3029
+ ]);
3030
+ }
3031
+
3032
+
3033
+ /**
3034
+ * @override
3035
+ */
3036
+ start() {
3037
+
3038
+ this.stepDetectionProviderId = StepDetector$1.addEventListener(
3039
+ events => this.onStepEvent(events[0]),
3040
+ error => this.notifyError(error)
3041
+ );
3042
+
3043
+ this.absoluteAttitudeProviderId = AbsoluteAttitude$1.addEventListener(
3044
+ events => (this.attitudeEvent = events[0]),
3045
+ error => this.notifyError(error)
3046
+ );
3047
+
3048
+ }
3049
+
3050
+ /**
3051
+ * @override
3052
+ */
3053
+ stop() {
3054
+ StepDetector$1.removeEventListener(this.stepDetectionProviderId);
3055
+ AbsoluteAttitude$1.removeEventListener(this.absoluteAttitudeProviderId);
3056
+ }
3057
+
3058
+ /**
3059
+ * @private
3060
+ */
3061
+ onStepEvent = stepEvent => {
3062
+
3063
+ if (!this.attitudeEvent) {
3064
+ return;
3065
+ }
3066
+
3067
+ const stepSize = stepEvent.data.size;
3068
+
3069
+ /**
3070
+ * There is three frames because (device heading != walking direction):
3071
+ *
3072
+ * Device frame (x-Device Right, y-Device Top, z-Device Front)
3073
+ * Navigation frame (x-Nav East, y-Nav North, z-Nav Up)
3074
+ * Earth local frame (x-East y-North z-Up)
3075
+ *
3076
+ *
3077
+ * For the PDR, in order to find the step direction,
3078
+ * we are looking for the rotation between the device frame and the navigation frame.
3079
+ * Attitude is the rotation from the device frame to the earth local frame.
3080
+ * Misalignment is the rotation from the navigation frame to the earth local frame
3081
+ */
3082
+ const deviceAttitude = this.attitudeEvent.data;
3083
+
3084
+ /**
3085
+ * For optimisation, as we define misalignment to identity, we do not process the quat mulitply formula.
3086
+ */
3087
+ // const deviceInNavFrame = Quaternion.multiply(deviceAttitude.quaternion, this.misalignment);
3088
+ // const deviceDirection = new Attitude(deviceInNavFrame).heading;
3089
+ const deviceDirection = deviceAttitude.heading;
3090
+
3091
+ /**
3092
+ * A bad accuracy from PDR is due to three things:
3093
+ * - Attitude accuracy
3094
+ * - Misalignement (device heading != walking direction)
3095
+ * - Step detection false positives / false negatives
3096
+ * Following formula only use attitude accuracy with cone formula
3097
+ */
3098
+ const deviceDirectionAccuracy = deviceAttitude.accuracy + this.misalignmentError;
3099
+ const accuracy = (stepSize / 2) * Math.sin(deviceDirectionAccuracy / 2);
3100
+ const timestamp = deviceAttitude.time;
3101
+
3102
+ /**
3103
+ * Relative position is defined in ENU frame
3104
+ */
3105
+ const position = new GeoRelativePosition$1(
3106
+ stepSize * Math.sin(deviceDirection),
3107
+ stepSize * Math.cos(deviceDirection),
3108
+ 0,
3109
+ timestamp,
3110
+ accuracy,
3111
+ deviceDirection
3112
+ );
3113
+
3114
+ this.notify(this.createEvent(
3115
+ EventType.GeoRelativePosition,
3116
+ position,
3117
+ [stepEvent, this.attitudeEvent]
3118
+ ));
3119
+ }
3120
+
3121
+ }
3122
+
3123
+ var Pdr$1 = new Pdr();
3124
+
3125
+ class GeoRelativePosition extends Provider {
3126
+
3127
+ /**
3128
+ * @override
3129
+ */
3130
+ static get pname() {
3131
+ return 'GeoRelativePosition';
3132
+ }
3133
+
3134
+ /**
3135
+ * @override
3136
+ */
3137
+ static get eventsType() {
3138
+ return [EventType.GeoRelativePosition];
3139
+ }
3140
+
3141
+ /**
3142
+ * @override
3143
+ */
3144
+ get _availability() {
3145
+ return PromiseUtils.any([
3146
+ Pdr$1.availability,
3147
+ GeoRelativePositionFromArCore$1.availability
3148
+ ]);
3149
+ }
3150
+
3151
+
3152
+ /**
3153
+ * @override
3154
+ */
3155
+ start() {
3156
+
3157
+ GeoRelativePositionFromArCore$1.availability
3158
+ .then(() => (this.geoRelativeProvider = GeoRelativePositionFromArCore$1))
3159
+ .catch(() => (this.geoRelativeProvider = Pdr$1))
3160
+ .finally(() => {
3161
+ this.providerId = this.geoRelativeProvider.addEventListener(
3162
+ events => {
3163
+ const event = events.find(_event => _event.dataType === EventType.GeoRelativePosition);
3164
+ if (event) {
3165
+ this.notify(event.clone());
3166
+ }
3167
+ },
3168
+ error => this.notifyError(error)
3169
+ );
3170
+ });
3171
+ }
3172
+
3173
+ /**
3174
+ * @override
3175
+ */
3176
+ stop() {
3177
+ if (this.geoRelativeProvider) {
3178
+ this.geoRelativeProvider.removeEventListener(this.providerId);
3179
+ this.geoRelativeProvider = null;
3180
+ }
3181
+ }
3182
+ }
3183
+
3184
+ var GeoRelativePositionProvider = new GeoRelativePosition();
3185
+
3186
+ class AbsolutePosition extends Provider {
3187
+
3188
+ // Use the new absolute position if its accuracy is at least x times better than the last one.
3189
+ static ACCURACY_RELOC_RATIO = 1.5;
3190
+
3191
+ /** @type {boolean} */
3192
+ static USE_MM_FOR_FEED = true;
3193
+
3194
+
3195
+ /** @type {number?} */
3196
+ _gnssWifiProviderId;
3197
+
3198
+ /** @type {number?} */
3199
+ _relativePositionProviderId;
3200
+
3201
+ /** @type {number?} */
3202
+ _mapMatchingHandlerId
3203
+
3204
+ /**
3205
+ * @override
3206
+ */
3207
+ static get pname() {
3208
+ return 'AbsolutePosition';
3209
+ }
3210
+
3211
+ /**
3212
+ * @override
3213
+ */
3214
+ static get eventsType() {
3215
+ return [EventType.AbsolutePosition];
3216
+ }
3217
+
3218
+ /**
3219
+ * @override
3220
+ */
3221
+ get _availability() {
3222
+ return PromiseUtils.any([
3223
+ GeoRelativePositionProvider.availability,
3224
+ GnssWifi$1.availability
3225
+ ]);
3226
+ }
3227
+
3228
+
3229
+ /**
3230
+ * @override
3231
+ */
3232
+ start() {
3233
+ GeoRelativePositionProvider.availability
3234
+ .then(() => {
3235
+ this._relativePositionProviderId = GeoRelativePositionProvider.addEventListener(
3236
+ events => this._onRelativePosition(events[0])
3237
+ );
3238
+ })
3239
+ .catch(() => {
3240
+ // do nothing
3241
+ });
3242
+
3243
+ this._gnssWifiProviderId = GnssWifi$1.addEventListener(
3244
+ events => {
3245
+ // bearing from GnssWifi is not reliable for our usecase
3246
+ events[0].data.bearing = null;
3247
+ this._onAbsolutePosition(events[0], false);
3248
+ }
3249
+ );
3250
+
3251
+ this._mapMatchingHandlerId = MapMatchingHandler$1.addEventListener();
3252
+ }
3253
+
3254
+
3255
+ /**
3256
+ * @override
3257
+ */
3258
+ stop() {
3259
+ if (this._relativePositionProviderId) {
3260
+ GeoRelativePositionProvider.removeEventListener(this._relativePositionProviderId);
3261
+ }
3262
+ GnssWifi$1.removeEventListener(this._gnssWifiProviderId);
3263
+
3264
+ MapMatchingHandler$1.removeEventListener(this._mapMatchingHandlerId);
3265
+ }
3266
+
3267
+ /**
3268
+ * @param {ProviderEvent<UserPosition>} positionEvent
3269
+ * @param {boolean} canContainLevel
3270
+ */
3271
+ _onAbsolutePosition(positionEvent, canContainLevel = true) {
3272
+
3273
+ const newPosition = positionEvent.data.clone();
3274
+ const lastPosition = this.lastEvent ? this.lastEvent.data : null;
3275
+
3276
+ if (lastPosition) {
3277
+
3278
+ // Is the new position accuracy is better enough than the last position accuracy
3279
+ const isBetterEnough = newPosition.accuracy * AbsolutePosition.ACCURACY_RELOC_RATIO < lastPosition.accuracy;
3280
+
3281
+ // Is the new position is far from the new one (regarding accuracy)
3282
+ // This is important if the person put the current page in the background during a while
3283
+ const isFarEnough = lastPosition.distanceTo(newPosition) > lastPosition.accuracy + newPosition.accuracy;
3284
+
3285
+ if (!isBetterEnough && !isFarEnough) {
3286
+ return;
3287
+ }
3288
+
3289
+ if (!canContainLevel) {
3290
+ newPosition.level = lastPosition.level;
3291
+ }
3292
+
3293
+ // If the new position does not have a bearing, retrieve the bearing from the last position
3294
+ if (newPosition.bearing === null) {
3295
+ newPosition.bearing = lastPosition.bearing;
3296
+ }
3297
+ }
3298
+
3299
+
3300
+ const newPositionEvent = this.createEvent(
3301
+ EventType.AbsolutePosition,
3302
+ newPosition,
3303
+ [positionEvent]
3304
+ );
3305
+
3306
+ // If the MM is disable or the network is not set yet, use the new position as it is.
3307
+ // If the position bearing is null, do not use MM, it is too dangerous.
3308
+ if (!MapMatchingHandler$1.canUseMapMatching()) {
3309
+ this.notify(newPositionEvent);
3310
+ return;
3311
+ }
3312
+
3313
+ MapMatchingHandler$1.notifyPositionFromAbsolute(newPositionEvent);
3314
+ }
3315
+
3316
+ /**
3317
+ * @param {ProviderEvent<GeoRelativePosition>} relativeEvent
3318
+ */
3319
+ _onRelativePosition(relativeEvent) {
3320
+
3321
+ if (!this.lastEvent) {
3322
+ return;
3323
+ }
3324
+
3325
+ const lastPosition = this.lastEvent.data;
3326
+
3327
+ const offsetPos = relativeEvent.data;
3328
+
3329
+ const dist = Math.sqrt(offsetPos.x ** 2 + offsetPos.y ** 2);
3330
+ const bearing = Math.atan2(offsetPos.x, offsetPos.y);
3331
+ const alt = lastPosition.alt !== null ? offsetPos.z : null;
3332
+
3333
+ const newPosition = lastPosition.destinationPoint(dist, bearing, alt);
3334
+ newPosition.bearing = offsetPos.bearing;
3335
+ newPosition.time = offsetPos.time;
3336
+ newPosition.accuracy += offsetPos.accuracy;
3337
+
3338
+
3339
+ const newPositionEvent = this.createEvent(
3340
+ EventType.AbsolutePosition,
3341
+ newPosition,
3342
+ [relativeEvent]
3343
+ );
3344
+
3345
+
3346
+ if (!MapMatchingHandler$1.canUseMapMatching()) {
3347
+ this.notify(newPositionEvent);
3348
+ return;
3349
+ }
3350
+
3351
+ MapMatchingHandler$1.notifyPositionFromRelative(newPositionEvent);
3352
+ }
3353
+
3354
+
3355
+ /**
3356
+ * @override
3357
+ * @param {UserPosition|ProviderEvent} data
3358
+ */
3359
+ feed(data) {
3360
+
3361
+ /** @type {ProviderEvent<UserPosition>} */
3362
+ let newPositionEvent;
3363
+
3364
+ if (data instanceof UserPosition) {
3365
+
3366
+ if (data.time === null) {
3367
+ throw Error('the time of the position is not defined');
3368
+ }
3369
+ if (data.accuracy === null) {
3370
+ throw Error('the accuracy of the position is not defined');
3371
+ }
3372
+
3373
+ newPositionEvent = new ProviderEvent(EventType.AbsolutePosition, data);
3374
+
3375
+ } else if (data instanceof ProviderEvent) {
3376
+
3377
+ if (data.dataType !== EventType.AbsolutePosition
3378
+ || !(data.data instanceof UserPosition)) {
3379
+ throw Error('the provider event is not an UserPosition');
3380
+ }
3381
+
3382
+ newPositionEvent = data;
3383
+
3384
+ } else {
3385
+ throw new Error('data is nor an UserPosition or a ProviderEvent');
3386
+ }
3387
+
3388
+ // If the MM is disable or the network is not set yet, use the new position as it is.
3389
+ if (!AbsolutePosition.USE_MM_FOR_FEED || !MapMatchingHandler$1.canUseMapMatching()) {
3390
+ this.notify(newPositionEvent);
3391
+ return;
3392
+ }
3393
+
3394
+ MapMatchingHandler$1.notifyPositionFromFeed(newPositionEvent);
3395
+ }
3396
+ }
3397
+
3398
+ var AbsolutePosition$1 = new AbsolutePosition();
3399
+
3400
+ /**
3401
+ * Absolute attitude provider gives the device attitude in East-North-Up (ENU) frame using
3402
+ * browser deviceorientation or deviceorientationabsolute
3403
+ * The provider does not work until an AbsolutePosition is given. This is necessary to
3404
+ * calculate declination.
3405
+ *
3406
+ * -----------------------------------
3407
+ * Overview of compatibilities:
3408
+ * -----------------------------------
3409
+ *
3410
+ * Chrome Android (v72.0.3626): YES (via deviceorientationabsolute)
3411
+ * Safari iOS (v12.0): YES (via deviceorientation and event.webkitCompassHeading)
3412
+ * Opera Android (v50.2.2426): NO {@link https://developer.mozilla.org/en-US/docs/Web/API/Window/ondeviceorientation}
3413
+ * Firefox Android (v65.0.1): NO {@link https://www.fxsitecompat.com/en-CA/docs/2018/various-device-sensor-apis-are-now-deprecated/}
3414
+ *
3415
+ * -----------------------------------
3416
+ */
3417
+ class AbsoluteAttitudeFromBrowser extends Provider {
3418
+
3419
+ // from http://tyrex.inria.fr/mobile/benchmarks-attitude/
3420
+ static DEFAULT_ACCURACY = deg2rad(15);
3421
+
3422
+ absolutePositionProviderId = null;
3423
+
3424
+ /**
3425
+ * @override
3426
+ */
3427
+ static get pname() {
3428
+ return 'AbsoluteAttitudeFromBrowser';
3429
+ }
3430
+
3431
+ /**
3432
+ * @override
3433
+ */
3434
+ static get eventsType() {
3435
+ return [EventType.AbsoluteAttitude];
3436
+ }
3437
+
3438
+ /**
3439
+ * @override
3440
+ */
3441
+ get _availability() {
3442
+ return BrowserUtils.isMobile
3443
+ ? Promise.resolve()
3444
+ : Promise.reject(new AskImuOnDesktopError());
3445
+ }
3446
+
3447
+ /**
3448
+ * @override
3449
+ */
3450
+ start() {
3451
+
3452
+ const subscribe = () => {
3453
+ switch (BrowserUtils.name) {
3454
+ case Browser.CHROME:
3455
+ window.addEventListener('deviceorientationabsolute',
3456
+ this.onDeviceOrientationChromeEvent, true);
3457
+ break;
3458
+
3459
+ case Browser.SAFARI:
3460
+ case Browser.IOS_WEBVIEW:
3461
+ window.addEventListener('deviceorientation',
3462
+ this.onDeviceOrientationSafariEvent, true);
3463
+ break;
3464
+ }
3465
+ };
3466
+
3467
+ if (typeof (DeviceOrientationEvent) !== 'undefined' && typeof (DeviceOrientationEvent.requestPermission) === 'function') {
3468
+ DeviceOrientationEvent
3469
+ .requestPermission()
3470
+ .then(response => {
3471
+ if (response !== 'granted') {
3472
+ throw new Error('Permission not granted');
3473
+ }
3474
+ subscribe();
3475
+ })
3476
+ .catch(error => this.notifyError(error));
3477
+ } else {
3478
+ subscribe();
3479
+ }
3480
+
3481
+
3482
+ const lastAbsolutePosition = AbsolutePosition$1.lastEvent;
3483
+ if (lastAbsolutePosition) {
3484
+ this.onAbsolutePositionEvent(lastAbsolutePosition);
3485
+ } else {
3486
+ this.absolutePositionProviderId = AbsolutePosition$1.addEventListener(
3487
+ events => this.onAbsolutePositionEvent(events[0]),
3488
+ error => this.notifyError(error),
3489
+ false
3490
+ );
3491
+ }
3492
+
3493
+ }
3494
+
3495
+ /**
3496
+ * @override
3497
+ */
3498
+ stop() {
3499
+ switch (BrowserUtils.name) {
3500
+ case Browser.CHROME:
3501
+ window.removeEventListener('deviceorientationabsolute',
3502
+ this.onDeviceOrientationChromeEvent, true);
3503
+ break;
3504
+
3505
+ case Browser.SAFARI:
3506
+ case Browser.IOS_WEBVIEW:
3507
+ window.removeEventListener('deviceorientation',
3508
+ this.onDeviceOrientationSafariEvent, true);
3509
+ break;
3510
+ }
3511
+
3512
+ if (this.absolutePositionProviderId !== null) {
3513
+ AbsolutePosition$1.removeEventListener(this.absolutePositionProviderId);
3514
+ }
3515
+ }
3516
+
3517
+
3518
+ onDeviceOrientationChromeEvent = e => {
3519
+
3520
+ this.magQuaternionTimestamp = e.timeStamp / 1e3;
3521
+
3522
+ if (typeof e.alpha !== 'number' || typeof e.beta !== 'number' || typeof e.gamma !== 'number') {
3523
+ this.notifyError(new MissingSensorError().from('deviceorientationabsolute'));
3524
+ return;
3525
+ }
3526
+
3527
+ this.magQuaternion = Rotations.eulerToQuaternionZXYDegrees(
3528
+ [e.alpha, e.beta, e.gamma]);
3529
+
3530
+ this.compute();
3531
+ };
3532
+
3533
+
3534
+ iosPreviousQuat = null;
3535
+ iosIsSkyMode;
3536
+
3537
+ onDeviceOrientationSafariEvent = e => {
3538
+
3539
+ this.magQuaternionTimestamp = e.timeStamp / 1e3;
3540
+
3541
+ if (typeof e.beta !== 'number' || typeof e.gamma !== 'number') {
3542
+ this.notifyError(new MissingSensorError().from('deviceorientation'));
3543
+ return;
3544
+ }
3545
+
3546
+ if (typeof e.webkitCompassHeading !== 'number') {
3547
+ super.notifyError(new MissingMagnetometerError().from('deviceorientation'));
3548
+ return;
3549
+ }
3550
+
3551
+ /**
3552
+ * Trying the best to retrieve a good quaternion from devicemotion event.
3553
+ */
3554
+
3555
+ let alpha;
3556
+ const [qw, qx, qy, qz] = Rotations.eulerToQuaternionZXYDegrees([e.webkitCompassHeading, e.beta, e.gamma]);
3557
+ const groundAngle = rad2deg(Math.acos(qw ** 2 - qx ** 2 - qy ** 2 + qz ** 2));
3558
+
3559
+ let isSkyMode;
3560
+ if (groundAngle > 136) {
3561
+ isSkyMode = true;
3562
+ } else if (groundAngle < 134) {
3563
+ isSkyMode = false;
3564
+ } else if (this.iosPreviousQuat && this.iosIsSkyMode !== null) {
3565
+ /**
3566
+ * This condition is true only if :
3567
+ * - we are in the [134°; 136°] ground angle range
3568
+ * - we know the previous quaternion
3569
+ * - one of the both previous condition has been reached during this session
3570
+ *
3571
+ * In this case, we define a threshold to detect if there is a "jump" in the quaternion calculation,
3572
+ * then, the mode is switched.
3573
+ */
3574
+ isSkyMode = Quaternion.distance([qw, qx, qy, qz], this.iosPreviousQuat) < 0.5
3575
+ ? this.iosIsSkyMode
3576
+ : !this.iosIsSkyMode;
3577
+ }
3578
+ this.iosPreviousQuat = [qw, qx, qy, qz];
3579
+ this.iosIsSkyMode = isSkyMode;
3580
+
3581
+ if (typeof (isSkyMode) === 'undefined') {
3582
+ // Algorithm uncertainity
3583
+ return;
3584
+ }
3585
+
3586
+ if (isSkyMode) {
3587
+ alpha = 180 - e.webkitCompassHeading;
3588
+ } else {
3589
+ alpha = AbsoluteAttitudeFromBrowser.webkitCompassToHeading(
3590
+ e.webkitCompassHeading, e.beta, e.gamma);
3591
+ }
3592
+
3593
+ this.magQuaternion = Rotations.eulerToQuaternionZXYDegrees([alpha, e.beta, e.gamma]);
3594
+
3595
+ this.compute();
3596
+ };
3597
+
3598
+
3599
+ compute() {
3600
+
3601
+ if (!this.declinationQuaternion || !this.magQuaternion) {
3602
+ return;
3603
+ }
3604
+
3605
+ const trueQuaternion = Quaternion.multiply(this.declinationQuaternion, this.magQuaternion);
3606
+ const attitude = new Attitude(trueQuaternion, this.magQuaternionTimestamp,
3607
+ this.constructor.DEFAULT_ACCURACY, this.pname);
3608
+ this.notify(this.createEvent(
3609
+ EventType.AbsoluteAttitude,
3610
+ attitude,
3611
+ [this.absolutePositionEvent]
3612
+ ));
3613
+ }
3614
+
3615
+ /**
3616
+ * Initialized declination quaternion using current position.
3617
+ * This method should be theoretically called every time the user moves.
3618
+ * But in reality declination does not change as much.
3619
+ * @param {Coordinates} positionEvent user position event
3620
+ */
3621
+ onAbsolutePositionEvent = positionEvent => {
3622
+
3623
+ if (!positionEvent) {
3624
+ return;
3625
+ }
3626
+
3627
+ this.absolutePositionEvent = positionEvent;
3628
+
3629
+ const position = positionEvent.data;
3630
+ const wmmResult = geomagnetism.model().point([position.lat, position.lng]);
3631
+ // Declination is given in NED frame and our code use ENU, that is why we have: "-decl"
3632
+ this.declinationQuaternion = Quaternion.fromAxisAngle([0, 0, 1], - deg2rad(wmmResult.decl));
3633
+
3634
+ AbsolutePosition$1.removeEventListener(this.absolutePositionProviderId);
3635
+ this.absolutePositionProviderId = null;
3636
+ this.compute();
3637
+ }
3638
+
3639
+ static webkitCompassToHeading(_webkitCompassHeading, _beta, _gamma) {
3640
+ const webkitCompassHeading = deg2rad(_webkitCompassHeading);
3641
+ const beta = deg2rad(_beta);
3642
+ const gamma = deg2rad(_gamma);
3643
+
3644
+ const c1 = Math.cos(webkitCompassHeading / 2);
3645
+ const c2 = Math.cos(beta / 2);
3646
+ const c3 = Math.cos(gamma / 2);
3647
+ const s1 = Math.sin(webkitCompassHeading / 2);
3648
+ const s2 = Math.sin(beta / 2);
3649
+ const s3 = Math.sin(gamma / 2);
3650
+
3651
+ const qw = c1 * c2 * c3 - s1 * s2 * s3;
3652
+ const qz = s1 * c2 * c3 + c1 * s2 * s3;
3653
+
3654
+ return rad2deg(-2 * Math.atan(qz / qw));
3655
+ }
3656
+ }
3657
+
3658
+ var AbsoluteAttitudeFromBrowser$1 = new AbsoluteAttitudeFromBrowser();
3659
+
3660
+ /**
3661
+ * Inclination provider gives the inclination of the device using Imu Sensor
3662
+ * For example, when the top of the device is pointing the sky, inclination = Math.PI/2
3663
+ * when the device is layed on a table, inclination = 0
3664
+ * This provider use window.orientation to return a result in function of screen orientation
3665
+ */
3666
+ class InclinationFromAcc extends Provider {
3667
+
3668
+
3669
+ /**
3670
+ * @override
3671
+ */
3672
+ static get pname() {
3673
+ return 'InclinationFromAcc';
3674
+ }
3675
+
3676
+ /**
3677
+ * @override
3678
+ */
3679
+ static get eventsType() {
3680
+ return [EventType.Inclination];
3681
+ }
3682
+
3683
+ /**
3684
+ * @override
3685
+ */
3686
+ get _availability() {
3687
+ return Accelerometer$1.availability;
3688
+ }
3689
+
3690
+ /**
3691
+ * @override
3692
+ */
3693
+ start() {
3694
+ this.providerId = Accelerometer$1.addEventListener(
3695
+ events => this.onAccelerometerEvent(events[0]),
3696
+ error => this.notifyError(error)
3697
+ );
3698
+ }
3699
+
3700
+ /**
3701
+ * @override
3702
+ */
3703
+ stop() {
3704
+ Accelerometer$1.removeEventListener(this.providerId);
3705
+ }
3706
+
3707
+ /**
3708
+ * @private
3709
+ */
3710
+ onAccelerometerEvent = accelerometerEvent => {
3711
+ const acc = accelerometerEvent.data.values;
3712
+
3713
+ const screenOrientation = window.orientation || 0;
3714
+
3715
+ const sizeAcc = Math.sqrt(acc[0] * acc[0] + acc[1] * acc[1] + acc[2] * acc[2]);
3716
+ const accNormalized = [acc[0] / sizeAcc, acc[1] / sizeAcc, acc[2] / sizeAcc];
3717
+
3718
+ const q = [accNormalized[2] + 1, accNormalized[1], -accNormalized[0], 0];
3719
+ const qSize = Math.sqrt(q[0] * q[0] + q[1] * q[1] + q[2] * q[2]);
3720
+ const qNormalized = [q[0] / qSize, q[1] / qSize, q[2] / qSize, 0];
3721
+
3722
+ let inclination;
3723
+ if (screenOrientation === 0) {
3724
+ inclination = Math.asin(2 * qNormalized[1] * qNormalized[0]);
3725
+ } else if (screenOrientation === 90) {
3726
+ inclination = -Math.asin(2 * qNormalized[2] * qNormalized[0]);
3727
+ } else if (screenOrientation === -90) {
3728
+ inclination = Math.asin(2 * qNormalized[2] * qNormalized[0]);
3729
+ } else if (screenOrientation === 180) {
3730
+ inclination = -Math.asin(2 * qNormalized[1] * qNormalized[0]);
3731
+ }
3732
+
3733
+ this.notify(this.createEvent(
3734
+ EventType.Inclination,
3735
+ inclination,
3736
+ [accelerometerEvent]
3737
+ ));
3738
+ };
3739
+ }
3740
+
3741
+ var InclinationFromAcc$1 = new InclinationFromAcc();
3742
+
3743
+ /**
3744
+ * Inclination provider gives the inclination of the device using Relative Attitude
3745
+ * For example, when the top of the device is pointing the sky, inclination = Math.PI/2
3746
+ * when the device is layed on a table, inclination = 0
3747
+ * This provider use window.orientation to return a result in function of screen orientation
3748
+ */
3749
+ class InclinationFromRelativeAttitude extends Provider {
3750
+
3751
+ /**
3752
+ * @override
3753
+ */
3754
+ static get pname() {
3755
+ return 'InclinationFromRelativeAttitude';
3756
+ }
3757
+
3758
+ /**
3759
+ * @override
3760
+ */
3761
+ static get eventsType() {
3762
+ return [EventType.Inclination];
3763
+ }
3764
+
3765
+ /**
3766
+ * @override
3767
+ */
3768
+ get _availability() {
3769
+ return RelativeAttitudeFromInertial$1.availability;
3770
+ }
3771
+
3772
+ /**
3773
+ * @override
3774
+ */
3775
+ start() {
3776
+ this.providerId = RelativeAttitudeFromInertial$1.addEventListener(
3777
+ events => {
3778
+ const attitudeEvent = events[0];
3779
+ const inclination = this.constructor.enuQuatToInclination(
3780
+ attitudeEvent.data.quaternion
3781
+ );
3782
+ this.notify(this.createEvent(
3783
+ EventType.Inclination,
3784
+ inclination,
3785
+ [attitudeEvent]
3786
+ ));
3787
+ },
3788
+ error => this.notifyError(error)
3789
+ );
3790
+ }
3791
+
3792
+ /**
3793
+ * @override
3794
+ */
3795
+ stop() {
3796
+ RelativeAttitudeFromInertial$1.removeEventListener(this.providerId);
3797
+ }
3798
+
3799
+ static enuQuatToInclination(q) {
3800
+ const screenOrientation = window.orientation || 0;
3801
+
3802
+ if (screenOrientation === 0) {
3803
+ return Math.asin(2 * (q[2] * q[3] + q[1] * q[0]));
3804
+ } else if (screenOrientation === 90) {
3805
+ return Math.asin(2 * (q[1] * q[3] - q[2] * q[0]));
3806
+ } else if (screenOrientation === -90) {
3807
+ return Math.asin(2 * (q[2] * q[0] - q[1] * q[3]));
3808
+ }
3809
+ // else if (screenOrientation === 180)
3810
+ return -Math.asin(2 * (q[2] * q[3] + q[1] * q[0]));
3811
+ }
3812
+ }
3813
+
3814
+ var InclinationFromRelativeAttitude$1 = new InclinationFromRelativeAttitude();
3815
+
3816
+ /**
3817
+ * Inclination provider gives the inclination of the device using Imu Sensor
3818
+ * For example, when the top of the device is pointing the sky, inclination = Math.PI/2
3819
+ * when the device is layed on a table, inclination = 0
3820
+ * This provider use window.orientation to return a result in function of screen orientation
3821
+ */
3822
+ class Inclination extends Provider {
3823
+
3824
+ /**
3825
+ * @override
3826
+ */
3827
+ static get pname() {
3828
+ return 'InclinationProvider';
3829
+ }
3830
+
3831
+ /**
3832
+ * @override
3833
+ */
3834
+ static get eventsType() {
3835
+ return [EventType.Inclination];
3836
+ }
3837
+
3838
+ /**
3839
+ * @override
3840
+ */
3841
+ get _availability() {
3842
+ return PromiseUtils.any([
3843
+ InclinationFromAcc$1.availability,
3844
+ InclinationFromRelativeAttitude$1.availability
3845
+ ]);
3846
+ }
3847
+
3848
+ /**
3849
+ * @override
3850
+ */
3851
+ start() {
3852
+
3853
+ const startInclinationFromAcc = () => {
3854
+ this.provider = InclinationFromAcc$1;
3855
+ this.providerId = this.provider.addEventListener(
3856
+ events => this.notify(events[0].clone()),
3857
+ error => this.notifyError(error)
3858
+ );
3859
+ };
3860
+
3861
+ if (InclinationFromRelativeAttitude$1.availability) {
3862
+ this.provider = InclinationFromRelativeAttitude$1;
3863
+ this.providerId = this.provider.addEventListener(
3864
+ events => this.notify(events[0].clone()),
3865
+ () => {
3866
+ InclinationFromRelativeAttitude$1.removeEventListener(this.providerId);
3867
+ startInclinationFromAcc();
3868
+ }
3869
+ );
3870
+ } else {
3871
+ startInclinationFromAcc();
3872
+ }
3873
+ }
3874
+
3875
+ /**
3876
+ * @override
3877
+ */
3878
+ stop() {
3879
+ if (this.provider) {
3880
+ this.provider.removeEventListener(this.providerId);
3881
+ }
3882
+ }
3883
+ }
3884
+
3885
+ var Inclination$1 = new Inclination();
3886
+
3887
+ class IpResolveServerError extends Error {
3888
+
3889
+ static DEFAULT_MESSAGE = 'IP Resolver failed';
3890
+
3891
+ constructor(message) {
3892
+ super(message || IpResolveServerError.DEFAULT_MESSAGE);
3893
+ }
3894
+ }
3895
+
3896
+ /**
3897
+ * GnssWifiProvider is a provider based on navigator.geolocation.
3898
+ * This API does not allow us to know if the position returned is provided
3899
+ * by Wifi Fingerprinting algorithms or by GNSS. That is why the name is
3900
+ * "GnssWifi".
3901
+ */
3902
+ class Ip extends Provider {
3903
+
3904
+ /**
3905
+ * @override
3906
+ */
3907
+ static get pname() {
3908
+ return 'IP';
3909
+ }
3910
+
3911
+ /**
3912
+ * @override
3913
+ */
3914
+ static get eventsType() {
3915
+ return [EventType.AbsolutePosition];
3916
+ }
3917
+
3918
+ /**
3919
+ * @override
3920
+ */
3921
+ start() {
3922
+
3923
+ fetch('https://ipinfo.io/geo?token=24a7ca2f3b489d')
3924
+ .then((response) => {
3925
+ if (!response) {
3926
+ this.notifyError(new IpResolveServerError());
3927
+ return;
3928
+ }
3929
+
3930
+ const timestamp = TimeUtils.preciseTime() / 1e3;
3931
+
3932
+ const latLngStr = response.loc.split(',');
3933
+ const position = new UserPosition(
3934
+ parseFloat(latLngStr[0]),
3935
+ parseFloat(latLngStr[1]),
3936
+ null,
3937
+ null,
3938
+ timestamp
3939
+ );
3940
+
3941
+ this.notify(this.createEvent(EventType.AbsolutePosition, position));
3942
+ });
3943
+ }
3944
+
3945
+ /**
3946
+ * @override
3947
+ */
3948
+ stop() {
3949
+
3950
+ }
3951
+
3952
+ }
3953
+
3954
+ var Ip$1 = new Ip();
3955
+
3956
+ class Barcode extends Provider {
3957
+
3958
+ /**
3959
+ * @override
3960
+ */
3961
+ static get pname() {
3962
+ return 'Barcode';
3963
+ }
3964
+
3965
+ /**
3966
+ * @override
3967
+ */
3968
+ static get eventsType() {
3969
+ return [EventType.Barcode];
3970
+ }
3971
+
3972
+ /**
3973
+ * @override
3974
+ */
3975
+ get _availability() {
3976
+ return ArCore$1.availability;
3977
+ }
3978
+
3979
+ /**
3980
+ * @override
3981
+ */
3982
+ start() {
3983
+ ArCore$1.enableBarcodeScanner();
3984
+
3985
+ this.providerId = ArCore$1.addEventListener(
3986
+ events => {
3987
+ const barcodeEvent = events.find(event => event.dataType === EventType.Barcode);
3988
+ if (barcodeEvent) {
3989
+ this.notify(barcodeEvent);
3990
+ }
3991
+ },
3992
+ error => this.notifyError(error)
3993
+ );
3994
+ }
3995
+
3996
+ /**
3997
+ * @override
3998
+ */
3999
+ stop() {
4000
+ ArCore$1.disableBarcodeScanner();
4001
+ ArCore$1.removeEventListener(this.providerId);
4002
+ }
4003
+ }
4004
+
4005
+ var Barcode$1 = new Barcode();
4006
+
4007
+ class CameraNative extends Provider {
4008
+
4009
+ /**
4010
+ * @override
4011
+ */
4012
+ static get pname() {
4013
+ return 'CameraNative';
4014
+ }
4015
+
4016
+ /**
4017
+ * @override
4018
+ */
4019
+ static get eventsType() {
4020
+ return [EventType.CameraNative];
4021
+ }
4022
+
4023
+ /**
4024
+ * @override
4025
+ */
4026
+ get _availability() {
4027
+ return new Promise((resolve, reject) => {
4028
+ ArCore$1.availability
4029
+ .then(output => resolve(output))
4030
+ .catch(() => reject('Providers will not use the camera.'));
4031
+ });
4032
+ }
4033
+
4034
+ /**
4035
+ * @override
4036
+ */
4037
+ start() {
4038
+
4039
+ if (ArCore$1.state !== ProviderState.STOPPPED) {
4040
+ this.notify(this.createEvent(EventType.CameraNative, 'started'));
4041
+ }
4042
+ this.providerId = ArCore$1.addMonitoringListener(
4043
+ () => this.notify(this.createEvent(EventType.CameraNative, 'started')),
4044
+ () => this.notify(this.createEvent(EventType.CameraNative, 'stopped'))
4045
+ );
4046
+ }
4047
+
4048
+ /**
4049
+ * @override
4050
+ */
4051
+ stop() {
4052
+ ArCore$1.removeMonitoringListener(this.providerId);
4053
+ }
4054
+ }
4055
+
4056
+ var CameraNative$1 = new CameraNative();
4057
+
4058
+ class CameraProjectionMatrix extends Provider {
4059
+
4060
+ /**
4061
+ * @override
4062
+ */
4063
+ static get pname() {
4064
+ return 'CameraProjectionMatrix';
4065
+ }
4066
+
4067
+ /**
4068
+ * @override
4069
+ */
4070
+ static get eventsType() {
4071
+ return [EventType.CameraProjectionMatrix];
4072
+ }
4073
+
4074
+ /**
4075
+ * @override
4076
+ */
4077
+ get _availability() {
4078
+ return ArCore$1.availability;
4079
+ }
4080
+
4081
+ /**
4082
+ * @override
4083
+ */
4084
+ start() {
4085
+ this.providerId = ArCore$1.addEventListener(
4086
+ events => {
4087
+ const projMatrixEvent = events.find(event => event.dataType === EventType.CameraProjectionMatrix);
4088
+ if (projMatrixEvent) {
4089
+ this.notify(projMatrixEvent);
4090
+ }
4091
+ },
4092
+ error => this.notifyError(error)
4093
+ );
4094
+ }
4095
+
4096
+ /**
4097
+ * @override
4098
+ */
4099
+ stop() {
4100
+ ArCore$1.removeEventListener(this.providerId);
4101
+ }
4102
+ }
4103
+
4104
+ var CameraProjectionMatrix$1 = new CameraProjectionMatrix();
4105
+
4106
+ /* eslint-disable max-len */
4107
+
4108
+ var Providers = /*#__PURE__*/Object.freeze({
4109
+ __proto__: null,
4110
+ Imu: Imu$1,
4111
+ Accelerometer: Accelerometer$1,
4112
+ Gyroscope: Gyroscope$1,
4113
+ HighRotationsDetector: HighRotationsDetector$1,
4114
+ RelativeAttitudeFromBrowser: RelativeAttitudeFromBrowser$1,
4115
+ RelativeAttitudeFromEkf: RelativeAttitudeFromEkf$1,
4116
+ RelativeAttitudeFromInertial: RelativeAttitudeFromInertial$1,
4117
+ RelativeAttitude: RelativeAttitude$1,
4118
+ AbsoluteAttitudeFromBrowser: AbsoluteAttitudeFromBrowser$1,
4119
+ AbsoluteAttitude: AbsoluteAttitude$1,
4120
+ TurnDetector: TurnDectector,
4121
+ InclinationFromAcc: InclinationFromAcc$1,
4122
+ InclinationFromRelativeAttitude: InclinationFromRelativeAttitude$1,
4123
+ Inclination: Inclination$1,
4124
+ StepDetector: StepDetector$1,
4125
+ StraightLineDetector: StraightLineDetector$1,
4126
+ ArCore: ArCore$1,
4127
+ Pdr: Pdr$1,
4128
+ GeoRelativePositionFromArCore: GeoRelativePositionFromArCore$1,
4129
+ GeoRelativePosition: GeoRelativePositionProvider,
4130
+ GnssWifi: GnssWifi$1,
4131
+ Ip: Ip$1,
4132
+ AbsolutePosition: AbsolutePosition$1,
4133
+ Barcode: Barcode$1,
4134
+ CameraNative: CameraNative$1,
4135
+ CameraProjectionMatrix: CameraProjectionMatrix$1
4136
+ });
4137
+
4138
+ class ProvidersInterface {
4139
+
4140
+ static idListeners = new Map();
4141
+
4142
+ /**
4143
+ * @param {EventType} eventType
4144
+ * @param {Function} onEvent
4145
+ * @param {Function} onError
4146
+ * @param {Boolean} watchOnly if true, addEventListener does not start any provider, it just watches for events
4147
+ * @returns {Number}
4148
+ */
4149
+ static addEventListener(eventType, onEvent, onError, watchOnly = false) {
4150
+ const provider = this._getProviderFromEventType(eventType);
4151
+ const id = provider.addEventListener(events => onEvent(events[0].data), onError, !watchOnly);
4152
+ this.idListeners.set(id, provider);
4153
+ return id;
4154
+ }
4155
+
4156
+ /**
4157
+ * @see addEventListener
4158
+ * @param {Number} id
4159
+ */
4160
+ static removeEventListener(id) {
4161
+ const provider = this.idListeners.get(id);
4162
+ if (provider) {
4163
+ return provider.removeEventListener(id);
4164
+ }
4165
+ return null;
4166
+ }
4167
+
4168
+
4169
+ /**
4170
+ * @param {EventType} eventType
4171
+ * @param {Object} data
4172
+ */
4173
+ static feed(eventType, data) {
4174
+ this._checkEventTypeDataConsistency(eventType, data);
4175
+ if (eventType === EventType.Network) {
4176
+ MapMatchingHandler$1.network = data;
4177
+ return;
4178
+ }
4179
+ if (eventType === EventType.Itinerary) {
4180
+ MapMatchingHandler$1.itinerary = data;
4181
+ return;
4182
+ }
4183
+ this._getProviderFromEventType(eventType).feed(data, eventType);
4184
+ }
4185
+
4186
+ /**
4187
+ * @param {EventType} eventType
4188
+ */
4189
+ static getLastKnown(eventType) {
4190
+ const { lastEvent } = this._getProviderFromEventType(eventType);
4191
+ return lastEvent ? lastEvent.data : null;
4192
+ }
4193
+
4194
+ /**
4195
+ * @param {EventType} eventType
4196
+ * @returns {Provider}
4197
+ */
4198
+ static _getProviderFromEventType(eventType) {
4199
+
4200
+ switch (eventType) {
4201
+ case EventType.AbsoluteAttitude:
4202
+ case EventType.AbsoluteHeading:
4203
+ return AbsoluteAttitude$1;
4204
+
4205
+ case EventType.AbsolutePosition:
4206
+ return AbsolutePosition$1;
4207
+
4208
+ case EventType.RelativeAttitude:
4209
+ return RelativeAttitude$1;
4210
+
4211
+ case EventType.Inclination:
4212
+ return Inclination$1;
4213
+
4214
+ case EventType.CameraNative:
4215
+ return CameraNative$1;
4216
+
4217
+ case EventType.Barcode:
4218
+ return Barcode$1;
4219
+
4220
+ case EventType.CameraProjectionMatrix:
4221
+ return CameraProjectionMatrix$1;
4222
+
4223
+ default:
4224
+ throw new Error(`Unable to deal with this event type: ${eventType}`);
4225
+ }
4226
+ }
4227
+
4228
+ static _checkEventTypeDataConsistency(eventType, data) {
4229
+ const errorFn = expectedType =>
4230
+ new Error(`Event type (${eventType}) is not an instance of ${expectedType}`);
4231
+
4232
+ switch (eventType) {
4233
+ case EventType.AbsoluteAttitude:
4234
+ if (!(data instanceof Attitude)) {
4235
+ throw errorFn('Attitude');
4236
+ }
4237
+ break;
4238
+ case EventType.AbsoluteHeading:
4239
+ if (!(data instanceof AbsoluteHeading)) {
4240
+ throw errorFn('AbsoluteHeading');
4241
+ }
4242
+ break;
4243
+ case EventType.AbsolutePosition:
4244
+ if (!(data instanceof UserPosition)) {
4245
+ throw errorFn('UserPosition');
4246
+ }
4247
+ break;
4248
+ case EventType.Network:
4249
+ if (!(data instanceof Network || data === null)) {
4250
+ throw errorFn('Network');
4251
+ }
4252
+ break;
4253
+ case EventType.Itinerary:
4254
+ if (!(data instanceof Itinerary || data === null)) {
4255
+ throw errorFn('Itinerary');
4256
+ }
4257
+ break;
4258
+ default:
4259
+ throw new Error(`Unable to deal with this event type: ${eventType}`);
4260
+ }
4261
+ }
4262
+
4263
+ /**
4264
+ * @param {Boolean} enabled
4265
+ */
4266
+ static set logger(enabled) {
4267
+ ProvidersLoggerOld$1.enabled = enabled;
4268
+ }
4269
+
4270
+ static get mapMatchingMaxDistance() {
4271
+ return MapMatchingHandler$1.maxDistance;
4272
+ }
4273
+
4274
+ static set mapMatchingMaxDistance(maxDistance) {
4275
+ MapMatchingHandler$1.maxDistance = maxDistance;
4276
+ }
4277
+
4278
+ static get mapMatchingMinDistance() {
4279
+ return MapMatchingHandler$1.minDistance;
4280
+ }
4281
+
4282
+ static set mapMatchingMinDistance(minDistance) {
4283
+ MapMatchingHandler$1.minDistance = minDistance;
4284
+ }
4285
+
4286
+ static get mapMatchingMaxAngleBearing() {
4287
+ return MapMatchingHandler$1.maxAngleBearing;
4288
+ }
4289
+
4290
+ static set mapMatchingMaxAngleBearing(maxAngleBearing) {
4291
+ MapMatchingHandler$1.maxAngleBearing = maxAngleBearing;
4292
+ }
4293
+ }
4294
+
4295
+ /* eslint-disable max-statements */
4296
+
4297
+ class PositionSmoother {
4298
+
4299
+
4300
+ // Generated positions by second
4301
+ static DEFAULT_FREQUENCY = 60;
4302
+
4303
+ // flyby (in second)
4304
+ static DEFAULT_FLYBY_TIME = 1;
4305
+
4306
+
4307
+ /**
4308
+ * @param {Function} callback
4309
+ * @param {Number} frequency in Hz
4310
+ */
4311
+ constructor(callback, frequency = this.constructor.DEFAULT_FREQUENCY) {
4312
+ this.callback = callback;
4313
+ this.positionsQueue = [];
4314
+ this.frequency = frequency;
4315
+ }
4316
+
4317
+ /**
4318
+ * @param {UserPosition} newPosition
4319
+ * @param {Number} flybyTime in seconds
4320
+ */
4321
+ feed(newPosition, flybyTime = this.constructor.DEFAULT_FLYBY_TIME) {
4322
+
4323
+ if (!(newPosition instanceof UserPosition)) {
4324
+ throw new TypeError('newPosition is not instance of UserPosition');
4325
+ }
4326
+
4327
+ if (newPosition.time === null) {
4328
+ throw new Error('newPosition does not have time property');
4329
+ }
4330
+
4331
+ let previousPosition;
4332
+ if (this.positionsQueue.length !== 0) {
4333
+ previousPosition = this.positionsQueue[0];
4334
+ this.positionsQueue = [];
4335
+ } else {
4336
+ previousPosition = this.previousPosition;
4337
+ }
4338
+
4339
+ if (previousPosition) {
4340
+
4341
+ const refTimestamp = newPosition.time;
4342
+ const distance = previousPosition.distanceTo(newPosition);
4343
+ const azimuth = previousPosition.bearingTo(newPosition);
4344
+
4345
+ let i = 1;
4346
+ const nSamples = this.frequency * flybyTime + 1;
4347
+
4348
+ let newPositionLevel = null;
4349
+ if (newPosition.level !== null) {
4350
+ newPositionLevel = newPosition.level.clone();
4351
+ }
4352
+
4353
+ while (i < nSamples + 1) {
4354
+ const smoothedPosition = previousPosition.destinationPoint(distance * i / nSamples, azimuth);
4355
+ smoothedPosition.time = refTimestamp + (i - 1) / this.frequency;
4356
+ smoothedPosition.level = newPositionLevel;
4357
+ smoothedPosition.bearing = newPosition.bearing;
4358
+ smoothedPosition.accuracy = Math.max(
4359
+ smoothedPosition.accuracy + (newPosition.accuracy - smoothedPosition.accuracy) * i / nSamples,
4360
+ 0
4361
+ );
4362
+ this.positionsQueue.push(smoothedPosition);
4363
+ i++;
4364
+ }
4365
+
4366
+ if (this.timeoutNotify) {
4367
+ clearTimeout(this.timeoutNotify);
4368
+ }
4369
+ } else {
4370
+ this.positionsQueue.push(newPosition.clone());
4371
+ }
4372
+
4373
+ this.previousPosition = newPosition;
4374
+ this._notifyNext();
4375
+ }
4376
+
4377
+ _notifyNext = () => {
4378
+ this.callback(this.positionsQueue.shift());
4379
+ if (this.positionsQueue.length !== 0) {
4380
+ this.timeoutNotify = setTimeout(this._notifyNext, 1e3 / this.frequency);
4381
+ } else {
4382
+ delete this.timeoutNotify;
4383
+ }
4384
+ }
4385
+
4386
+ clear() {
4387
+ clearTimeout(this.timeoutNotify);
4388
+ delete this.timeoutNotify;
4389
+ this.positionsQueue = [];
4390
+ }
4391
+ }
4392
+
4393
+ /* eslint-disable max-statements */
4394
+
4395
+ class AttitudeSmoother {
4396
+
4397
+ /** @type {number} in radians/s */
4398
+ static ROTATION_SPEED_JUMP_THRESHOLD = deg2rad(180);
4399
+
4400
+ /** @type {number} in radians/s */
4401
+ static ROTATION_SPEED_CONVERGENCE = deg2rad(10);
4402
+
4403
+ /** @type {number} in radians/s */
4404
+ static HIGH_JUMP_THRESHOLD = deg2rad(20);
4405
+
4406
+ /** @type {number} in radians/s */
4407
+ static ROTATION_SPEED_HIGH_JUMP_CONVERGENCE = deg2rad(100);
4408
+
4409
+ /** @type {number} in radians */
4410
+ static PITCH_UNCERTAINITY_HEADING_THRESHOLD = deg2rad(5);
4411
+
4412
+ /** @type {Function} */
4413
+ _callback;
4414
+
4415
+ /** @type {object?} */
4416
+ _smoothing = null;
4417
+
4418
+ /**
4419
+ * @param {Function} callback
4420
+ */
4421
+ constructor(callback) {
4422
+ this._callback = callback;
4423
+ }
4424
+
4425
+ /**
4426
+ * @param {Attitude} attitude
4427
+ */
4428
+ feed(newAttitude) {
4429
+
4430
+ if (!(newAttitude instanceof Attitude)) {
4431
+ throw new TypeError('newAttitude is not instance of Attitude');
4432
+ }
4433
+
4434
+ if (newAttitude.time === null) {
4435
+ throw new Error('newAttitude does not have time property');
4436
+ }
4437
+
4438
+ const { _previousAttitude: previousAttitude } = this;
4439
+ this._previousAttitude = newAttitude;
4440
+
4441
+ if (!previousAttitude) {
4442
+ this._callback(newAttitude);
4443
+ return;
4444
+ }
4445
+
4446
+ /*
4447
+ * Comparison between two successive Attitude from the "feed" function
4448
+ */
4449
+ if (AttitudeSmoother.isJump(previousAttitude, newAttitude)) {
4450
+
4451
+ const fromAttitude = this._smoothing === null
4452
+ ? previousAttitude
4453
+ : this._smoothing.interpAttitude(previousAttitude);
4454
+
4455
+ const fromHeading = fromAttitude.heading;
4456
+ const toHeading = newAttitude.heading;
4457
+ const diffAngleHeading = diffAngle(toHeading, fromHeading);
4458
+
4459
+ const isHighJump = Math.abs(diffAngleHeading) < AttitudeSmoother.HIGH_JUMP_THRESHOLD;
4460
+ const rotationSpeedConvergence = isHighJump
4461
+ ? AttitudeSmoother.ROTATION_SPEED_CONVERGENCE
4462
+ : AttitudeSmoother.ROTATION_SPEED_HIGH_JUMP_CONVERGENCE;
4463
+
4464
+ const fromTime = fromAttitude.time;
4465
+ const timeToConsume = Math.abs(diffAngleHeading) / rotationSpeedConvergence;
4466
+ const multiplier = diffAngleHeading < 0 ? -1 : 1;
4467
+
4468
+ this._smoothing = {
4469
+ toTime: fromTime + timeToConsume,
4470
+
4471
+ /**
4472
+ * @param {Attitude} attitude
4473
+ */
4474
+ interpAttitude: attitude => {
4475
+ const angle = rotationSpeedConvergence * (attitude.time - fromTime);
4476
+ const interpHeading = fromHeading + angle * multiplier;
4477
+ const offsetQuat = Quaternion.fromAxisAngle([0, 0, 1], toHeading - interpHeading);
4478
+ const interpQuat = Quaternion.multiply(offsetQuat, attitude.quaternion);
4479
+ return new Attitude(interpQuat, attitude.time, attitude.accuracy);
4480
+ }
4481
+ };
4482
+ }
4483
+
4484
+ if (this._smoothing !== null) {
4485
+ if (newAttitude.time >= this._smoothing.toTime) {
4486
+ // This means that is the last epoch for smoothing
4487
+ this._smoothing = null;
4488
+ } else {
4489
+ const interpAttitude = this._smoothing.interpAttitude(newAttitude);
4490
+ this._callback(interpAttitude);
4491
+ return;
4492
+ }
4493
+ }
4494
+
4495
+ this._callback(newAttitude);
4496
+ }
4497
+
4498
+ /**
4499
+ * @param {Attitude} previousAttitude
4500
+ * @param {Attitude} newAttitude
4501
+ * @returns {boolean}
4502
+ */
4503
+ static isJump(previousAttitude, newAttitude) {
4504
+ const fromHeading = previousAttitude.heading;
4505
+ const toHeading = newAttitude.heading;
4506
+ const diffAngleHeading = diffAngle(toHeading, fromHeading);
4507
+ const diffTime = newAttitude.time - previousAttitude.time;
4508
+
4509
+ /**
4510
+ * Heading is calculated from two different formulas in function of the pitch angle.
4511
+ * Do not consider a jump if attitude is close to the change of the methods
4512
+ * @see MathsRotations#getHeadingFromQuaternion()
4513
+ */
4514
+ const [qw, qx, qy, qz] = newAttitude.quaternion;
4515
+ const distToPitchThreshold = Math.abs(Math.asin(2 * (qw * qx + qy * qz)) - Math.PI / 4);
4516
+ if (distToPitchThreshold < AttitudeSmoother.PITCH_UNCERTAINITY_HEADING_THRESHOLD) {
4517
+ return false;
4518
+ }
4519
+
4520
+ return Math.abs(diffAngleHeading) > AttitudeSmoother.ROTATION_SPEED_JUMP_THRESHOLD * diffTime;
4521
+ }
4522
+
4523
+ }
4524
+
4525
+ export { AttitudeSmoother, EventType, PositionSmoother, ProviderEvent, Providers, ProvidersInterface, ProvidersLoggerOld$1 as ProvidersLoggerOld, ProvidersOptions };
4526
+ //# sourceMappingURL=wemap-providers.es.js.map