@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,312 @@
1
+ import noop from 'lodash.noop';
2
+
3
+ import {
4
+ Itinerary, WGS84UserPosition
5
+ } from '@wemap/geo';
6
+ import { Rotations, Utils as MathUtils } from '@wemap/maths';
7
+
8
+ import LocationSource from './LocationSource';
9
+ import AttitudeHandler from '../attitude/AttitudeHandler';
10
+ import Constants from '../Constants';
11
+ import SensorsLogger from '../sensors/SensorsLogger';
12
+ import SensorsCompatibility from '../sensors/SensorsCompatibility';
13
+ import StepDetection from './steps/StepDetection';
14
+ import HeadingUnlocker from './pdr/HeadingUnlocker';
15
+ import ThugDetector from './pdr/ThugDetector';
16
+ import Smoother from './pdr/Smoother';
17
+
18
+
19
+ const MM_PDR_ANGLE = 20;
20
+ const MM_PDR_DIST = 15;
21
+ const MM_CONV_SPEED = 0.7;
22
+
23
+ const LO_SEGMENT_SIZE = 1.5;
24
+
25
+ const DEFAULT_OPTIONS = {
26
+ stepdetectionlocker: true,
27
+ smoother: true,
28
+ onThugEvent: noop
29
+ };
30
+
31
+ class PdrLocationSource extends LocationSource {
32
+
33
+ // pdrLocation can be different from this.pose.location
34
+ // pdrLocation is raw location calculated by PdrLocationSource where
35
+ // this.pose.location is smoothed location.
36
+ pdrLocation = null;
37
+
38
+ constructor(callback, options = DEFAULT_OPTIONS) {
39
+ super(callback);
40
+
41
+ // Assign params
42
+ this.callback = callback;
43
+ this.stepDetectionLockerEnabled = options.stepdetectionlocker !== null ? options.stepdetectionlocker : DEFAULT_OPTIONS.stepdetectionlocker;
44
+ this.smootherEnabled = options.smoother !== null ? options.smoother : DEFAULT_OPTIONS.smoother;
45
+ this.onThugEvent = options.onThugEvent !== null ? options.onThugEvent : DEFAULT_OPTIONS.onThugEvent;
46
+
47
+ // Input data providers
48
+ this.attitudeHandler = new AttitudeHandler();
49
+ this.sensorsCompatibility = new SensorsCompatibility();
50
+
51
+ // Helpers for PDR
52
+ this.stepDetection = new StepDetection();
53
+ this.stepDetectionLocker = new HeadingUnlocker();
54
+ this.smoother = new Smoother();
55
+ this.thugDetector = new ThugDetector();
56
+ }
57
+
58
+ /**
59
+ * Start the location source algorithm
60
+ * @override
61
+ */
62
+ start() {
63
+ super.start();
64
+
65
+ return Promise.all([
66
+ this.sensorsCompatibility.startImu(this.onNewMotion, {
67
+ accelerometer: true,
68
+ gyroscope: true
69
+ }),
70
+ this.attitudeHandler.startRelative(this.onNewAttitude, AttitudeHandler.RelativeMethod.INTERNAL_EKF)
71
+ ]);
72
+ }
73
+
74
+ /**
75
+ * Stop the location source algorithm
76
+ * @override
77
+ */
78
+ stop() {
79
+ super.stop();
80
+
81
+ this.attitudeHandler.stopRelative();
82
+ this.sensorsCompatibility.stopImu();
83
+ }
84
+
85
+ /**
86
+ * Get provider name
87
+ * @override
88
+ */
89
+ get providerNameWithOptions() {
90
+ let name = this.providerName;
91
+ name += ' (locker: ';
92
+ name += this.stepDetectionLockerEnabled ? 'Y' : 'N';
93
+ name += ', ';
94
+ name += 'smoo: ';
95
+ name += this.smootherEnabled ? 'Y' : 'N';
96
+ name += ', ';
97
+ name += 'mm: ';
98
+ name += this.mapMatching ? 'Y' : 'N';
99
+ name += ')';
100
+ return name;
101
+ }
102
+
103
+ /**
104
+ * Get provider name
105
+ * @override
106
+ */
107
+ static get providerName() {
108
+ return Constants.LOCATION_SOURCE_PROVIDERS.PDR;
109
+ }
110
+
111
+ /**
112
+ * @override
113
+ */
114
+ notify() {
115
+ if (!this.pose.attitude) {
116
+ return;
117
+ }
118
+ super.notify();
119
+ }
120
+
121
+ setLogger(logger) {
122
+ super.setLogger(logger);
123
+ this.sensorsCompatibility.setLogger(logger);
124
+ this.attitudeHandler.setLogger(logger);
125
+ }
126
+
127
+ setHeading(heading) {
128
+ this.attitudeHandler.setRelativeHeading(heading);
129
+ if (this.logger) {
130
+ this.logger.feedWithCurrentTime(SensorsLogger.DataType.USER_INPUT_HEADING, heading);
131
+ }
132
+ }
133
+
134
+ setLocation(location) {
135
+ this.pdrLocation = location;
136
+
137
+ if (this.smootherEnabled) {
138
+ this.smoother.generateNextLocations(location, true);
139
+ // this.smoother.pullLocation(location.time) should never return null
140
+ this.pose.location = this.smoother.pullLocation(location.time);
141
+ } else {
142
+ this.pose.location = location;
143
+ }
144
+
145
+ this.notify();
146
+
147
+ if (this.logger) {
148
+ const data = [location.lat, location.lng];
149
+ if (location.altitude) {
150
+ data.push(location.altitude);
151
+ }
152
+ this.logger.feedWithCurrentTime(SensorsLogger.DataType.USER_INPUT_POSITION, data);
153
+ }
154
+ }
155
+
156
+ onNewAttitude = (attitude) => {
157
+ this.pose.attitude = attitude;
158
+ }
159
+
160
+ onNewMotion = (motionEvent) => {
161
+
162
+ if (!this.pose.attitude) {
163
+ // We should wait attitude for stepDetectionLocker and linear acceleration
164
+ return;
165
+ }
166
+
167
+ if (!this.pdrLocation) {
168
+ // We should wait at least an input location for PDR
169
+ return;
170
+ }
171
+
172
+ if (this.stepDetectionLockerEnabled
173
+ && this.stepDetectionLocker.locked
174
+ && this.stepDetectionLocker.feedHeading(this.pose.attitude.heading)) {
175
+ // Step detection is locked by stepDetectionLocker
176
+ // We still need to notify for attitude
177
+ this.notify();
178
+ return;
179
+ }
180
+
181
+ const timestamp = motionEvent.timestamp;
182
+ const heading = this.pose.attitude.heading;
183
+ const acceleration = motionEvent.acc;
184
+
185
+ if (this.onThugEvent && this.thugDetector.compute(timestamp, acceleration)) {
186
+ this.onThugEvent();
187
+ }
188
+
189
+ /**
190
+ * Step Detection and Step Size Detection
191
+ */
192
+ const linearAcc = Rotations.computeLinearAcceleration(
193
+ this.pose.attitude.quaternion, acceleration);
194
+ const stepDetected = this.stepDetection.compute(timestamp, linearAcc, motionEvent.gyr);
195
+
196
+ if (stepDetected) {
197
+
198
+ this.pdrLocation = this.calculateNewLocation(this.pdrLocation, timestamp,
199
+ heading, this.stepDetection.lastStepSize);
200
+
201
+ if (this.smootherEnabled) {
202
+ this.smoother.generateNextLocations(this.pdrLocation);
203
+ }
204
+
205
+ /**
206
+ * Update current_position
207
+ */
208
+ this.pose.location = this.smootherEnabled
209
+ ? this.smoother.pullLocation(timestamp)
210
+ : this.pdrLocation;
211
+
212
+ } else if (this.smootherEnabled) {
213
+ // If no step is detected, we pull last known location from smoother until now (timestamp).
214
+ const smoothedLocation = this.smoother.pullLocation(timestamp);
215
+ if (smoothedLocation) {
216
+ this.pose.location = smoothedLocation;
217
+ }
218
+ }
219
+
220
+ this.notify();
221
+ }
222
+
223
+ calculateNewLocation(previousLocation, timestamp, heading, stepSize) {
224
+
225
+ /**
226
+ * Compute new_position
227
+ */
228
+ const newLocationWithoutMM = previousLocation.clone();
229
+ newLocationWithoutMM.move(stepSize, heading);
230
+ newLocationWithoutMM.bearing = MathUtils.rad2deg(heading);
231
+ newLocationWithoutMM.time = timestamp;
232
+
233
+ if (!this.mapMatching) {
234
+ return newLocationWithoutMM;
235
+ }
236
+
237
+ const projection = this.mapMatching.getProjection(newLocationWithoutMM);
238
+
239
+ if (!projection || !projection.projection) {
240
+ return newLocationWithoutMM;
241
+ }
242
+
243
+ if (projection.distanceFromNearestElement < stepSize) {
244
+ return WGS84UserPosition.fromWGS84(projection.projection, newLocationWithoutMM);
245
+ }
246
+
247
+ /**
248
+ * Update new_position
249
+ */
250
+ const smoothedDistance = projection.distanceFromNearestElement * MM_CONV_SPEED;
251
+ const smoothedBearing = previousLocation.bearingTo(projection.projection);
252
+ const smoothedLocation = previousLocation.clone();
253
+ smoothedLocation.move(smoothedDistance, smoothedBearing);
254
+
255
+ return WGS84UserPosition.fromWGS84(smoothedLocation, newLocationWithoutMM);
256
+ }
257
+
258
+
259
+ /**
260
+ * MapMatching
261
+ */
262
+
263
+ enableMapMatching(network = [], maxDistance = MM_PDR_DIST, maxAngleBearing = MM_PDR_ANGLE) {
264
+ super.enableMapMatching(network, maxDistance, maxAngleBearing);
265
+ }
266
+
267
+
268
+ /**
269
+ * Itinerary and PDR Locker
270
+ */
271
+
272
+ setItinerary(itinerary) {
273
+ super.setItinerary(itinerary);
274
+
275
+ if (this.stepDetectionLockerEnabled) {
276
+ this.stepDetectionLocker.setWaitingOrientation(
277
+ PdrLocationSource.retrieveWaitingOrientationFromItinerary(itinerary)
278
+ );
279
+ }
280
+ }
281
+
282
+ static retrieveWaitingOrientationFromItinerary(itinerary) {
283
+
284
+ if (!itinerary) {
285
+ throw new Error('Empty itinerary');
286
+ }
287
+
288
+ if (!(itinerary instanceof Itinerary)) {
289
+ throw new TypeError(itinerary + ' is not an instance of itinerary');
290
+ }
291
+
292
+ if (itinerary.length < 1) {
293
+ throw new Error('Empty itinerary');
294
+ }
295
+
296
+ if (itinerary.firstEdge.getLength() > LO_SEGMENT_SIZE) {
297
+ return itinerary.firstEdge.getBearing();
298
+ }
299
+
300
+ if (itinerary.length < 2) {
301
+ throw new Error('Itinerary is too short');
302
+ }
303
+ return itinerary.secondEdge.getBearing();
304
+ }
305
+
306
+ setStepDetectionLockerOrientation(orientation) {
307
+ this.stepDetectionLocker.setWaitingOrientation(orientation);
308
+ }
309
+
310
+ }
311
+
312
+ export default PdrLocationSource;
@@ -0,0 +1,77 @@
1
+ import Logger from '@wemap/logger';
2
+
3
+ let currentId = 0;
4
+ const objectsIdMap = new WeakMap();
5
+
6
+ let pushEvents = {};
7
+ let pushEventsRef = {};
8
+
9
+ let interval;
10
+ let initDate;
11
+
12
+ class ProvidersLogger {
13
+
14
+ static initializeInterval() {
15
+
16
+ if (interval) {
17
+ return;
18
+ }
19
+
20
+ interval = setInterval(() => {
21
+
22
+ for (const [key, value] of Object.entries(pushEvents)) {
23
+ Logger.debug('Received ' + value + ' values from ' + pushEventsRef[key].constructor.name + ' last second');
24
+ }
25
+
26
+ pushEvents = {};
27
+ pushEventsRef = {};
28
+ }, 1000);
29
+ }
30
+
31
+ static getObjectId(object) {
32
+ if (!objectsIdMap.has(object)) {
33
+ objectsIdMap.set(object, ++currentId);
34
+ }
35
+ return objectsIdMap.get(object);
36
+ }
37
+
38
+ static addEvent(object, method) {
39
+
40
+ if (!ProvidersLogger.enabled) {
41
+ return;
42
+ }
43
+
44
+ if (!initDate) {
45
+ initDate = Date.now();
46
+ }
47
+
48
+ ProvidersLogger.initializeInterval();
49
+
50
+ const objectId = ProvidersLogger.getObjectId(object);
51
+ const objectClassName = object.constructor.name;
52
+
53
+ Logger.debug(objectClassName + '[' + objectId + '].' + method);
54
+ }
55
+
56
+ static addPushEvent(object) {
57
+
58
+ if (!ProvidersLogger.enabled) {
59
+ return;
60
+ }
61
+
62
+ const objectId = ProvidersLogger.getObjectId(object);
63
+
64
+ let counter = pushEvents[objectId];
65
+ if (!counter) {
66
+ counter = 0;
67
+ pushEventsRef[objectId] = object;
68
+ }
69
+ pushEvents[objectId] = counter + 1;
70
+ }
71
+
72
+ static get enabled() {
73
+ return Logger.LOG_LEVEL === Logger.DEBUG;
74
+ }
75
+
76
+ }
77
+ export default ProvidersLogger;
@@ -0,0 +1,41 @@
1
+ import isnumber from 'lodash.isnumber';
2
+
3
+ import {Utils as MathUtils} from '@wemap/maths';
4
+
5
+ const LO_ANGLE = 15 * Math.PI / 180;
6
+
7
+ /**
8
+ * HeadingUnlocker is designed to block Pdr Step Detection until user is aiming in the right direction
9
+ */
10
+ class HeadingUnlocker {
11
+
12
+ constructor() {
13
+ // HeadingUnlocker status, by default it is unlocked
14
+ this.locked = false;
15
+ }
16
+
17
+ setWaitingOrientation(waitingOrientation) {
18
+ this.waitingOrientation = waitingOrientation;
19
+ this.locked = true;
20
+ }
21
+
22
+ feedHeading(heading) {
23
+
24
+ if (!isnumber(this.waitingOrientation)) {
25
+ throw new Error('HeadingUnlocker has not been initialized by setWaitingOrientation');
26
+ }
27
+
28
+ if (!this.locked) {
29
+ throw new Error('HeadingUnlocker is already unlocked');
30
+ }
31
+
32
+ if (Math.abs(MathUtils.diffAngle(heading, this.waitingOrientation)) < LO_ANGLE) {
33
+ this.locked = false;
34
+ }
35
+
36
+ return this.locked;
37
+ }
38
+
39
+ }
40
+
41
+ export default HeadingUnlocker;
@@ -0,0 +1,26 @@
1
+ import chai from 'chai';
2
+ import chaiAlmost from 'chai-almost';
3
+
4
+ import HeadingUnlocker from './HeadingUnlocker';
5
+
6
+ const expect = chai.expect;
7
+ chai.use(chaiAlmost());
8
+
9
+ describe('HeadingUnlocker test short first edge', () => {
10
+
11
+ it('Should return the good value', () => {
12
+
13
+ const headingUnlocker = new HeadingUnlocker();
14
+ headingUnlocker.setWaitingOrientation(102 * Math.PI / 180);
15
+
16
+ headingUnlocker.feedHeading(0 * Math.PI / 180);
17
+ expect(headingUnlocker.locked).to.be.true;
18
+
19
+ headingUnlocker.feedHeading(130 * Math.PI / 180);
20
+ expect(headingUnlocker.locked).to.be.true;
21
+
22
+ headingUnlocker.feedHeading(100 * Math.PI / 180);
23
+ expect(headingUnlocker.locked).to.be.false;
24
+
25
+ });
26
+ });
@@ -0,0 +1,90 @@
1
+ /* eslint max-statements: ["error", 27]*/
2
+ import {WGS84UserPosition} from '@wemap/geo';
3
+
4
+ // Generated locations by second
5
+ const GEN_FREQUENCY = 60;
6
+
7
+ // Min and max time for flyby (in second)
8
+ const MIN_FLYBY_TIME = 0.25;
9
+ const MAX_FLYBY_TIME = 1;
10
+
11
+ class Smoother {
12
+
13
+ constructor() {
14
+ this.locationsQueue = [];
15
+ }
16
+
17
+ /**
18
+ * Calculate smoothed positions for the next milliseconds given a new location
19
+ */
20
+ generateNextLocations(_newLocation, flyby = false) {
21
+
22
+ if (!(_newLocation instanceof WGS84UserPosition)) {
23
+ throw new TypeError('newLocation is not instance of WGS84UserPosition');
24
+ }
25
+
26
+ const newLocation = _newLocation.clone();
27
+ if (!newLocation.hasOwnProperty('time')) {
28
+ throw new Error('newLocation does not have time property');
29
+ }
30
+
31
+ if (!this.previousLocation) {
32
+ this.previousLocation = newLocation;
33
+ this.locationsQueue.push(this.previousLocation);
34
+ return;
35
+ }
36
+
37
+ const distance = this.previousLocation.distanceTo(newLocation);
38
+ const azimuth = this.previousLocation.bearingTo(newLocation);
39
+
40
+ let refTimestamp = newLocation.time;
41
+
42
+ const queueLength = this.locationsQueue.length;
43
+ if (queueLength) {
44
+ refTimestamp = Math.max(refTimestamp, this.locationsQueue[queueLength - 1].time);
45
+ }
46
+
47
+ let diffTime = newLocation.time - this.previousLocation.time;
48
+
49
+ if (flyby) {
50
+ diffTime = MAX_FLYBY_TIME;
51
+ }
52
+
53
+ const nSamples = GEN_FREQUENCY * Math.min(Math.max(MIN_FLYBY_TIME, diffTime), MAX_FLYBY_TIME);
54
+
55
+ let i = 1;
56
+ while (i < nSamples + 1) {
57
+ i = Math.min(i, nSamples);
58
+ const smoothedLocation = this.previousLocation.destinationPoint(distance * i / nSamples, azimuth);
59
+ smoothedLocation.time = refTimestamp + (i - 1) / GEN_FREQUENCY;
60
+ this.locationsQueue.push(smoothedLocation);
61
+ i++;
62
+ }
63
+
64
+ this.previousLocation = newLocation;
65
+ }
66
+
67
+ /**
68
+ * Pull locations until timestamp and return the last one.
69
+ * @param {*} timestamp location returned is the last before this timestamp
70
+ * @returns last known location before timestamp, undefined otherwise
71
+ */
72
+ pullLocation(timestamp) {
73
+
74
+ if ((typeof timestamp === 'undefined' || !timestamp)
75
+ && this.locationsQueue.length > 0) {
76
+ const output = this.locationsQueue[this.locationsQueue.length - 1];
77
+ this.locationsQueue = [];
78
+ return output;
79
+ }
80
+
81
+ let location;
82
+ while (this.locationsQueue.length && this.locationsQueue[0].time <= timestamp) {
83
+ location = this.locationsQueue.shift();
84
+ }
85
+ return location;
86
+ }
87
+
88
+ }
89
+
90
+ export default Smoother;