@wemap/positioning 1.2.2 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (108) hide show
  1. package/.eslintrc.json +4 -2
  2. package/debug/absolute-attitude.html +16 -0
  3. package/debug/gnss-wifi-pdr.html +16 -0
  4. package/debug/gnss-wifi.html +16 -0
  5. package/debug/imu.html +16 -0
  6. package/debug/inclination.html +16 -0
  7. package/debug/pdr.html +16 -0
  8. package/debug/pose.html +16 -0
  9. package/debug/positioning.html +16 -0
  10. package/debug/relative-attitude.html +16 -0
  11. package/package.json +6 -4
  12. package/src/PositioningHandler.js +124 -0
  13. package/src/components/AbsoluteAttitudeComponent.jsx +104 -0
  14. package/src/components/GnssWifiComponent.jsx +46 -0
  15. package/src/components/GnssWifiPdrComponent.jsx +85 -0
  16. package/src/components/ImuComponent.jsx +100 -0
  17. package/src/components/InclinationComponent.jsx +53 -0
  18. package/src/components/PdrComponent.jsx +88 -0
  19. package/src/components/PoseComponent.jsx +74 -0
  20. package/src/components/PositioningComponent.jsx +26 -0
  21. package/src/components/PositioningInclinationComponent.jsx +76 -0
  22. package/src/components/PositioningPoseComponent.jsx +111 -0
  23. package/src/components/RelativeAttitudeComponent.jsx +82 -0
  24. package/src/components/StartStopComponent.jsx +50 -0
  25. package/src/components/Utils.js +41 -2
  26. package/src/components/index.js +19 -2
  27. package/src/errors/AskImuOnDesktopError.js +9 -0
  28. package/src/errors/GeolocationApiMissingError.js +9 -0
  29. package/src/errors/GeolocationPermissionDeniedError.js +9 -0
  30. package/src/errors/GeolocationPositionUnavailableError.js +9 -0
  31. package/src/errors/IpResolveServerError.js +9 -0
  32. package/src/errors/MissingAccelerometerError.js +11 -0
  33. package/src/errors/MissingGyroscopeError.js +11 -0
  34. package/src/errors/MissingMagnetometerError.js +9 -0
  35. package/src/errors/MissingSensorError.js +14 -0
  36. package/src/events/EventType.js +20 -0
  37. package/src/events/ProviderError.js +52 -0
  38. package/src/events/ProviderEvent.js +35 -0
  39. package/src/index.js +3 -4
  40. package/src/providers/Constants.js +5 -0
  41. package/src/providers/FakeAbsolutePositionProvider.js +56 -0
  42. package/src/providers/Provider.js +218 -0
  43. package/src/providers/ProviderOptions.js +28 -0
  44. package/src/providers/ProvidersLogger.js +3 -3
  45. package/src/providers/attitude/AbsoluteAttitudeProvider.js +207 -0
  46. package/src/providers/attitude/EkfAttitude.js +238 -0
  47. package/src/providers/attitude/EkfAttitude.spec.js +116 -0
  48. package/src/providers/attitude/RelativeAttitudeProvider.js +129 -0
  49. package/src/providers/others/ImuProvider.js +186 -0
  50. package/src/providers/others/InclinationProvider.js +107 -0
  51. package/src/providers/others/MapMatchingProvider.js +147 -0
  52. package/src/providers/pose/GnssWifiPdrProvider.js +233 -0
  53. package/src/providers/pose/PoseProvider.js +90 -0
  54. package/src/providers/pose/pdr/PdrProvider.js +352 -0
  55. package/src/providers/pose/pdr/helpers/HeadingUnlocker.js +41 -0
  56. package/src/providers/pose/pdr/helpers/HeadingUnlocker.spec.js +26 -0
  57. package/src/providers/pose/pdr/helpers/Smoother.js +90 -0
  58. package/src/providers/pose/pdr/helpers/Smoother.spec.js +424 -0
  59. package/src/providers/pose/pdr/helpers/ThugDetector.js +37 -0
  60. package/src/providers/pose/pdr/steps/StepDetection.js +7 -0
  61. package/src/providers/pose/pdr/steps/StepDetectionLadetto.js +67 -0
  62. package/src/providers/pose/pdr/steps/StepDetectionMinMaxPeaks.js +80 -0
  63. package/src/providers/pose/pdr/steps/StepDetectionMinMaxPeaks2.js +108 -0
  64. package/src/providers/position/GnssWifiProvider.js +129 -0
  65. package/src/providers/position/IpProvider.js +75 -0
  66. package/src.old/components/Utils.js +35 -0
  67. package/src.old/components/index.js +13 -0
  68. package/src.old/index.js +7 -0
  69. package/{src → src.old}/providers/GnssPdrLocationSource.js +1 -1
  70. package/src.old/providers/ProvidersLogger.js +77 -0
  71. package/webpack/webpack.dev.js +1 -1
  72. package/debug/index.html +0 -15
  73. package/debug/index.old.html +0 -37
  74. package/scripts/release-github.js +0 -216
  75. package/src.new/NavigationHandler.js +0 -62
  76. package/src.new/index.js +0 -3
  77. package/src.new/providers/FakeLocationSource.js +0 -39
  78. /package/{src → src.old}/Constants.js +0 -0
  79. /package/{src → src.old}/NavigationHandler.js +0 -0
  80. /package/{src → src.old}/Pose.js +0 -0
  81. /package/{src → src.old}/attitude/AttitudeHandler.js +0 -0
  82. /package/{src → src.old}/attitude/EkfAttitude.js +0 -0
  83. /package/{src → src.old}/attitude/EkfAttitude.spec.js +0 -0
  84. /package/{src → src.old}/components/AbsoluteAttitude.jsx +0 -0
  85. /package/{src → src.old}/components/Imu.jsx +0 -0
  86. /package/{src → src.old}/components/LocationSource.jsx +0 -0
  87. /package/{src → src.old}/components/Logger.jsx +0 -0
  88. /package/{src → src.old}/components/NavigationDebugApp.jsx +0 -0
  89. /package/{src → src.old}/components/Others.jsx +0 -0
  90. /package/{src → src.old}/components/RelativeAttitude.jsx +0 -0
  91. /package/{src → src.old}/providers/FixedLocationImuLocationSource.js +0 -0
  92. /package/{src → src.old}/providers/GnssLocationSource.js +0 -0
  93. /package/{src → src.old}/providers/IPLocationSource.js +0 -0
  94. /package/{src → src.old}/providers/LocationSource.js +0 -0
  95. /package/{src → src.old}/providers/PdrLocationSource.js +0 -0
  96. /package/{src → src.old}/providers/pdr/HeadingUnlocker.js +0 -0
  97. /package/{src → src.old}/providers/pdr/HeadingUnlocker.spec.js +0 -0
  98. /package/{src → src.old}/providers/pdr/Smoother.js +0 -0
  99. /package/{src → src.old}/providers/pdr/Smoother.spec.js +0 -0
  100. /package/{src → src.old}/providers/pdr/ThugDetector.js +0 -0
  101. /package/{src → src.old}/providers/steps/StepDetection.js +0 -0
  102. /package/{src → src.old}/providers/steps/StepDetectionLadetto.js +0 -0
  103. /package/{src → src.old}/providers/steps/StepDetectionMinMaxPeaks.js +0 -0
  104. /package/{src → src.old}/providers/steps/StepDetectionMinMaxPeaks2.js +0 -0
  105. /package/{src → src.old}/sensors/SensorsCompatibility.js +0 -0
  106. /package/{src → src.old}/sensors/SensorsCompatibility.spec.js +0 -0
  107. /package/{src → src.old}/sensors/SensorsLogger.js +0 -0
  108. /package/{src → src.old}/sensors/SensorsLoggerUtils.js +0 -0
@@ -0,0 +1,207 @@
1
+ import geomagnetism from 'geomagnetism';
2
+
3
+ import {
4
+ Attitude, WGS84
5
+ } from '@wemap/geo';
6
+ import {
7
+ deg2rad, rad2deg, Quaternion, Rotations
8
+ } from '@wemap/maths';
9
+ import {
10
+ Browser, BrowserUtils
11
+ } from '@wemap/utils';
12
+
13
+ import Provider from '../Provider';
14
+ import EventType from '../../events/EventType';
15
+ import AskImuOnDesktopError from '../../errors/AskImuOnDesktopError';
16
+ import MissingMagnetometerError from '../../errors/MissingMagnetometerError';
17
+ import MissingSensorError from '../../errors/MissingSensorError';
18
+ import Logger from '@wemap/logger';
19
+
20
+
21
+ /**
22
+ * Absolute attitude provider gives the device attitude in East-North-Up (ENU) frame using
23
+ * browser deviceorientation or deviceorientationabsolute
24
+ * The provider does not work until an AbsolutePosition is given. This is necessary to
25
+ * calculate declination.
26
+ *
27
+ * -----------------------------------
28
+ * Overview of compatibilities:
29
+ * -----------------------------------
30
+ *
31
+ * Chrome Android (v72.0.3626): YES (via deviceorientationabsolute)
32
+ * Safari iOS (v12.0): YES (via deviceorientation and event.webkitCompassHeading)
33
+ * Opera Android (v50.2.2426): NO {@link https://developer.mozilla.org/en-US/docs/Web/API/Window/ondeviceorientation}
34
+ * Firefox Android (v65.0.1): NO {@link https://www.fxsitecompat.com/en-CA/docs/2018/various-device-sensor-apis-are-now-deprecated/}
35
+ *
36
+ * -----------------------------------
37
+ */
38
+ class AbsoluteAttitudeProvider extends Provider {
39
+
40
+ /**
41
+ * @override
42
+ */
43
+ static get displayName() {
44
+ return 'Absolute Attitude from Browser';
45
+ }
46
+
47
+ /**
48
+ * @override
49
+ */
50
+ static get eventsType() {
51
+ return [EventType.AbsoluteAttitude];
52
+ }
53
+
54
+ /**
55
+ * @override
56
+ */
57
+ static checkAvailabilityErrors() {
58
+
59
+ if (BrowserUtils.isMobile) {
60
+ return [];
61
+ }
62
+
63
+ return [
64
+ AbsoluteAttitudeProvider.createError(
65
+ EventType.AbsoluteAttitude,
66
+ new AskImuOnDesktopError()
67
+ )
68
+ ];
69
+ }
70
+
71
+ /**
72
+ * @override
73
+ */
74
+ startInternal() {
75
+ switch (BrowserUtils.name) {
76
+ case Browser.CHROME:
77
+ window.addEventListener('deviceorientationabsolute',
78
+ this.onDeviceOrientationChromeEvent, true);
79
+ break;
80
+
81
+ case Browser.SAFARI:
82
+ window.addEventListener('deviceorientation',
83
+ this.onDeviceOrientationSafariEvent, true);
84
+ break;
85
+ }
86
+ }
87
+
88
+ /**
89
+ * @override
90
+ */
91
+ stopInternal() {
92
+ switch (BrowserUtils.name) {
93
+ case Browser.CHROME:
94
+ window.removeEventListener('deviceorientationabsolute',
95
+ this.onDeviceOrientationChromeEvent, true);
96
+ break;
97
+
98
+ case Browser.SAFARI:
99
+ window.removeEventListener('deviceorientation',
100
+ this.onDeviceOrientationSafariEvent, true);
101
+ break;
102
+ }
103
+ }
104
+
105
+
106
+ onDeviceOrientationChromeEvent = e => {
107
+
108
+ const timestamp = e.timeStamp / 1e3;
109
+
110
+ if (!e.alpha || !e.beta || !e.gamma) {
111
+ super.notifyError(this.createError(
112
+ EventType.AbsoluteAttitude,
113
+ new MissingSensorError().from('deviceorientationabsolute'),
114
+ timestamp
115
+ ));
116
+ return;
117
+ }
118
+ this.onDeviceOrientationCommonEvent(timestamp,
119
+ Rotations.eulerToQuaternionZXYDegrees([e.alpha, e.beta, e.gamma]));
120
+ };
121
+
122
+
123
+ onDeviceOrientationSafariEvent = e => {
124
+
125
+ const timestamp = e.timeStamp / 1e3;
126
+
127
+ if (!e.beta || !e.gamma) {
128
+ super.notifyError(this.createError(
129
+ EventType.AbsoluteAttitude,
130
+ new MissingSensorError().from('deviceorientation'),
131
+ timestamp
132
+ ));
133
+ return;
134
+ }
135
+
136
+ if (!e.webkitCompassHeading) {
137
+ super.notifyError(this.createError(
138
+ EventType.AbsoluteAttitude,
139
+ new MissingMagnetometerError().from('deviceorientation'),
140
+ timestamp
141
+ ));
142
+ return;
143
+ }
144
+
145
+ let webkitCompassHeading = -e.webkitCompassHeading;
146
+ // Be Careful: webkitCompassHeading is not continuous.
147
+ // Reference frame changes in function of beta with thresholds: beta > 30deg and beta < 62deg
148
+ // Below 30deg, East-North-Up (ENU) frame is used
149
+ // Above 62def, East-Up-South (EUS) frame is used
150
+ // Between 30deg and 62deg either ENU or EUS can be used
151
+ if (e.beta > 62) {
152
+ webkitCompassHeading = AbsoluteAttitudeProvider.headingEusToEnu(
153
+ 180 - webkitCompassHeading, e.beta, e.gamma);
154
+ }
155
+ const quaternion = Rotations.eulerToQuaternionZXYDegrees(
156
+ [webkitCompassHeading, e.beta, e.gamma]);
157
+
158
+ this.onDeviceOrientationCommonEvent(timestamp, quaternion);
159
+ };
160
+
161
+
162
+ onDeviceOrientationCommonEvent = (timestamp, quaternion) => {
163
+
164
+ if (!this.declinationQuaternion) {
165
+ Logger.warn('Location of AbsoluteAttitude provider is not set yet. '
166
+ + 'Please call setLocation() before.');
167
+ return;
168
+ }
169
+ const trueQuaternion = Quaternion.multiply(this.declinationQuaternion, quaternion);
170
+
171
+ super.notify(
172
+ this.createEvent(
173
+ EventType.AbsoluteAttitude,
174
+ new Attitude(trueQuaternion),
175
+ timestamp
176
+ )
177
+ );
178
+ }
179
+
180
+ /**
181
+ * Initialized declination quaternion using current location.
182
+ * This method should be theoretically called every time the user moves.
183
+ * But in reality declination does not change as much.
184
+ * @param {WGS84} location user location
185
+ */
186
+ setLocation(location) {
187
+ const wmmResult = geomagnetism.model().point([location.lat, location.lng]);
188
+ // Declination is given in NED frame and our code use ENU, that is why we have: "-decl"
189
+ this.declinationQuaternion = Quaternion.fromAxisAngle([0, 0, 1], - deg2rad(wmmResult.decl));
190
+ }
191
+
192
+ /**
193
+ * {@link https://math.stackexchange.com/questions/3181981/solve-a-system-of-rotation-matrices-z-x-z-z-x-y}
194
+ */
195
+ static headingEusToEnu(_alpha, _beta, _gamma) {
196
+ const alpha = deg2rad(_alpha);
197
+ const beta = deg2rad(_beta);
198
+ const gamma = deg2rad(_gamma);
199
+
200
+ return rad2deg(Math.atan2(
201
+ Math.cos(alpha) * Math.sin(gamma) + Math.cos(gamma) * Math.sin(alpha) * Math.sin(beta),
202
+ Math.sin(alpha) * Math.sin(gamma) - Math.cos(alpha) * Math.cos(gamma) * Math.sin(beta)
203
+ ));
204
+ }
205
+ }
206
+
207
+ export default AbsoluteAttitudeProvider;
@@ -0,0 +1,238 @@
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
+ *
55
+ * Set yaw offset, this value will be used if the filter does not use magnetometer
56
+ * @param {Number} yaw yaw offset in radians and clockwise
57
+ */
58
+ setOrientationYaw(yaw) {
59
+ this.offsetYawQuaternion = Quaternion.fromAxisAngle(this.accRef, yaw);
60
+ this.quaternion = null;
61
+ }
62
+
63
+ /**
64
+ * Try to initialize filter.
65
+ * To initialize, we need two information: current acceleration (acc) and offset on yaw angle (given by the quaternion: this.offsetYawQuaternion)
66
+ */
67
+ tryInitialize(acc, mag) {
68
+
69
+ const accNormalized = Vector3.normalize(acc);
70
+
71
+ if (mag) {
72
+ const magNormalized = Vector3.normalize(mag);
73
+
74
+ const H = Vector3.normalize(Vector3.cross(magNormalized, accNormalized));
75
+ const M = Vector3.cross(accNormalized, H);
76
+
77
+ const R = [
78
+ [H[0], M[0], accNormalized[0]],
79
+ [H[1], M[1], accNormalized[1]],
80
+ [H[2], M[2], accNormalized[2]]
81
+ ];
82
+
83
+ this.quaternion = Quaternion.fromMatrix3(R);
84
+
85
+ } else {
86
+
87
+ if (!this.offsetYawQuaternion) {
88
+ return null;
89
+ }
90
+
91
+ const r = Vector3.dot(accNormalized, this.accRef) + 1;
92
+ const v = Vector3.cross(accNormalized, this.accRef);
93
+
94
+ let quaternionWithoutYaw = [r, v[0], v[1], v[2]];
95
+ quaternionWithoutYaw = Quaternion.normalize(quaternionWithoutYaw);
96
+
97
+ this.quaternion = Quaternion.multiply(this.offsetYawQuaternion, quaternionWithoutYaw);
98
+ }
99
+
100
+ return this.quaternion;
101
+ }
102
+
103
+ update(diffTime, acc, gyr, mag) {
104
+
105
+ if (!this.quaternion) {
106
+ return this.tryInitialize(acc, mag);
107
+ }
108
+
109
+ let q = this.quaternion;
110
+
111
+ /* ------------
112
+ * ESTIMATION
113
+ * ------------*/
114
+
115
+ const qArray = q;
116
+ const gyrInt = Vector3.multiplyScalar(gyr, 0.5 * diffTime);
117
+ const F = this.computeC([1, gyrInt[0], gyrInt[1], gyrInt[2]]);
118
+ const qAPriori = Matrix.multiplyVector(F, q);
119
+ const E1 = Matrix.diag([qArray[0], qArray[0], qArray[0]]);
120
+ const eSkew = Matrix3.skew([qArray[1], qArray[2], qArray[3]]);
121
+
122
+ const qPart = [-1 * qArray[1], -1 * qArray[2], -1 * qArray[3]];
123
+ const E = Matrix.concatRow([qPart], Matrix3.add(eSkew, E1));
124
+
125
+ const Qk = Matrix.multiplyScalar(
126
+ Matrix.multiply(
127
+ Matrix.multiply(E, this.noises[mag ? 'absolute' : 'relative'].gyroscope),
128
+ Matrix.transpose(E)
129
+ ),
130
+ (diffTime / 2) ** 2
131
+ );
132
+
133
+ const pAPriori = Matrix4.add(
134
+ Matrix.multiply(
135
+ Matrix.multiply(F, this.P),
136
+ Matrix.transpose(F)
137
+ ),
138
+ Qk
139
+ );
140
+
141
+ /* ------------
142
+ * CORRECTION
143
+ * ------------*/
144
+
145
+ const accNormalized = Vector3.normalize(acc);
146
+ let dz, K, H;
147
+
148
+ if (mag) {
149
+
150
+ const magNormalized = Vector3.normalize(mag);
151
+ const yc = Vector3.cross(accNormalized, magNormalized);
152
+ const ycNormalized = Vector3.normalize(yc);
153
+
154
+ const dzYc = Vector3.subtract(ycNormalized, Quaternion.rotate(qAPriori, this.cRef));
155
+ const dzAcc = Vector3.subtract(accNormalized, Quaternion.rotate(qAPriori, this.accRef));
156
+ dz = Vector.concat(dzYc, dzAcc);
157
+
158
+ const HYc = this.jacobianES(qAPriori, this.cRef);
159
+ const HAcc = this.jacobianES(qAPriori, this.accRef);
160
+ H = Matrix.concatRow(HYc, HAcc);
161
+
162
+ const RYc = Matrix.concatLine(this.noises.absolute.yc, Matrix3.zeros());
163
+ const RAcc = Matrix.concatLine(Matrix3.zeros(), this.noises.absolute.accelerometer);
164
+ const R = Matrix.concatRow(RYc, RAcc);
165
+
166
+ K = Matrix.multiply(
167
+ Matrix.multiply(pAPriori, Matrix.transpose(H)),
168
+ Matrix.inverse(
169
+ Matrix.add(
170
+ Matrix.multiply(
171
+ Matrix.multiply(H, pAPriori),
172
+ Matrix.transpose(H)
173
+ ),
174
+ R
175
+ )
176
+ )
177
+ );
178
+ } else {
179
+ dz = Vector3.subtract(accNormalized, Quaternion.rotate(qAPriori, this.accRef));
180
+ H = this.jacobianES(qAPriori, this.accRef);
181
+ const R = this.noises.relative.accelerometer;
182
+
183
+ K = Matrix.multiply(
184
+ Matrix.multiply(pAPriori, Matrix.transpose(H)),
185
+ Matrix3.inverse(
186
+ Matrix3.add(
187
+ Matrix.multiply(
188
+ Matrix.multiply(H, pAPriori),
189
+ Matrix.transpose(H)
190
+ ),
191
+ R
192
+ )
193
+ )
194
+ );
195
+ }
196
+
197
+ q = Quaternion.add(
198
+ qAPriori,
199
+ Matrix.multiplyVector(K, dz)
200
+ );
201
+ const P = Matrix.multiply(
202
+ Matrix4.subtract(
203
+ Matrix.identity4(),
204
+ Matrix.multiply(K, H)
205
+ ),
206
+ pAPriori
207
+ );
208
+
209
+ q = Quaternion.normalize(q);
210
+ this.quaternion = q;
211
+ this.P = P;
212
+
213
+ return q;
214
+ }
215
+
216
+ computeC(b) {
217
+ return [
218
+ [b[0], -b[1], -b[2], -b[3]],
219
+ [b[1], b[0], b[3], -b[2]],
220
+ [b[2], -b[3], b[0], b[1]],
221
+ [b[3], b[2], -b[1], b[0]]
222
+ ];
223
+ }
224
+
225
+ jacobianES(q, v) {
226
+
227
+ const [qw, qx, qy, qz] = q;
228
+ const [vx, vy, vz] = v;
229
+
230
+ return [
231
+ [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],
232
+ [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],
233
+ [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]
234
+ ];
235
+ }
236
+ }
237
+
238
+ export default EkfAttitude;
@@ -0,0 +1,116 @@
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.setOrientationYaw(0);
89
+ ekf.setRelativeNoises({
90
+ acc: 0.5,
91
+ gyr: 0.3
92
+ });
93
+ const result = ekf.update(dt[0], accData[0], gyrData[0]);
94
+ const distance = Quaternion.distance(result, expectationsRelative[0]);
95
+ expect(distance).to.below(0.000001);
96
+ });
97
+ });
98
+
99
+ describe('updateRelative', () => {
100
+ it('Should return the good value', () => {
101
+ const ekf = new EkfAttitude();
102
+ ekf.setOrientationYaw(0);
103
+ ekf.setRelativeNoises({
104
+ acc: 0.5,
105
+ gyr: 0.3
106
+ });
107
+ let result;
108
+ let distance;
109
+
110
+ for (let i = 0; i < accData.length; i++) {
111
+ result = ekf.update(dt[i], accData[i], gyrData[i]);
112
+ distance = Quaternion.distance(result, expectationsRelative[i]);
113
+ expect(distance).to.below(0.000001);
114
+ }
115
+ });
116
+ });
@@ -0,0 +1,129 @@
1
+ import Provider from '../Provider';
2
+ import EventType from '../../events/EventType';
3
+ import EkfAttitude from './EkfAttitude';
4
+ import ImuProvider from '../others/ImuProvider';
5
+ import { Attitude } from '@wemap/geo';
6
+ import { deg2rad } from '@wemap/maths';
7
+ import ProviderError from '../../events/ProviderError';
8
+
9
+
10
+ /**
11
+ * Relative attitude provider gives the device attitude in East-North-Up (ENU) frame using
12
+ * browser deviceorientation
13
+ * The provider does not work until an offset is given.
14
+ */
15
+ class RelativeAttitudeProvider extends Provider {
16
+
17
+ lastTimestamp = 0;
18
+
19
+ /**
20
+ * @override
21
+ */
22
+ constructor(onEvent, onError, options) {
23
+ super(onEvent, onError, options);
24
+
25
+ this.ekfAttitude = new EkfAttitude();
26
+ this.relativeOffsetQuaternion = [1, 0, 0, 0];
27
+
28
+ this.imuProvider = new ImuProvider(this.onImuEvent,
29
+ this.onImuError, { require: [EventType.Acceleration, EventType.AngularRate] });
30
+
31
+ }
32
+
33
+ /**
34
+ * @override
35
+ */
36
+ static get displayName() {
37
+ return 'Relative Attitude from Ekf';
38
+ }
39
+
40
+ /**
41
+ * @override
42
+ */
43
+ static get eventsType() {
44
+ return [EventType.RelativeAttitude];
45
+ }
46
+
47
+ /**
48
+ * @override
49
+ */
50
+ static get requiredProviders() {
51
+ return [ImuProvider];
52
+ }
53
+
54
+ /**
55
+ * @override
56
+ */
57
+ startInternal() {
58
+ this.imuProvider.start();
59
+ }
60
+
61
+ /**
62
+ * @override
63
+ */
64
+ stopInternal() {
65
+ this.imuProvider.stop();
66
+ }
67
+
68
+ /**
69
+ * @private
70
+ */
71
+ onImuEvent = imuEvent => {
72
+
73
+ let timestamp, acceleration, angularRate;
74
+ imuEvent.forEach(event => {
75
+ if (event.dataType === EventType.Acceleration) {
76
+ acceleration = event.data;
77
+ timestamp = event.timestamp;
78
+ } else if (event.dataType === EventType.AngularRate) {
79
+ angularRate = event.data;
80
+ }
81
+ });
82
+
83
+ // Handle timestamps and dt
84
+ if (this.lastTimestamp === 0) {
85
+ this.lastTimestamp = timestamp;
86
+ return;
87
+ }
88
+ const diffTime = timestamp - this.lastTimestamp;
89
+ this.lastTimestamp = timestamp;
90
+
91
+ const quaternion = this.ekfAttitude.update(diffTime, acceleration, angularRate);
92
+
93
+ if (quaternion) {
94
+ this.notify(this.createEvent(EventType.RelativeAttitude, new Attitude(quaternion), timestamp));
95
+ }
96
+ };
97
+
98
+ /**
99
+ *
100
+ * Set yaw offset, this value will be used as the filter does not use magnetometer
101
+ * @param {Number} heading heading offset in radians and clockwise
102
+ */
103
+ setOffset(_heading) {
104
+
105
+ // Minus before "heading" is here because ENU attitude is counter-clockwise whereas WGS84 heading is clockwise.
106
+ let heading = -_heading;
107
+
108
+ // Offset from window orientation
109
+ heading += deg2rad(window.orientation || 0);
110
+
111
+ this.ekfAttitude.setOrientationYaw(heading);
112
+ }
113
+
114
+ onImuError = imuErrors => {
115
+ this.notifyError(...ProviderError.modifyArrayDataType(imuErrors, EventType.RelativeAttitude));
116
+ }
117
+
118
+ /**
119
+ * @override
120
+ */
121
+ static checkAvailabilityErrors() {
122
+ return ProviderError.modifyArrayDataType(
123
+ super.checkAvailabilityErrors(),
124
+ EventType.RelativeAttitude
125
+ );
126
+ }
127
+ }
128
+
129
+ export default RelativeAttitudeProvider;