@wemap/providers 3.0.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 (102) hide show
  1. package/babel.config.js +11 -0
  2. package/config.json +4 -0
  3. package/debug/absolute-attitude.html +16 -0
  4. package/debug/absolute-position.html +16 -0
  5. package/debug/attitude.html +16 -0
  6. package/debug/components/AbsoluteAttitudeComponent.jsx +142 -0
  7. package/debug/components/AbsolutePositionComponent.jsx +79 -0
  8. package/debug/components/AttitudeComponent.jsx +40 -0
  9. package/debug/components/Common.css +27 -0
  10. package/debug/components/GnssWifiComponent.jsx +53 -0
  11. package/debug/components/ImuComponent.jsx +53 -0
  12. package/debug/components/InclinationComponent.jsx +68 -0
  13. package/debug/components/MapComponent.jsx +366 -0
  14. package/debug/components/NavigationConfig.js +112 -0
  15. package/debug/components/PoseComponent.jsx +168 -0
  16. package/debug/components/RelativeAttitudeComponent.jsx +85 -0
  17. package/debug/components/StartStopComponent.jsx +45 -0
  18. package/debug/components/StepDetectionComponent.jsx +39 -0
  19. package/debug/components/Utils.js +216 -0
  20. package/debug/components/index.js +30 -0
  21. package/debug/components/old/PositioningComponent.jsx +29 -0
  22. package/debug/components/old/PositioningInclinationComponent.jsx +82 -0
  23. package/debug/components/old/PositioningPoseComponent.jsx +117 -0
  24. package/debug/gnss-wifi.html +16 -0
  25. package/debug/imu.html +16 -0
  26. package/debug/inclination.html +16 -0
  27. package/debug/pose.html +16 -0
  28. package/debug/positioning-legacy.html +16 -0
  29. package/debug/relative-attitude.html +16 -0
  30. package/debug/step-detection.html +16 -0
  31. package/index.js +7 -0
  32. package/package.json +67 -0
  33. package/src/Providers.js +80 -0
  34. package/src/ProvidersInterface.js +125 -0
  35. package/src/ProvidersOptions.js +29 -0
  36. package/src/errors/AskImuOnDesktopError.js +9 -0
  37. package/src/errors/ContainsIgnoredProviderError.js +9 -0
  38. package/src/errors/GeolocationApiMissingError.js +9 -0
  39. package/src/errors/GeolocationPermissionDeniedError.js +9 -0
  40. package/src/errors/GeolocationPositionUnavailableError.js +9 -0
  41. package/src/errors/IpResolveServerError.js +9 -0
  42. package/src/errors/MissingAccelerometerError.js +11 -0
  43. package/src/errors/MissingArCoreError.js +9 -0
  44. package/src/errors/MissingGyroscopeError.js +11 -0
  45. package/src/errors/MissingMagnetometerError.js +9 -0
  46. package/src/errors/MissingNativeInterfaceError.js +9 -0
  47. package/src/errors/MissingSensorError.js +14 -0
  48. package/src/errors/NoProviderFoundError.js +9 -0
  49. package/src/events/Availability.js +44 -0
  50. package/src/events/EventType.js +33 -0
  51. package/src/events/ProviderEvent.js +32 -0
  52. package/src/events/ProvidersLogger.js +83 -0
  53. package/src/providers/Constants.js +5 -0
  54. package/src/providers/FakeProvider.spec.js +57 -0
  55. package/src/providers/MetaProvider.js +42 -0
  56. package/src/providers/Provider.js +314 -0
  57. package/src/providers/Provider.spec.js +136 -0
  58. package/src/providers/ProviderState.js +5 -0
  59. package/src/providers/attitude/AttitudeProvider.js +63 -0
  60. package/src/providers/attitude/EkfAttitude.js +224 -0
  61. package/src/providers/attitude/EkfAttitude.spec.js +114 -0
  62. package/src/providers/attitude/absolute/AbsoluteAttitudeFromBrowserProvider.js +224 -0
  63. package/src/providers/attitude/absolute/AbsoluteAttitudeFromRelAttProvider.js +134 -0
  64. package/src/providers/attitude/absolute/AbsoluteAttitudeProvider.js +143 -0
  65. package/src/providers/attitude/relative/RelativeAttitudeFromBrowserProvider.js +89 -0
  66. package/src/providers/attitude/relative/RelativeAttitudeFromEkfProvider.js +114 -0
  67. package/src/providers/attitude/relative/RelativeAttitudeProvider.js +103 -0
  68. package/src/providers/imu/AccelerometerProvider.js +61 -0
  69. package/src/providers/imu/GyroscopeProvider.js +61 -0
  70. package/src/providers/imu/ImuProvider.js +122 -0
  71. package/src/providers/inclination/InclinationFromAccProvider.js +87 -0
  72. package/src/providers/inclination/InclinationFromAttitudeProvider.js +77 -0
  73. package/src/providers/inclination/InclinationProvider.js +69 -0
  74. package/src/providers/legacy/AbsolutePdrProvider.js +258 -0
  75. package/src/providers/legacy/ArCoreAbsoluteProvider.js +230 -0
  76. package/src/providers/legacy/GnssWifiPdrProvider.js +217 -0
  77. package/src/providers/legacy/MapMatchingProvider.js +65 -0
  78. package/src/providers/legacy/PdrProvider.old.js +300 -0
  79. package/src/providers/legacy/PoseProvider.js +68 -0
  80. package/src/providers/legacy/helpers/HeadingUnlocker.js +47 -0
  81. package/src/providers/legacy/helpers/HeadingUnlocker.spec.js +53 -0
  82. package/src/providers/legacy/helpers/Smoother.js +92 -0
  83. package/src/providers/legacy/helpers/Smoother.spec.js +426 -0
  84. package/src/providers/legacy/helpers/ThugDetector.js +37 -0
  85. package/src/providers/others/CameraNativeProvider.js +44 -0
  86. package/src/providers/position/absolute/AbsolutePositionFromRelProvider.js +109 -0
  87. package/src/providers/position/absolute/AbsolutePositionProvider.js +172 -0
  88. package/src/providers/position/absolute/GnssWifiProvider.js +122 -0
  89. package/src/providers/position/absolute/IpProvider.js +68 -0
  90. package/src/providers/position/relative/ArCoreProvider.js +197 -0
  91. package/src/providers/position/relative/GeoRelativePositionFromArCoreProvider.js +85 -0
  92. package/src/providers/position/relative/GeoRelativePositionProvider.js +66 -0
  93. package/src/providers/position/relative/PdrProvider.js +132 -0
  94. package/src/providers/steps/StepDetectionLadetto.js +67 -0
  95. package/src/providers/steps/StepDetectionMinMaxPeaks.js +80 -0
  96. package/src/providers/steps/StepDetectionMinMaxPeaks2.js +108 -0
  97. package/src/providers/steps/StepDetectionProvider.js +100 -0
  98. package/src/smoothers/PositionSmoother.js +86 -0
  99. package/src/smoothers/PositionSmoother.spec.js +55 -0
  100. package/webpack/webpack.common.js +20 -0
  101. package/webpack/webpack.dev.js +24 -0
  102. package/webpack/webpack.prod.js +15 -0
@@ -0,0 +1,224 @@
1
+ import {
2
+ Matrix, Matrix3, Matrix4, Quaternion, Vector, Vector3
3
+ } from '@wemap/maths';
4
+
5
+
6
+ const DEFAULT_RELATIVE_NOISES = {
7
+ acc: 0.5,
8
+ gyr: 0.3
9
+ };
10
+
11
+ const DEFAULT_ABSOLUTE_NOISES = {
12
+ acc: 0.5,
13
+ gyr: 0.3,
14
+ yc: 2
15
+ };
16
+
17
+ class EkfAttitude {
18
+
19
+ constructor(accRef = [0, 0, 1], ycRef = [-1, 0, 0]) {
20
+
21
+ this.accRef = accRef;
22
+ this.cRef = ycRef;
23
+
24
+ this.P = Matrix.diag(Array(4).fill(0.1 ** 2));
25
+
26
+ this.quaternion = null;
27
+
28
+
29
+ this.noises = {
30
+ relative: null,
31
+ absolute: null
32
+ };
33
+ this.setRelativeNoises(DEFAULT_RELATIVE_NOISES);
34
+ this.setAbsoluteNoises(DEFAULT_ABSOLUTE_NOISES);
35
+ }
36
+
37
+ setRelativeNoises(relativeNoises) {
38
+ this.noises.relative = {
39
+ accelerometer: Matrix.diag(Array(3).fill(relativeNoises.acc ** 2)),
40
+ gyroscope: Matrix.diag(Array(3).fill(relativeNoises.gyr ** 2))
41
+ };
42
+ }
43
+
44
+
45
+ setAbsoluteNoises(absoluteNoises) {
46
+ this.noises.absolute = {
47
+ accelerometer: Matrix.diag(Array(3).fill(absoluteNoises.acc ** 2)),
48
+ gyroscope: Matrix.diag(Array(3).fill(absoluteNoises.gyr ** 2)),
49
+ yc: Matrix.diag(Array(3).fill(absoluteNoises.yc ** 2))
50
+ };
51
+ }
52
+
53
+ /**
54
+ * Try to initialize filter.
55
+ * To initialize, we need at least current acceleration (acc)
56
+ */
57
+ tryInitialize(acc, mag) {
58
+
59
+ const accNormalized = Vector3.normalize(acc);
60
+
61
+ if (mag) {
62
+ const magNormalized = Vector3.normalize(mag);
63
+
64
+ const H = Vector3.normalize(Vector3.cross(magNormalized, accNormalized));
65
+ const M = Vector3.cross(accNormalized, H);
66
+
67
+ const R = [
68
+ [H[0], M[0], accNormalized[0]],
69
+ [H[1], M[1], accNormalized[1]],
70
+ [H[2], M[2], accNormalized[2]]
71
+ ];
72
+
73
+ this.quaternion = Quaternion.fromMatrix3(R);
74
+
75
+ } else {
76
+
77
+ const r = Vector3.dot(accNormalized, this.accRef) + 1;
78
+ const v = Vector3.cross(accNormalized, this.accRef);
79
+
80
+ let quaternion = [r, v[0], v[1], v[2]];
81
+ quaternion = Quaternion.normalize(quaternion);
82
+
83
+ this.quaternion = quaternion;
84
+ }
85
+
86
+ return this.quaternion;
87
+ }
88
+
89
+ update(diffTime, acc, gyr, mag) {
90
+
91
+ if (!this.quaternion) {
92
+ return this.tryInitialize(acc, mag);
93
+ }
94
+
95
+ let q = this.quaternion;
96
+
97
+ /* ------------
98
+ * ESTIMATION
99
+ * ------------*/
100
+
101
+ const qArray = q;
102
+ const gyrInt = Vector3.multiplyScalar(gyr, 0.5 * diffTime);
103
+ const F = this.computeC([1, gyrInt[0], gyrInt[1], gyrInt[2]]);
104
+ const qAPriori = Matrix.multiplyVector(F, q);
105
+ const E1 = Matrix.diag([qArray[0], qArray[0], qArray[0]]);
106
+ const eSkew = Matrix3.skew([qArray[1], qArray[2], qArray[3]]);
107
+
108
+ const qPart = [-1 * qArray[1], -1 * qArray[2], -1 * qArray[3]];
109
+ const E = Matrix.concatRow([qPart], Matrix3.add(eSkew, E1));
110
+
111
+ const Qk = Matrix.multiplyScalar(
112
+ Matrix.multiply(
113
+ Matrix.multiply(E, this.noises[mag ? 'absolute' : 'relative'].gyroscope),
114
+ Matrix.transpose(E)
115
+ ),
116
+ (diffTime / 2) ** 2
117
+ );
118
+
119
+ const pAPriori = Matrix4.add(
120
+ Matrix.multiply(
121
+ Matrix.multiply(F, this.P),
122
+ Matrix.transpose(F)
123
+ ),
124
+ Qk
125
+ );
126
+
127
+ /* ------------
128
+ * CORRECTION
129
+ * ------------*/
130
+
131
+ const accNormalized = Vector3.normalize(acc);
132
+ let dz, K, H;
133
+
134
+ if (mag) {
135
+
136
+ const magNormalized = Vector3.normalize(mag);
137
+ const yc = Vector3.cross(accNormalized, magNormalized);
138
+ const ycNormalized = Vector3.normalize(yc);
139
+
140
+ const dzYc = Vector3.subtract(ycNormalized, Quaternion.rotate(qAPriori, this.cRef));
141
+ const dzAcc = Vector3.subtract(accNormalized, Quaternion.rotate(qAPriori, this.accRef));
142
+ dz = Vector.concat(dzYc, dzAcc);
143
+
144
+ const HYc = this.jacobianES(qAPriori, this.cRef);
145
+ const HAcc = this.jacobianES(qAPriori, this.accRef);
146
+ H = Matrix.concatRow(HYc, HAcc);
147
+
148
+ const RYc = Matrix.concatLine(this.noises.absolute.yc, Matrix3.zeros);
149
+ const RAcc = Matrix.concatLine(Matrix3.zeros, this.noises.absolute.accelerometer);
150
+ const R = Matrix.concatRow(RYc, RAcc);
151
+
152
+ K = Matrix.multiply(
153
+ Matrix.multiply(pAPriori, Matrix.transpose(H)),
154
+ Matrix.inverse(
155
+ Matrix.add(
156
+ Matrix.multiply(
157
+ Matrix.multiply(H, pAPriori),
158
+ Matrix.transpose(H)
159
+ ),
160
+ R
161
+ )
162
+ )
163
+ );
164
+ } else {
165
+ dz = Vector3.subtract(accNormalized, Quaternion.rotate(qAPriori, this.accRef));
166
+ H = this.jacobianES(qAPriori, this.accRef);
167
+ const R = this.noises.relative.accelerometer;
168
+
169
+ K = Matrix.multiply(
170
+ Matrix.multiply(pAPriori, Matrix.transpose(H)),
171
+ Matrix3.inverse(
172
+ Matrix3.add(
173
+ Matrix.multiply(
174
+ Matrix.multiply(H, pAPriori),
175
+ Matrix.transpose(H)
176
+ ),
177
+ R
178
+ )
179
+ )
180
+ );
181
+ }
182
+
183
+ q = Quaternion.add(
184
+ qAPriori,
185
+ Matrix.multiplyVector(K, dz)
186
+ );
187
+ const P = Matrix.multiply(
188
+ Matrix4.subtract(
189
+ Matrix4.identity,
190
+ Matrix.multiply(K, H)
191
+ ),
192
+ pAPriori
193
+ );
194
+
195
+ q = Quaternion.normalize(q);
196
+ this.quaternion = q;
197
+ this.P = P;
198
+
199
+ return q;
200
+ }
201
+
202
+ computeC(b) {
203
+ return [
204
+ [b[0], -b[1], -b[2], -b[3]],
205
+ [b[1], b[0], b[3], -b[2]],
206
+ [b[2], -b[3], b[0], b[1]],
207
+ [b[3], b[2], -b[1], b[0]]
208
+ ];
209
+ }
210
+
211
+ jacobianES(q, v) {
212
+
213
+ const [qw, qx, qy, qz] = q;
214
+ const [vx, vy, vz] = v;
215
+
216
+ return [
217
+ [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],
218
+ [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],
219
+ [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]
220
+ ];
221
+ }
222
+ }
223
+
224
+ export default EkfAttitude;
@@ -0,0 +1,114 @@
1
+ import chai from 'chai';
2
+
3
+ import { Quaternion } from '@wemap/maths';
4
+
5
+ import EkfAttitude from './EkfAttitude';
6
+
7
+ const expect = chai.expect;
8
+
9
+ const dt = [
10
+ 0.02, 0.02, 0.02, 0.02, 0.02
11
+ ];
12
+ const accData = [
13
+ [-0.034561157, 3.81073, 8.860977],
14
+ [-0.030700684, 3.814499, 8.818954],
15
+ [-0.018234253, 3.8023376, 8.85762],
16
+ [-0.020080566, 3.8205414, 8.8676605],
17
+ [-0.054519653, 3.8456726, 8.810287]
18
+ ];
19
+
20
+ const gyrData = [
21
+ [0.0047454834, 0.0028076172, 0.0022888184],
22
+ [0.005218506, 0.0020446777, 0.0012207031],
23
+ [0.0044555664, 0.0023040771, 4.4250488E-4],
24
+ [0.0044555664, 0.0027618408, 0.0011444092],
25
+ [0.0040893555, 0.0020446777, 0.0025787354]
26
+ ];
27
+
28
+ const magData = [
29
+ [-7.260132, -29.21753, -30.532837],
30
+ [-8.378601, -29.589844, -29.684448],
31
+ [-8.784485, -29.97284, -29.86145],
32
+ [-8.784485, -30.06134, -30.036926],
33
+ [-8.917236, -29.666138, -28.616333]
34
+ ];
35
+
36
+ const expectationsAbsolute = [
37
+ [0.22501367907095468, 0.048112975227173178, -0.19586497565981961, -0.95325279813673824],
38
+ [0.22521836492577899, 0.048187611771482071, -0.19604062637627487, -0.953164579168738],
39
+ [0.22546875479675244, 0.048146964451479861, -0.19603487950732199, -0.95310861733648156],
40
+ [0.22571872218138991, 0.04816067876157637, -0.19609909097002176, -0.95303554708035731],
41
+ [0.22598409746972753, 0.048415228863568645, -0.1963289784536566, -0.95291242279877153]
42
+ ];
43
+
44
+ const expectationsRelative = [
45
+ [0.979449872594084, 0.201679452107589, 0.001829118097573, 0],
46
+ [0.979412310855378, 0.201861876976980, 0.001818648953412, 0.000022005516141],
47
+ [0.979416707676713, 0.201841237873455, 0.001739180982152, 0.000051642340567],
48
+ [0.979405268570585, 0.201897124314688, 0.001692389320913, 0.000083114532679],
49
+ [0.979351148979101, 0.202158360999907, 0.001821014027950, 0.000089325563504]
50
+ ];
51
+
52
+ describe('initAbsolute', () => {
53
+ it('Should return the good value', () => {
54
+ const ekf = new EkfAttitude();
55
+ ekf.setAbsoluteNoises({
56
+ acc: 0.5,
57
+ gyr: 0.3,
58
+ yc: 2
59
+ });
60
+ const result = ekf.update(dt[0], accData[0], gyrData[0], magData[0]);
61
+ const distance = Quaternion.distance(result, expectationsAbsolute[0]);
62
+ expect(distance).to.below(0.000001);
63
+ });
64
+ });
65
+
66
+ describe('updateAbsolute', () => {
67
+ it('Should return the good value', () => {
68
+ const ekf = new EkfAttitude();
69
+ ekf.setAbsoluteNoises({
70
+ acc: 0.5,
71
+ gyr: 0.3,
72
+ yc: 2
73
+ });
74
+ let result;
75
+ let distance;
76
+
77
+ for (let i = 0; i < accData.length; i++) {
78
+ result = ekf.update(dt[i], accData[i], gyrData[i], magData[i]);
79
+ distance = Quaternion.distance(result, expectationsAbsolute[i]);
80
+ expect(distance).to.below(0.000001);
81
+ }
82
+ });
83
+ });
84
+
85
+ describe('initRelative', () => {
86
+ it('Should return the good value', () => {
87
+ const ekf = new EkfAttitude();
88
+ ekf.setRelativeNoises({
89
+ acc: 0.5,
90
+ gyr: 0.3
91
+ });
92
+ const result = ekf.update(dt[0], accData[0], gyrData[0]);
93
+ const distance = Quaternion.distance(result, expectationsRelative[0]);
94
+ expect(distance).to.below(0.000001);
95
+ });
96
+ });
97
+
98
+ describe('updateRelative', () => {
99
+ it('Should return the good value', () => {
100
+ const ekf = new EkfAttitude();
101
+ ekf.setRelativeNoises({
102
+ acc: 0.5,
103
+ gyr: 0.3
104
+ });
105
+ let result;
106
+ let distance;
107
+
108
+ for (let i = 0; i < accData.length; i++) {
109
+ result = ekf.update(dt[i], accData[i], gyrData[i]);
110
+ distance = Quaternion.distance(result, expectationsRelative[i]);
111
+ expect(distance).to.below(0.000001);
112
+ }
113
+ });
114
+ });
@@ -0,0 +1,224 @@
1
+ import geomagnetism from 'geomagnetism';
2
+ import isNumber from 'lodash.isnumber';
3
+
4
+ import {
5
+ Attitude, Coordinates
6
+ } from '@wemap/geo';
7
+ import {
8
+ deg2rad, rad2deg, Quaternion, Rotations
9
+ } from '@wemap/maths';
10
+ import {
11
+ Browser, BrowserUtils
12
+ } from '@wemap/utils';
13
+
14
+ import Provider from '../../Provider';
15
+ import EventType from '../../../events/EventType';
16
+ import AskImuOnDesktopError from '../../../errors/AskImuOnDesktopError';
17
+ import MissingMagnetometerError from '../../../errors/MissingMagnetometerError';
18
+ import MissingSensorError from '../../../errors/MissingSensorError';
19
+ import Availability from '../../../events/Availability';
20
+ import { AbsolutePosition } from '../../../Providers';
21
+
22
+
23
+ /**
24
+ * Absolute attitude provider gives the device attitude in East-North-Up (ENU) frame using
25
+ * browser deviceorientation or deviceorientationabsolute
26
+ * The provider does not work until an AbsolutePosition is given. This is necessary to
27
+ * calculate declination.
28
+ *
29
+ * -----------------------------------
30
+ * Overview of compatibilities:
31
+ * -----------------------------------
32
+ *
33
+ * Chrome Android (v72.0.3626): YES (via deviceorientationabsolute)
34
+ * Safari iOS (v12.0): YES (via deviceorientation and event.webkitCompassHeading)
35
+ * Opera Android (v50.2.2426): NO {@link https://developer.mozilla.org/en-US/docs/Web/API/Window/ondeviceorientation}
36
+ * Firefox Android (v65.0.1): NO {@link https://www.fxsitecompat.com/en-CA/docs/2018/various-device-sensor-apis-are-now-deprecated/}
37
+ *
38
+ * -----------------------------------
39
+ */
40
+ class AbsoluteAttitudeFromBrowserProvider extends Provider {
41
+
42
+ // from http://tyrex.inria.fr/mobile/benchmarks-attitude/
43
+ static DEFAULT_ACCURACY = deg2rad(15);
44
+
45
+ /**
46
+ * @override
47
+ */
48
+ static get displayName() {
49
+ return 'Absolute Attitude from Browser';
50
+ }
51
+
52
+ /**
53
+ * @override
54
+ */
55
+ static get eventsType() {
56
+ return [EventType.AbsoluteAttitude];
57
+ }
58
+
59
+ /**
60
+ * @override
61
+ */
62
+ get _availability() {
63
+ return BrowserUtils.isMobile
64
+ ? Availability.yes()
65
+ : Availability.no(new AskImuOnDesktopError());
66
+ }
67
+
68
+ /**
69
+ * @override
70
+ */
71
+ start() {
72
+ switch (BrowserUtils.name) {
73
+ case Browser.CHROME:
74
+ window.addEventListener('deviceorientationabsolute',
75
+ this.onDeviceOrientationChromeEvent, true);
76
+ break;
77
+
78
+ case Browser.SAFARI:
79
+ window.addEventListener('deviceorientation',
80
+ this.onDeviceOrientationSafariEvent, true);
81
+ break;
82
+ }
83
+
84
+
85
+ const lastAbsolutePosition = AbsolutePosition.lastEvent;
86
+ if (lastAbsolutePosition) {
87
+ this.onAbsolutePositionEvent(lastAbsolutePosition);
88
+ } else {
89
+ this.absolutePositionProviderId = AbsolutePosition.addEventListener(
90
+ events => this.onAbsolutePositionEvent(events[0]),
91
+ this.notifyError,
92
+ this.name,
93
+ false
94
+ );
95
+ }
96
+
97
+ }
98
+
99
+ /**
100
+ * @override
101
+ */
102
+ stop() {
103
+ switch (BrowserUtils.name) {
104
+ case Browser.CHROME:
105
+ window.removeEventListener('deviceorientationabsolute',
106
+ this.onDeviceOrientationChromeEvent, true);
107
+ break;
108
+
109
+ case Browser.SAFARI:
110
+ window.removeEventListener('deviceorientation',
111
+ this.onDeviceOrientationSafariEvent, true);
112
+ break;
113
+ }
114
+
115
+ if (isNumber(this.absolutePositionProviderId)) {
116
+ AbsolutePosition.removeEventListener(this.absolutePositionProviderId);
117
+ }
118
+ }
119
+
120
+
121
+ onDeviceOrientationChromeEvent = e => {
122
+
123
+ this.magQuaternionTimestamp = e.timeStamp / 1e3;
124
+
125
+ if (!isNumber(e.alpha) || !isNumber(e.beta) || !isNumber(e.gamma)) {
126
+ this.notifyError(new MissingSensorError().from('deviceorientationabsolute'));
127
+ return;
128
+ }
129
+
130
+ this.magQuaternion = Rotations.eulerToQuaternionZXYDegrees(
131
+ [e.alpha, e.beta, e.gamma]);
132
+
133
+ this.compute();
134
+ };
135
+
136
+
137
+ onDeviceOrientationSafariEvent = e => {
138
+
139
+ this.magQuaternionTimestamp = e.timeStamp / 1e3;
140
+
141
+ if (!isNumber(e.beta) || !isNumber(e.gamma)) {
142
+ this.notifyError(new MissingSensorError().from('deviceorientation'));
143
+ return;
144
+ }
145
+
146
+ if (!isNumber(e.webkitCompassHeading)) {
147
+ super.notifyError(new MissingMagnetometerError().from('deviceorientation'));
148
+ return;
149
+ }
150
+
151
+ let webkitCompassHeading = -e.webkitCompassHeading;
152
+ // Be Careful: webkitCompassHeading is not continuous.
153
+ // Reference frame changes in function of beta with thresholds: beta > 30deg and beta < 62deg
154
+ // Below 30deg, East-North-Up (ENU) frame is used
155
+ // Above 62def, East-Up-South (EUS) frame is used
156
+ // Between 30deg and 62deg either ENU or EUS can be used
157
+ if (e.beta > 62) {
158
+ webkitCompassHeading = this.constructor.headingEusToEnu(
159
+ 180 - webkitCompassHeading, e.beta, e.gamma);
160
+ }
161
+ this.magQuaternion = Rotations.eulerToQuaternionZXYDegrees(
162
+ [webkitCompassHeading, e.beta, e.gamma]);
163
+
164
+ this.compute();
165
+ };
166
+
167
+
168
+ compute() {
169
+
170
+ if (!this.declinationQuaternion || !this.magQuaternion) {
171
+ return;
172
+ }
173
+
174
+ const trueQuaternion = Quaternion.multiply(this.declinationQuaternion, this.magQuaternion);
175
+ const attitude = new Attitude(trueQuaternion, this.magQuaternionTimestamp,
176
+ this.constructor.DEFAULT_ACCURACY, this.name);
177
+ this.notify(this.createEvent(
178
+ EventType.AbsoluteAttitude,
179
+ attitude,
180
+ this.magQuaternionTimestamp,
181
+ [this.absolutePositionEvent]
182
+ ));
183
+ }
184
+
185
+ /**
186
+ * Initialized declination quaternion using current position.
187
+ * This method should be theoretically called every time the user moves.
188
+ * But in reality declination does not change as much.
189
+ * @param {Coordinates} positionEvent user position event
190
+ */
191
+ onAbsolutePositionEvent = positionEvent => {
192
+
193
+ if (!positionEvent) {
194
+ return;
195
+ }
196
+
197
+ this.absolutePositionEvent = positionEvent;
198
+
199
+ const position = positionEvent.data;
200
+ const wmmResult = geomagnetism.model().point([position.lat, position.lng]);
201
+ // Declination is given in NED frame and our code use ENU, that is why we have: "-decl"
202
+ this.declinationQuaternion = Quaternion.fromAxisAngle([0, 0, 1], - deg2rad(wmmResult.decl));
203
+
204
+ AbsolutePosition.removeEventListener(this.absolutePositionProviderId);
205
+ delete this.absolutePositionProviderId;
206
+ this.compute();
207
+ }
208
+
209
+ /**
210
+ * {@link https://math.stackexchange.com/questions/3181981/solve-a-system-of-rotation-matrices-z-x-z-z-x-y}
211
+ */
212
+ static headingEusToEnu(_alpha, _beta, _gamma) {
213
+ const alpha = deg2rad(_alpha);
214
+ const beta = deg2rad(_beta);
215
+ const gamma = deg2rad(_gamma);
216
+
217
+ return rad2deg(Math.atan2(
218
+ Math.cos(alpha) * Math.sin(gamma) + Math.cos(gamma) * Math.sin(alpha) * Math.sin(beta),
219
+ Math.sin(alpha) * Math.sin(gamma) - Math.cos(alpha) * Math.cos(gamma) * Math.sin(beta)
220
+ ));
221
+ }
222
+ }
223
+
224
+ export default AbsoluteAttitudeFromBrowserProvider;
@@ -0,0 +1,134 @@
1
+ import { Quaternion } from '@wemap/maths';
2
+ import { Attitude } from '@wemap/geo';
3
+
4
+ import Provider from '../../Provider';
5
+ import EventType from '../../../events/EventType';
6
+ import {
7
+ RelativeAttitude, AbsoluteAttitude
8
+ } from '../../../Providers';
9
+
10
+ /**
11
+ * Absolute attitude provider gives the device attitude in East-North-Up (ENU) frame
12
+ */
13
+ class AbsoluteAttitudeFromRelAttProvider extends Provider {
14
+
15
+ accuracy = 0;
16
+
17
+ /**
18
+ * @override
19
+ */
20
+ static get displayName() {
21
+ return 'Absolute Attitude';
22
+ }
23
+
24
+ /**
25
+ * @override
26
+ */
27
+ static get eventsType() {
28
+ return [EventType.AbsoluteAttitude];
29
+ }
30
+
31
+ /**
32
+ * @override
33
+ */
34
+ get _availability() {
35
+ return RelativeAttitude.availability;
36
+ }
37
+
38
+ /**
39
+ * @override
40
+ */
41
+ start() {
42
+
43
+ this.relativeAttitudeProviderId = RelativeAttitude.addEventListener(
44
+ events => this.onRelativeAttitudeEvent(events[0]),
45
+ this.notifyError,
46
+ this.name);
47
+
48
+
49
+ this.onAbsoluteAttitudeEvent(AbsoluteAttitude.lastEvent);
50
+ this.absoluteAttitudeProviderId = AbsoluteAttitude.addEventListener(
51
+ events => this.onAbsoluteAttitudeEvent(events[0]),
52
+ this.notifyError,
53
+ this.name,
54
+ false
55
+ );
56
+ }
57
+
58
+ /**
59
+ * @override
60
+ */
61
+ stop() {
62
+ RelativeAttitude.removeEventListener(this.relativeAttitudeProviderId);
63
+ AbsoluteAttitude.removeEventListener(this.absoluteAttitudeProviderId);
64
+ }
65
+
66
+
67
+ onRelativeAttitudeEvent(relativeAttitudeEvent) {
68
+
69
+ /**
70
+ * Calculate relative accuracy
71
+ */
72
+ if (this.relativeAttitudeEvent) {
73
+ const {
74
+ accuracy, time
75
+ } = relativeAttitudeEvent.data;
76
+ const diffTime = time - this.relativeAttitudeEvent.data.time;
77
+ this.accuracy += diffTime * accuracy;
78
+ }
79
+
80
+ this.relativeAttitudeEvent = relativeAttitudeEvent;
81
+ this.compute();
82
+ }
83
+
84
+
85
+ onAbsoluteAttitudeEvent = absoluteAttitudeEvent => {
86
+
87
+ if (!absoluteAttitudeEvent) {
88
+ return;
89
+ }
90
+
91
+ /**
92
+ * Use absolute attitude events only when they are not from this provider
93
+ */
94
+ if (absoluteAttitudeEvent.providersStack.includes(this.name)) {
95
+ return;
96
+ }
97
+ this.absoluteAttitudeEvent = absoluteAttitudeEvent;
98
+ this.accuracy = 0;
99
+
100
+ // preprocess zOffset for "compute" function
101
+ const currentHeading = this.relativeAttitudeEvent ? this.relativeAttitudeEvent.data.heading : 0;
102
+ this.zOffset = Quaternion.fromAxisAngle([0, 0, 1], -absoluteAttitudeEvent.data.heading + currentHeading);
103
+
104
+ this.compute();
105
+ };
106
+
107
+
108
+ compute() {
109
+ if (!this.absoluteAttitudeEvent || !this.relativeAttitudeEvent) {
110
+ return;
111
+ }
112
+
113
+ const {
114
+ quaternion, time
115
+ } = this.relativeAttitudeEvent.data;
116
+
117
+ const absoluteAttitudeAccuracy = this.absoluteAttitudeEvent.data.accuracy;
118
+
119
+ const absoluteQuat = Quaternion.multiply(this.zOffset, quaternion);
120
+ const newAccuracy = absoluteAttitudeAccuracy + this.accuracy;
121
+ const attitude = new Attitude(absoluteQuat, time, newAccuracy, this.name);
122
+
123
+ this.notify(this.createEvent(
124
+ EventType.AbsoluteAttitude,
125
+ attitude,
126
+ time,
127
+ [this.relativeAttitudeEvent, this.absoluteAttitudeEvent]
128
+ ));
129
+
130
+ }
131
+
132
+ }
133
+
134
+ export default AbsoluteAttitudeFromRelAttProvider;