@wemap/providers 5.8.0 → 6.0.2

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