@wemap/positioning 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/.eslintrc.json +479 -0
  2. package/.nvmrc +1 -0
  3. package/babel.config.js +11 -0
  4. package/config.json +7 -0
  5. package/debug/index.html +15 -0
  6. package/debug/index.old.html +37 -0
  7. package/package.json +82 -0
  8. package/scripts/release-github.js +216 -0
  9. package/src/Constants.js +11 -0
  10. package/src/NavigationHandler.js +244 -0
  11. package/src/Pose.js +8 -0
  12. package/src/attitude/Attitude.js +65 -0
  13. package/src/attitude/AttitudeHandler.js +343 -0
  14. package/src/attitude/EkfAttitude.js +238 -0
  15. package/src/attitude/EkfAttitude.spec.js +116 -0
  16. package/src/components/AbsoluteAttitude.jsx +136 -0
  17. package/src/components/Imu.jsx +89 -0
  18. package/src/components/LocationSource.jsx +434 -0
  19. package/src/components/Logger.jsx +113 -0
  20. package/src/components/NavigationDebugApp.jsx +106 -0
  21. package/src/components/Others.jsx +121 -0
  22. package/src/components/RelativeAttitude.jsx +104 -0
  23. package/src/components/Utils.js +35 -0
  24. package/src/components/index.js +13 -0
  25. package/src/index.js +9 -0
  26. package/src/providers/FixedLocationImuLocationSource.js +66 -0
  27. package/src/providers/GnssLocationSource.js +118 -0
  28. package/src/providers/GnssPdrLocationSource.js +182 -0
  29. package/src/providers/IPLocationSource.js +96 -0
  30. package/src/providers/LocationSource.js +290 -0
  31. package/src/providers/PdrLocationSource.js +312 -0
  32. package/src/providers/ProvidersLogger.js +77 -0
  33. package/src/providers/pdr/HeadingUnlocker.js +41 -0
  34. package/src/providers/pdr/HeadingUnlocker.spec.js +26 -0
  35. package/src/providers/pdr/Smoother.js +90 -0
  36. package/src/providers/pdr/Smoother.spec.js +424 -0
  37. package/src/providers/pdr/ThugDetector.js +37 -0
  38. package/src/providers/steps/StepDetection.js +7 -0
  39. package/src/providers/steps/StepDetectionLadetto.js +67 -0
  40. package/src/providers/steps/StepDetectionMinMaxPeaks.js +80 -0
  41. package/src/providers/steps/StepDetectionMinMaxPeaks2.js +108 -0
  42. package/src/sensors/SensorsCompatibility.js +484 -0
  43. package/src/sensors/SensorsCompatibility.spec.js +270 -0
  44. package/src/sensors/SensorsLogger.js +94 -0
  45. package/src/sensors/SensorsLoggerUtils.js +35 -0
  46. package/src.new/NavigationHandler.js +62 -0
  47. package/src.new/index.js +3 -0
  48. package/src.new/providers/FakeLocationSource.js +39 -0
  49. package/webpack/webpack.common.js +20 -0
  50. package/webpack/webpack.dev.js +24 -0
  51. package/webpack/webpack.prod.js +15 -0
@@ -0,0 +1,108 @@
1
+
2
+ // in seconds
3
+ const WINDOW_TIME = 0.3;
4
+
5
+ // in seconds
6
+ const MIN_TIME_BETWEEN_STEPS = 0.4;
7
+
8
+ // in Hz
9
+ const MAX_FRENQUENCY = 4;
10
+ const MIN_FRENQUENCY = 1;
11
+
12
+ // in m.s-2
13
+ const VERTICAL_ACC_POSITIVE_PEAK_THRESHOLD = 0.75;
14
+ const VERTICAL_ACC_NEGATIVE_PEAK_THRESHOLD = -0.3;
15
+
16
+
17
+ class StepDetectionMinMaxPeaks2 {
18
+
19
+ constructor() {
20
+ this.slidingWindow = [];
21
+
22
+ this.lastStepTimestamp = -MIN_TIME_BETWEEN_STEPS;
23
+ this.previousVerticalAcc = 0;
24
+ this.influence = 0.2;
25
+ }
26
+
27
+
28
+ compute(timestamp, linearAcc, angularRate) {
29
+
30
+ const verticalAcc = this.influence * (linearAcc[2] * 2) + (1 - this.influence) * this.previousVerticalAcc;
31
+ this.previousVerticalAcc = verticalAcc;
32
+
33
+
34
+ if (Math.sqrt(angularRate[0] ** 2 + angularRate[1] ** 2 + angularRate[2] ** 2) > 0.75) {
35
+ return false;
36
+ }
37
+
38
+ if (this.lastStepTimestamp && this.lastStepTimestamp + MIN_TIME_BETWEEN_STEPS > timestamp) {
39
+ return false;
40
+ }
41
+
42
+ let maxValue = Number.MIN_SAFE_INTEGER;
43
+ let minValue = Number.MAX_SAFE_INTEGER;
44
+
45
+ this.slidingWindow.forEach(function(item, index, object) {
46
+ if (item.timestamp < timestamp - WINDOW_TIME) {
47
+ object.splice(index, 1);
48
+ } else {
49
+ maxValue = Math.max(item.verticalAcc, maxValue);
50
+ minValue = Math.min(item.verticalAcc, minValue);
51
+ }
52
+ });
53
+ this.slidingWindow.push({
54
+ timestamp: timestamp,
55
+ verticalAcc: verticalAcc
56
+ });
57
+
58
+
59
+ if (maxValue > VERTICAL_ACC_POSITIVE_PEAK_THRESHOLD
60
+ && minValue < VERTICAL_ACC_NEGATIVE_PEAK_THRESHOLD) {
61
+
62
+ const timeInterval = this.lastStepTimestamp ? timestamp - this.lastStepTimestamp : 1;
63
+ this.frequency = Math.min(Math.max((1 / timeInterval), MIN_FRENQUENCY), MAX_FRENQUENCY);
64
+
65
+ this.lastStepTimestamp = timestamp;
66
+ return true;
67
+ }
68
+
69
+ return false;
70
+ }
71
+
72
+ get lastStepSize() {
73
+
74
+ if (!this.frequency) {
75
+ return 0;
76
+ }
77
+
78
+ const kParamA = 0.45;
79
+ const kParamB = 0.2;
80
+ return kParamA + kParamB * this.frequency;
81
+ }
82
+
83
+ get speed() {
84
+ return this.lastStepSize && this.frequency ? this.lastStepSize * this.frequency : 0;
85
+ }
86
+
87
+ mean(data) {
88
+ let sum = 0.0, mean = 0.0;
89
+
90
+ for (let i = 0; i < data.length; ++i) {
91
+ sum += data[i].verticalAcc;
92
+ }
93
+ mean = sum / data.length;
94
+ return mean;
95
+ }
96
+
97
+ stddev(data) {
98
+ const theMean = this.mean(data);
99
+ let standardDeviation = 0;
100
+ for (let i = 0; i < data.length; ++i) {
101
+ standardDeviation += (data[i].verticalAcc - theMean) ** 2;
102
+ }
103
+
104
+ return Math.sqrt(standardDeviation / data.length);
105
+ }
106
+ }
107
+
108
+ export default StepDetectionMinMaxPeaks2;
@@ -0,0 +1,484 @@
1
+ /* global Accelerometer, Gyroscope, Magnetometer, AbsoluteOrientationSensor, RelativeOrientationSensor */
2
+
3
+ import { Quaternion, Rotations, Utils as MathUtils } from '@wemap/maths';
4
+
5
+ import SensorsLogger from './SensorsLogger';
6
+
7
+
8
+ const BROWSER = {
9
+ UNKNOWN: 0,
10
+ IOS_SAFARI: 1,
11
+ FIREFOX: 2,
12
+ OPERA: 3,
13
+ ANDROID_CHROME: 4,
14
+ ANDROID_STOCK: 5
15
+ };
16
+
17
+ const defaultMotionSensors = {
18
+ accelerometer: true,
19
+ gyroscope: true,
20
+ magnetometer: true
21
+ };
22
+
23
+ /**
24
+ * 5 April 2019
25
+ * The Generic Sensors API methods have been disabled due to a cross-origin problem.
26
+ */
27
+ /**
28
+ * 13 August 2019
29
+ * The Generic Sensors API methods have been disabled due to empty notifications when app is fullscreen and have no focus.
30
+ */
31
+ const ENABLE_GENERIC_SENSORS_API = false;
32
+
33
+ /**
34
+ * Keep a trace of each instance for SensorsLogger
35
+ * TODO: Should be removed when logger will be updated with providers
36
+ */
37
+ const instances = [];
38
+
39
+
40
+ // -----------------------------------
41
+ // -----------------------------------
42
+ // Overview of compatibilities:
43
+ // -----------------------------------
44
+ //
45
+ // --------
46
+ // Chrome Android (v72.0.3626)
47
+ // --------
48
+ // Accelerometer + Gyroscope: YES (via Sensors API if present or devicemotion otherwise)
49
+ // Relative orientation: YES (via RelativeOrientationSensor if present or deviceorientation otherwise but deviceorientation.alpha is unreliable! Sometimes it starts at 0°, sometimes at 270°)
50
+ // Absolute orientation: YES (via AbsoluteOrientationSensor if present or deviceorientationabsolute otherwise)
51
+ // --------
52
+ //
53
+ // --------
54
+ // Safari iOS (v12.0)
55
+ // --------
56
+ // Accelerometer + Gyroscope: YES (via devicemotion)
57
+ // Relative orientation: YES (via deviceorientation)
58
+ // Absolute orientation: YES (via deviceorientation and event.webkitCompassHeading)
59
+ // --------
60
+ //
61
+ // --------
62
+ // Opera (v50.2.2426)
63
+ // --------
64
+ // Accelerometer + Gyroscope: NO (https://developer.mozilla.org/en-US/docs/Web/API/DeviceMotionEvent)
65
+ // Relative orientation: NO (https://developer.mozilla.org/en-US/docs/Web/API/Window/ondeviceorientation)
66
+ // Absolute orientation: NO (https://developer.mozilla.org/en-US/docs/Web/API/Window/ondeviceorientation)
67
+ // devicemotion and deviceorientation send only one callback and data is empty.
68
+ // --------
69
+ //
70
+ // --------
71
+ // Firefox (v65.0.1)
72
+ // --------
73
+ // Accelerometer + Gyroscope: YES (via devicemotion)
74
+ // Relative orientation: YES (via deviceorientation)
75
+ // Absolute orientation: NO (https://www.fxsitecompat.com/en-CA/docs/2018/various-device-sensor-apis-are-now-deprecated/)
76
+ // --------
77
+ // -----------------------------------
78
+
79
+ class SensorsCompatibility {
80
+
81
+ imuTickInterval = null;
82
+
83
+ constructor() {
84
+ this.browser = this.retrieveBrowser();
85
+ instances.push(this);
86
+
87
+ this.lastGyroscopeValue = null;
88
+ }
89
+
90
+ // Parse the user agent to get OS and browser so we know how to normalize values
91
+ retrieveBrowser() {
92
+ const userAgent = window.navigator.userAgent;
93
+ // IOS uses a special property
94
+ if (userAgent.match(/(iPad|iPhone|iPod)/i)) {
95
+ return BROWSER.IOS_SAFARI;
96
+ } else if (userAgent.match(/Firefox/i)) {
97
+ return BROWSER.FIREFOX;
98
+ } else if (userAgent.match(/Opera/i)) {
99
+ return BROWSER.OPERA;
100
+ } else if (userAgent.match(/Android/i)) {
101
+ // TODO: check with android_stock browser
102
+ return BROWSER.ANDROID_CHROME;
103
+ }
104
+ return BROWSER.UNKNOWN;
105
+ }
106
+
107
+ setLogger(logger) {
108
+
109
+ if (logger && !(logger instanceof SensorsLogger)) {
110
+ throw new Error('logger is not an instance of SensorsLogger');
111
+ }
112
+
113
+ this.logger = logger;
114
+ }
115
+
116
+ static addLoggerOnKnownInstances(logger) {
117
+ instances.forEach(instance => instance.addLogger(logger));
118
+ }
119
+
120
+ hasArSensors() {
121
+ return this.startAbsoluteOrientation(this.stopAbsoluteOrientation);
122
+ }
123
+
124
+ /*
125
+ * Manage Accelerometer - Gyroscope - Magnetometer
126
+ */
127
+
128
+ startImu(callback, _sensors = defaultMotionSensors, frequency = 60) {
129
+
130
+ let isResolved = false;
131
+
132
+ const sensors = !_sensors ? defaultMotionSensors : _sensors;
133
+
134
+
135
+ if (ENABLE_GENERIC_SENSORS_API) {
136
+ if (sensors.accelerometer && window.Accelerometer) {
137
+ this.accelerometerSensor = new Accelerometer({ frequency: frequency });
138
+ this.accelerometerSensor.start();
139
+ }
140
+
141
+ if (sensors.gyroscope && window.Gyroscope) {
142
+ this.gyroscopeSensor = new Gyroscope({ frequency: frequency });
143
+ this.gyroscopeSensor.start();
144
+ }
145
+
146
+ if (sensors.magnetometer && window.Magnetometer) {
147
+ this.magnetometerSensor = new Magnetometer({ frequency: frequency });
148
+ this.magnetometerSensor.start();
149
+ }
150
+ }
151
+
152
+ return new Promise((resolve) => {
153
+
154
+ const notifyFn = (output) => {
155
+ if (sensors.accelerometer && !output.acc
156
+ || sensors.gyroscope && !output.gyr
157
+ || sensors.magnetometer && !output.mag) {
158
+ return;
159
+ }
160
+
161
+ callback(output);
162
+
163
+ if (this.logger) {
164
+ if (output.acc) {
165
+ this.logger.feed(SensorsLogger.DataType.ACCELEROMETER, output.timestamp, output.acc, true);
166
+ }
167
+ if (output.gyr) {
168
+ this.logger.feed(SensorsLogger.DataType.GYROSCOPE, output.timestamp, output.gyr, true);
169
+ }
170
+ if (output.mag) {
171
+ this.logger.feed(SensorsLogger.DataType.MAGNETOMETER, output.timestamp, output.mag, true);
172
+ }
173
+ }
174
+
175
+ if (!isResolved) {
176
+ resolve();
177
+ isResolved = true;
178
+ }
179
+ };
180
+
181
+ if (this.accelerometerSensor || this.gyroscopeSensor || this.magnetometerSensor) {
182
+
183
+ // Create a scheduler using frequency parameter
184
+ const schedulerFn = () => {
185
+ this.imuTickInterval = setInterval(() => {
186
+ notifyFn(this.onImuTick());
187
+ }, 1 / frequency * 1000);
188
+ };
189
+
190
+ schedulerFn();
191
+
192
+ } else if (sensors.accelerometer || sensors.gyroscope || sensors.magnetometer) {
193
+
194
+ this.onDeviceMotionEventListener = (e) => {
195
+ notifyFn(this.onDeviceMotion(e));
196
+ };
197
+ window.addEventListener('devicemotion', this.onDeviceMotionEventListener, true);
198
+ }
199
+
200
+ });
201
+ }
202
+
203
+ stopImu() {
204
+
205
+ if (this.imuTickInterval) {
206
+ clearInterval(this.imuTickInterval);
207
+ this.imuTickInterval = null;
208
+ }
209
+
210
+ if (this.accelerometerSensor || this.gyroscopeSensor || this.magnetometerSensor) {
211
+ if (this.accelerometerSensor) {
212
+ this.accelerometerSensor.stop();
213
+ this.accelerometerSensor = null;
214
+ }
215
+ if (this.gyroscopeSensor) {
216
+ this.gyroscopeSensor.stop();
217
+ this.gyroscopeSensor = null;
218
+ }
219
+ if (this.magnetometerSensor) {
220
+ this.magnetometerSensor.stop();
221
+ this.magnetometerSensor = null;
222
+ }
223
+ } else if (this.onDeviceMotionEventListener) {
224
+ window.removeEventListener('devicemotion', this.onDeviceMotionEventListener, true);
225
+ }
226
+ }
227
+
228
+ onImuTick() {
229
+ const acc = this.accelerometerSensor;
230
+ const accValues = acc && acc.x ? [acc.x, acc.y, acc.z] : null;
231
+
232
+ const gyr = this.gyroscopeSensor;
233
+ const gyrValues = gyr && gyr.x ? [gyr.x, gyr.y, gyr.z] : null;
234
+
235
+ const mag = this.magnetometerSensor;
236
+ const magValues = mag && mag.x ? [mag.x, mag.y, mag.z] : null;
237
+
238
+ let timestamp = 0;
239
+ if (accValues) {
240
+ timestamp = acc.timestamp;
241
+ } else if (gyrValues) {
242
+ timestamp = gyr.timestamp;
243
+ } else if (magValues) {
244
+ timestamp = mag.timestamp;
245
+ }
246
+
247
+ // Alignment of acc and gyr is made by taking the last value of accelerometer, gyroscope and magnetometer.
248
+ // This is not perfect but far enough for almost all algorithms.
249
+ const output = {
250
+ timestamp: timestamp / 1e3,
251
+ acc: accValues,
252
+ gyr: gyrValues,
253
+ mag: magValues
254
+ };
255
+
256
+ return output;
257
+ }
258
+
259
+ /**
260
+ * Verified for IOS_SAFARI, ANDROID_CHROME, FIREFOX
261
+ * Not working for OPERA
262
+ * @param {DeviceMotionEvent} e device motion event
263
+ */
264
+ onDeviceMotion(e) {
265
+ const timestamp = e.timeStamp / 1e3;
266
+
267
+ let acc = null;
268
+
269
+ if (e.accelerationIncludingGravity) {
270
+ const {
271
+ x, y, z
272
+ } = e.accelerationIncludingGravity;
273
+
274
+ if (x && y && z) {
275
+ acc = [x, y, z];
276
+ }
277
+ }
278
+
279
+ if (this.browser === BROWSER.IOS_SAFARI && acc) {
280
+ acc[0] *= -1;
281
+ acc[1] *= -1;
282
+ acc[2] *= -1;
283
+ }
284
+
285
+
286
+ let gyr = null;
287
+
288
+ if (e.rotationRate) {
289
+ const {
290
+ alpha, beta, gamma
291
+ } = e.rotationRate;
292
+
293
+ if (alpha && beta && gamma) {
294
+ gyr = [alpha * Math.PI / 180, beta * Math.PI / 180, gamma * Math.PI / 180];
295
+ }
296
+ }
297
+
298
+ const output = {
299
+ timestamp: timestamp,
300
+ acc: acc,
301
+ gyr: gyr,
302
+ mag: null
303
+ };
304
+ return output;
305
+ }
306
+
307
+
308
+ /*
309
+ * Manage absolute orientation
310
+ */
311
+
312
+ startAbsoluteOrientation(callback, frequency = 60) {
313
+
314
+
315
+ let isResolved = false;
316
+
317
+ return new Promise((resolve, reject) => {
318
+
319
+ const notifyFn = (timestamp, quaternion, extraData) => {
320
+ if (!quaternion) {
321
+ return;
322
+ }
323
+
324
+ callback(quaternion, timestamp, extraData);
325
+
326
+ if (this.logger) {
327
+ this.logger.feed(SensorsLogger.DataType.DEVICE_ORIENTATION_ABSOLUTE, timestamp, quaternion, true);
328
+ }
329
+
330
+ if (!isResolved) {
331
+ resolve();
332
+ isResolved = true;
333
+ }
334
+ };
335
+
336
+ if (ENABLE_GENERIC_SENSORS_API && window.AbsoluteOrientationSensor) {
337
+ // Cool! The device is compatible with Sensor_API.
338
+
339
+ this.absoluteOrientationSensor = new AbsoluteOrientationSensor({ frequency: frequency });
340
+ this.absoluteOrientationSensor.addEventListener('reading', (e) => {
341
+ if (!e.target.quaternion) {
342
+ return;
343
+ }
344
+ notifyFn(e.timeStamp, Quaternion.xyzw2wxyz(e.target.quaternion));
345
+ });
346
+ this.absoluteOrientationSensor.start();
347
+
348
+ } else {
349
+
350
+ switch (this.browser) {
351
+
352
+ case BROWSER.ANDROID_CHROME:
353
+
354
+ this.onDeviceOrientationAbsoluteEventListener = (e) => {
355
+ if (!e.alpha || !e.beta || !e.gamma) {
356
+ return;
357
+ }
358
+ notifyFn(e.timeStamp / 1e3, Rotations.eulerToQuaternionZXYDegrees([e.alpha, e.beta, e.gamma]));
359
+ };
360
+
361
+ window.addEventListener('deviceorientationabsolute', this.onDeviceOrientationAbsoluteEventListener, true);
362
+
363
+ break;
364
+
365
+ case BROWSER.IOS_SAFARI:
366
+
367
+ this.onDeviceOrientationAbsoluteSafariEventListener = (e) => {
368
+
369
+ if (!e.webkitCompassHeading || !e.beta || !e.gamma) {
370
+ return;
371
+ }
372
+
373
+ let webkitCompassHeading = -e.webkitCompassHeading;
374
+ if (e.beta > 62) {
375
+ webkitCompassHeading = SensorsCompatibility.headingEusToEnu(180 - webkitCompassHeading, e.beta, e.gamma);
376
+ }
377
+ const quaternion = Rotations.eulerToQuaternionZXYDegrees([webkitCompassHeading, e.beta, e.gamma]);
378
+ notifyFn(e.timeStamp / 1e3, quaternion, { 'webkitCompassHeading': e.webkitCompassHeading });
379
+ };
380
+
381
+ window.addEventListener('deviceorientation', this.onDeviceOrientationAbsoluteSafariEventListener, true);
382
+
383
+ // Be Careful: webkitCompassHeading is not continuous.
384
+ // It value changes with thresholds: beta > 30deg and beta < 62deg
385
+ console.warn('[Sensors] Be careful, you are using webkitCompassHeading on iOS and this method is unreliable (not continuous)');
386
+
387
+ break;
388
+
389
+ default:
390
+ reject(new Error('Your browser cannot provide absolute orientation or we do not know how to parse its data'));
391
+ }
392
+ }
393
+ });
394
+ }
395
+
396
+ stopAbsoluteOrientation() {
397
+ if (this.absoluteOrientationSensor) {
398
+ this.absoluteOrientationSensor.stop();
399
+ this.absoluteOrientationSensor = null;
400
+ } else if (this.onDeviceOrientationAbsoluteEventListener) {
401
+ window.removeEventListener('deviceorientationabsolute', this.onDeviceOrientationAbsoluteEventListener, true);
402
+ } else if (this.onDeviceOrientationAbsoluteSafariEventListener) {
403
+ window.removeEventListener('deviceorientation', this.onDeviceOrientationAbsoluteSafariEventListener, true);
404
+ }
405
+ }
406
+
407
+ static headingEusToEnu(_alpha, _beta, _gamma) {
408
+ const alpha = MathUtils.deg2rad(_alpha);
409
+ const beta = MathUtils.deg2rad(_beta);
410
+ const gamma = MathUtils.deg2rad(_gamma);
411
+
412
+ return MathUtils.rad2deg(Math.atan2(
413
+ Math.cos(alpha) * Math.sin(gamma) + Math.cos(gamma) * Math.sin(alpha) * Math.sin(beta),
414
+ Math.sin(alpha) * Math.sin(gamma) - Math.cos(alpha) * Math.cos(gamma) * Math.sin(beta)));
415
+ }
416
+
417
+ /*
418
+ * Manage relative orientation
419
+ */
420
+
421
+ startRelativeOrientation(callback, frequency = 60) {
422
+
423
+ let isResolved = false;
424
+
425
+ return new Promise((resolve) => {
426
+
427
+ const notifyFn = (timestamp, quaternion) => {
428
+ if (!quaternion) {
429
+ return;
430
+ }
431
+
432
+ if (!isResolved) {
433
+ resolve();
434
+ isResolved = true;
435
+ }
436
+
437
+ callback(quaternion, timestamp);
438
+
439
+ if (this.logger) {
440
+ this.logger.feed(SensorsLogger.DataType.DEVICE_ORIENTATION_RELATIVE, timestamp, quaternion, true);
441
+ }
442
+ };
443
+
444
+ if (ENABLE_GENERIC_SENSORS_API && window.RelativeOrientationSensor) {
445
+ // Cool! The device is compatible with Sensor_API.
446
+
447
+ this.relativeOrientationSensor = new RelativeOrientationSensor({ frequency: frequency });
448
+ this.relativeOrientationSensor.addEventListener('reading', (e) => {
449
+ if (!e.target.quaternion) {
450
+ return;
451
+ }
452
+ notifyFn(e.timeStamp / 1e3, Quaternion.xyzw2wxyz(e.target.quaternion));
453
+ });
454
+ this.relativeOrientationSensor.start();
455
+
456
+ } else {
457
+
458
+ this.onDeviceOrientationRelativeEventListener = (e) => {
459
+ if (!e.alpha || !e.beta || !e.gamma) {
460
+ return;
461
+ }
462
+ notifyFn(e.timeStamp / 1e3, Rotations.eulerToQuaternionZXYDegrees([e.alpha, e.beta, e.gamma]));
463
+ };
464
+ window.addEventListener('deviceorientation', this.onDeviceOrientationRelativeEventListener, true);
465
+
466
+ }
467
+
468
+ if (this.browser === BROWSER.ANDROID_CHROME) {
469
+ console.warn('[Sensors] Be careful, you are using relative orientation on Android Chrome and this method is unreliable');
470
+ }
471
+ });
472
+ }
473
+
474
+ stopRelativeOrientation() {
475
+ if (this.relativeOrientationSensor) {
476
+ this.relativeOrientationSensor.stop();
477
+ this.relativeOrientationSensor = null;
478
+ } else if (this.onDeviceOrientationRelativeEventListener) {
479
+ window.removeEventListener('deviceorientation', this.onDeviceOrientationRelativeEventListener, true);
480
+ }
481
+ }
482
+ }
483
+
484
+ export default SensorsCompatibility;