@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,270 @@
1
+ import chai from 'chai';
2
+ import chaiAlmost from 'chai-almost';
3
+ import chaiAsPromised from 'chai-as-promised';
4
+ import 'jsdom-global/register';
5
+
6
+ import SensorsCompatibility from './SensorsCompatibility';
7
+
8
+ const expect = chai.expect;
9
+ chai.use(chaiAlmost());
10
+ chai.use(chaiAsPromised);
11
+
12
+ const IOS_USER_AGENT = 'iPad';
13
+ const ANDROID_USER_AGENT = 'Android';
14
+ const FIREFOX_USER_AGENT = 'Firefox';
15
+ const OPERA_USER_AGENT = 'Opera';
16
+ const UNKNOWN_USER_AGENT = 'foobar';
17
+
18
+ const defaultMotionSensors = {
19
+ accelerometer: true,
20
+ gyroscope: true,
21
+ magnetometer: true
22
+ };
23
+
24
+ const defaultDeviceMotionSensors = {
25
+ accelerometer: true,
26
+ gyroscope: true,
27
+ magnetometer: false
28
+ };
29
+
30
+ const timestamps = [
31
+ 0, 0.02, 0.04, 0.06, 0.08
32
+ ];
33
+
34
+ const accData = [
35
+ [-0.034561157, 3.81073, 8.860977],
36
+ [-0.030700684, 3.814499, 8.818954],
37
+ [-0.018234253, 3.8023376, 8.85762],
38
+ [-0.020080566, 3.8205414, 8.8676605],
39
+ [-0.054519653, 3.8456726, 8.810287]
40
+ ];
41
+
42
+ const gyrData = [
43
+ [0.0047454834, 0.0028076172, 0.0022888184],
44
+ [0.005218506, 0.0020446777, 0.0012207031],
45
+ [0.0044555664, 0.0023040771, 4.4250488E-4],
46
+ [0.0044555664, 0.0027618408, 0.0011444092],
47
+ [0.0040893555, 0.0020446777, 0.0025787354]
48
+ ];
49
+
50
+
51
+ function fakeUserAgent(userAgent) {
52
+ navigator.__defineGetter__('userAgent', () => userAgent);
53
+ }
54
+
55
+ const commonDeviceMotion = (userAgent = ANDROID_USER_AGENT,
56
+ _simulatedSensors = defaultDeviceMotionSensors,
57
+ _expectedSensors = defaultDeviceMotionSensors) => {
58
+
59
+ const simulatedSensors = !_simulatedSensors ? defaultMotionSensors : _simulatedSensors;
60
+ const expectedSensors = !_expectedSensors ? defaultMotionSensors : _expectedSensors;
61
+
62
+ return new Promise((resolve, reject) => {
63
+ const result = [];
64
+
65
+ fakeUserAgent(userAgent);
66
+ document.hasFocus = () => true;
67
+ const sc = new SensorsCompatibility();
68
+
69
+ let eventsReceived = 0;
70
+ sc.startImu((e) => {
71
+ result.push(e);
72
+
73
+ eventsReceived++;
74
+ if (eventsReceived === accData.length) {
75
+ resolve(result);
76
+ }
77
+ }, expectedSensors).catch(e => {
78
+ reject(e);
79
+ });
80
+
81
+ if (!simulatedSensors.accelerometer && !simulatedSensors.gyroscope) {
82
+ return;
83
+ }
84
+
85
+ for (let i = 0; i < timestamps.length; i++) {
86
+
87
+ const event = new CustomEvent('devicemotion');
88
+
89
+ Object.defineProperty(event, 'timeStamp', { get: () => timestamps[i] });
90
+
91
+ if (simulatedSensors.accelerometer) {
92
+ const acc = accData[i];
93
+ event.accelerationIncludingGravity = {
94
+ x: acc[0],
95
+ y: acc[1],
96
+ z: acc[2]
97
+ };
98
+ }
99
+
100
+ if (simulatedSensors.gyroscope) {
101
+ const gyr = gyrData[i];
102
+ event.rotationRate = {
103
+ alpha: gyr[0],
104
+ beta: gyr[1],
105
+ gamma: gyr[2]
106
+ };
107
+ }
108
+ window.dispatchEvent(event);
109
+ }
110
+ });
111
+ };
112
+
113
+
114
+ describe('devicemotion', () => {
115
+
116
+ it('[Android] Acc - Gyr', (done) => {
117
+ commonDeviceMotion(ANDROID_USER_AGENT).then((receivedDataset) => {
118
+ for (let i = 0; i < receivedDataset.length; i++) {
119
+ const e = receivedDataset[i];
120
+
121
+ const expectedGyr = gyrData[i].map(x => x * Math.PI / 180);
122
+ const expectedAcc = accData[i];
123
+ const expectedTimestamp = timestamps[i] / 1e3;
124
+
125
+ expect(e.timestamp).almost(expectedTimestamp);
126
+ expect(e.acc).deep.almost(expectedAcc);
127
+ expect(e.gyr).deep.almost(expectedGyr);
128
+ }
129
+ done();
130
+ }).catch(e => done(e));
131
+ });
132
+
133
+ it('[iOS] Acc - Gyr', (done) => {
134
+ commonDeviceMotion(IOS_USER_AGENT).then((receivedDataset) => {
135
+ for (let i = 0; i < receivedDataset.length; i++) {
136
+ const e = receivedDataset[i];
137
+
138
+ const expectedGyr = gyrData[i].map(x => x * Math.PI / 180);
139
+ const expectedAcc = accData[i].map(x => -x);
140
+ const expectedTimestamp = timestamps[i] / 1e3;
141
+
142
+ expect(e.timestamp).almost(expectedTimestamp);
143
+ expect(e.acc).deep.almost(expectedAcc);
144
+ expect(e.gyr).deep.almost(expectedGyr);
145
+ }
146
+ done();
147
+ }).catch(e => done(e));
148
+ });
149
+
150
+ it('[Firefox] Acc - Gyr', (done) => {
151
+ commonDeviceMotion(FIREFOX_USER_AGENT).then((receivedDataset) => {
152
+ for (let i = 0; i < receivedDataset.length; i++) {
153
+ const e = receivedDataset[i];
154
+
155
+ const expectedGyr = gyrData[i].map(x => x * Math.PI / 180);
156
+ const expectedAcc = accData[i];
157
+ const expectedTimestamp = timestamps[i] / 1e3;
158
+
159
+ expect(e.timestamp).almost(expectedTimestamp);
160
+ expect(e.acc).deep.almost(expectedAcc);
161
+ expect(e.gyr).deep.almost(expectedGyr);
162
+ }
163
+ done();
164
+ }).catch(e => done(e));
165
+ });
166
+
167
+ it('[Opera] Acc - Gyr', (done) => {
168
+ commonDeviceMotion(OPERA_USER_AGENT).then((receivedDataset) => {
169
+ for (let i = 0; i < receivedDataset.length; i++) {
170
+ const e = receivedDataset[i];
171
+
172
+ const expectedGyr = gyrData[i].map(x => x * Math.PI / 180);
173
+ const expectedAcc = accData[i];
174
+ const expectedTimestamp = timestamps[i] / 1e3;
175
+
176
+ expect(e.timestamp).almost(expectedTimestamp);
177
+ expect(e.acc).deep.almost(expectedAcc);
178
+ expect(e.gyr).deep.almost(expectedGyr);
179
+ }
180
+ done();
181
+ }).catch(e => done(e));
182
+ });
183
+
184
+
185
+ it('[Unknown] Acc - Gyr', (done) => {
186
+ commonDeviceMotion(UNKNOWN_USER_AGENT).then((receivedDataset) => {
187
+ for (let i = 0; i < receivedDataset.length; i++) {
188
+ const e = receivedDataset[i];
189
+
190
+ const expectedGyr = gyrData[i].map(x => x * Math.PI / 180);
191
+ const expectedAcc = accData[i];
192
+ const expectedTimestamp = timestamps[i] / 1e3;
193
+
194
+ expect(e.timestamp).almost(expectedTimestamp);
195
+ expect(e.acc).deep.almost(expectedAcc);
196
+ expect(e.gyr).deep.almost(expectedGyr);
197
+ }
198
+ done();
199
+ }).catch(e => done(e));
200
+ });
201
+
202
+
203
+ it('[Unknown] Missing gyr', (done) => {
204
+
205
+ const globalTimeout = setTimeout(() => done(), 20);
206
+
207
+ commonDeviceMotion(UNKNOWN_USER_AGENT,
208
+ {
209
+ accelerometer: true,
210
+ gyroscope: false
211
+ }, {
212
+ accelerometer: true,
213
+ gyroscope: true
214
+ })
215
+ .then(() => {
216
+ clearTimeout(globalTimeout);
217
+ done(Error('Should not success'));
218
+ })
219
+ .catch(() => {
220
+ clearTimeout(globalTimeout);
221
+ done(Error('Should not reject'));
222
+ });
223
+ });
224
+
225
+
226
+ it('[Unknown] Missing acc', (done) => {
227
+
228
+ const globalTimeout = setTimeout(() => done(), 20);
229
+
230
+ commonDeviceMotion(UNKNOWN_USER_AGENT,
231
+ {
232
+ accelerometer: true,
233
+ gyroscope: false
234
+ }, {
235
+ accelerometer: true,
236
+ gyroscope: true
237
+ })
238
+ .then(() => {
239
+ clearTimeout(globalTimeout);
240
+ done(Error('Should not success'));
241
+ })
242
+ .catch(() => {
243
+ clearTimeout(globalTimeout);
244
+ done(Error('Should not reject'));
245
+ });
246
+ });
247
+
248
+
249
+ it('[Unknown] No sensor', (done) => {
250
+
251
+ const globalTimeout = setTimeout(() => done(), 20);
252
+
253
+ commonDeviceMotion(UNKNOWN_USER_AGENT,
254
+ {
255
+ accelerometer: false,
256
+ gyroscope: false
257
+ }, {
258
+ accelerometer: true,
259
+ gyroscope: true
260
+ })
261
+ .then(() => {
262
+ clearTimeout(globalTimeout);
263
+ done(Error('Should not success'));
264
+ })
265
+ .catch(() => {
266
+ clearTimeout(globalTimeout);
267
+ done(Error('Should not reject'));
268
+ });
269
+ });
270
+ });
@@ -0,0 +1,94 @@
1
+
2
+ const DataType = {
3
+ ACCELEROMETER: 0,
4
+ GYROSCOPE: 1,
5
+ DEVICE_ORIENTATION_RELATIVE: 2,
6
+ DEVICE_ORIENTATION_ABSOLUTE: 3,
7
+ WATCH_POSITION: 4,
8
+ USER_INPUT_POSITION: 10,
9
+ USER_INPUT_HEADING: 11,
10
+ IP_POSITION: 12
11
+ };
12
+
13
+ const PRECISION = 1000;
14
+
15
+ class SensorsLogger {
16
+
17
+ constructor() {
18
+ this.dataset = [];
19
+ this.videoBlobs = [];
20
+ this.removeDuplicates = true;
21
+
22
+ this.lastDataTimestamp = new Array(DataType.length);
23
+ for (let i = 0; i < DataType.length; i++) {
24
+ this.lastDataTimestamp[i] = 0;
25
+ }
26
+ }
27
+
28
+ static get DataType() {
29
+ return DataType;
30
+ }
31
+
32
+ disableRemoveDuplicates() {
33
+ this.removeDuplicates = false;
34
+ }
35
+
36
+ feedWithCurrentTime(dataType, data, compact) {
37
+ this.feed(dataType, performance.now() / 1e3, data, compact);
38
+ }
39
+
40
+ feed(dataType, timestamp, _data, compact = false) {
41
+
42
+ let data;
43
+
44
+ const smallTimestamp = Math.round(timestamp * PRECISION) / PRECISION;
45
+ if (this.removeDuplicates && this.lastDataTimestamp[dataType] === timestamp) {
46
+ return;
47
+ }
48
+
49
+ if (compact) {
50
+ const smallData = new Array(_data.length);
51
+ for (let i = 0; i < _data.length; i++) {
52
+ smallData[i] = Math.round(_data[i] * PRECISION) / PRECISION;
53
+ }
54
+ data = smallData;
55
+ } else {
56
+ data = _data;
57
+ }
58
+
59
+ const dataArray = [dataType, smallTimestamp].concat(data);
60
+ this.dataset.push(dataArray);
61
+ if (this.removeDuplicates) {
62
+ this.lastDataTimestamp[dataType] = timestamp;
63
+ }
64
+ }
65
+
66
+ // TODO: this is not linked to the rest of the code. Check why
67
+ // videoFeed = event => {
68
+ // if (event.data && event.data.size > 0) {
69
+ // this.videoBlobs.push(event.data);
70
+ // }
71
+ // }
72
+
73
+ getVideoSize() {
74
+ return this.videoBlobs.length;
75
+ }
76
+
77
+ /**
78
+ * Pop current logs dataset
79
+ */
80
+ pop() {
81
+ const currentDataset = this.dataset;
82
+ this.dataset = [];
83
+ return currentDataset;
84
+ }
85
+
86
+ popVideoBlob() {
87
+ const currentVideoBlobs = this.videoBlobs;
88
+ this.videoBlobs = [];
89
+ return new Blob(currentVideoBlobs, { type: 'video/webm' });
90
+ }
91
+
92
+ }
93
+
94
+ export default SensorsLogger;
@@ -0,0 +1,35 @@
1
+ import JSZip from 'jszip';
2
+ import { saveAs } from 'file-saver';
3
+
4
+ class SensorsLoggerUtils {
5
+
6
+ static download(sensorsLogger) {
7
+
8
+ const data = sensorsLogger.pop();
9
+ const csvData = data.map(row => row.join(',')).join('\r\n');
10
+
11
+ const zip = new JSZip();
12
+ zip.file('data.csv', csvData);
13
+ zip.file('video.webm', sensorsLogger.popVideoBlob());
14
+ zip.generateAsync({ type: 'blob' })
15
+ .then(function(content) {
16
+ saveAs(content, 'dataset.zip');
17
+ });
18
+ }
19
+
20
+
21
+ /**
22
+ * Return a value equivalent to performance now from a given unix timestamp
23
+ * @param {Number} timestamp a unix timestamp
24
+ * @returns timestamp relative to performance.now()
25
+ */
26
+ static unixTimestampToPerformanceNow(timestamp) {
27
+
28
+ if (!window || !window.performance) {
29
+ throw new Error('window.performance does not exist');
30
+ }
31
+ return window.performance.now() + timestamp - new Date().getTime();
32
+ }
33
+ }
34
+
35
+ export default SensorsLoggerUtils;
@@ -0,0 +1,62 @@
1
+ import noop from 'lodash.noop';
2
+
3
+ import FakeLocationSource from './providers/FakeLocationSource';
4
+
5
+ /**
6
+ * @private
7
+ */
8
+ class NavigationHandler {
9
+
10
+ static State = {
11
+ STARTING: 0,
12
+ STARTED: 1,
13
+ STOPPPED: 2
14
+ };
15
+
16
+ _state = NavigationHandler.State.STOPPPED;
17
+
18
+ /**
19
+ * Constructor of NavigationHandler
20
+ * @param {Function} _callback callback on new pose
21
+ * @public
22
+ */
23
+ constructor(_callback) {
24
+ const callback = _callback || noop;
25
+ this.locationSource = new FakeLocationSource(pose => callback(pose));
26
+ }
27
+
28
+ startIfNecessary() {
29
+
30
+ if (this._state === NavigationHandler.State.STARTING
31
+ || this._state === NavigationHandler.State.STARTED) {
32
+ const msg = 'Navigation already started';
33
+ console.log(msg);
34
+ return Promise.resolve(msg);
35
+ }
36
+
37
+ this._state = NavigationHandler.State.STARTING;
38
+
39
+ const promise = this.locationSource.start();
40
+ promise
41
+ .then(() => (this._state = NavigationHandler.State.STARTED))
42
+ .catch(() => (this._state = NavigationHandler.State.STOPPPED));
43
+
44
+ return promise;
45
+ }
46
+
47
+ stop() {
48
+
49
+ if (this._state === NavigationHandler.State.STOPPPED) {
50
+ const msg = 'Navigation already stopped';
51
+ console.log(msg);
52
+ return Promise.resolve(msg);
53
+ }
54
+
55
+ this.locationSource.stop();
56
+ this._state = NavigationHandler.State.STOPPPED;
57
+ return Promise.resolve();
58
+ }
59
+
60
+ }
61
+
62
+ export default NavigationHandler;
@@ -0,0 +1,3 @@
1
+ import NavigationHandler from './NavigationHandler';
2
+
3
+ export { NavigationHandler };
@@ -0,0 +1,39 @@
1
+ import noop from 'lodash.noop';
2
+
3
+ /**
4
+ * @private
5
+ */
6
+ class FakeLocationSource {
7
+
8
+ constructor(callback) {
9
+ this.callback = callback || noop;
10
+ }
11
+
12
+ start() {
13
+ this.interval = setInterval(() => {
14
+ this.callback({
15
+ location: {
16
+ lat: 45,
17
+ lng: 5,
18
+ alt: 0
19
+ },
20
+ attitude: {
21
+ quaternion: [1, 0, 0, 0]
22
+ }
23
+ });
24
+ }, 1000);
25
+
26
+ return Promise.resolve();
27
+ }
28
+
29
+ stop() {
30
+ if (this.interval) {
31
+ clearInterval(this.interval);
32
+ this.interval = null;
33
+ }
34
+ return Promise.resolve();
35
+ }
36
+
37
+ }
38
+
39
+ export default FakeLocationSource;
@@ -0,0 +1,20 @@
1
+ module.exports = {
2
+ module: {
3
+ rules: [
4
+ {
5
+ test: /\.jsx?$/,
6
+ include: [
7
+ /src/,
8
+ /wemap-(.*)/
9
+ ],
10
+ exclude: /(\/mapbox-gl\/|.test.js|.spec.js|.mock.js)/,
11
+ use: { loader: 'babel-loader' }
12
+ },
13
+ {
14
+ test: /\.css$/,
15
+ use: { loader: 'css-loader' }
16
+ }
17
+ ]
18
+ },
19
+ resolve: { extensions: ['.js', '.jsx'] }
20
+ };
@@ -0,0 +1,24 @@
1
+ const merge = require('webpack-merge');
2
+ const path = require('path');
3
+
4
+ const common = require('./webpack.common.js');
5
+
6
+ const debugFolder = path.join(__dirname, '../debug');
7
+
8
+ module.exports = merge(common, {
9
+ mode: 'development',
10
+ entry: { 'navigation-components': './src/components/index.js' },
11
+ output: {
12
+ publicPath: '/js/',
13
+ filename: '[name].js',
14
+ path: path.join(debugFolder, 'js'),
15
+ libraryTarget: 'global'
16
+ },
17
+ devServer: {
18
+ contentBase: debugFolder,
19
+ inline: true,
20
+ hot: true,
21
+ host: '0.0.0.0',
22
+ port: 9000
23
+ }
24
+ });
@@ -0,0 +1,15 @@
1
+ const merge = require('webpack-merge');
2
+ const path = require('path');
3
+
4
+ const common = require('./webpack.common.js');
5
+ const config = require('../config.json');
6
+
7
+ module.exports = merge(common, {
8
+ mode: 'production',
9
+ entry: './src/index.js',
10
+ output: {
11
+ filename: config.distFileName,
12
+ path: path.join(__dirname, '../', config.distFolder),
13
+ libraryTarget: 'commonjs2'
14
+ }
15
+ });