@wemap/positioning 2.0.0 → 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 (71) 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 +96 -34
  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 +74 -0
  26. package/src/components/index.js +30 -0
  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 +2 -2
  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 +77 -0
  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/providers/GnssPdrLocationSource.js +1 -1
  67. package/webpack/webpack.dev.js +1 -1
  68. package/debug/index.html +0 -15
  69. package/debug/index.old.html +0 -37
  70. package/scripts/release-github.js +0 -216
  71. package/src/providers/FakeLocationSource.js +0 -36
@@ -0,0 +1,233 @@
1
+ /* eslint max-statements: ["error", 40]*/
2
+
3
+ import { WGS84UserPosition } from '@wemap/geo';
4
+
5
+ import MapMatchingProvider from '../others/MapMatchingProvider';
6
+ import PdrProvider from './pdr/PdrProvider';
7
+ import GnssWifiProvider from '../position/GnssWifiProvider';
8
+ import EventType from '../../events/EventType';
9
+ import AbsoluteAttitudeProvider from '../attitude/AbsoluteAttitudeProvider';
10
+ import Constants from '../Constants';
11
+ import ProviderError from '../../events/ProviderError';
12
+
13
+ const GPF_ACCURACY = 25;
14
+ const GPF_DISTANCE = 25;
15
+
16
+ const MM_GNSS_DIST = 20;
17
+ const MM_GNSS_ANGLE = 20;
18
+
19
+ class GnssWifiPdrProvider extends MapMatchingProvider {
20
+
21
+ /**
22
+ * @override
23
+ */
24
+ constructor(onEvent, onError, options) {
25
+ super(onEvent, onError, options);
26
+
27
+ this.pdrProvider = new PdrProvider(e => this.onPdrEvent(e), e => this.onProviderError(e), options);
28
+ this.gnssWifiProvider = new GnssWifiProvider(e => this.onGnssWifiEvent(e), e => this.onProviderError(e), options);
29
+ this.absoluteAttitudeProvider = new AbsoluteAttitudeProvider(
30
+ e => this.onAbsoluteAttitudeEvent(e),
31
+ e => this.onProviderError(e),
32
+ options
33
+ );
34
+
35
+ this.gpsLastUpdate = 0;
36
+ this.isFirstGnssUpdate = true;
37
+ this.isFirstAttitudeUpdate = true;
38
+ }
39
+
40
+
41
+ /**
42
+ * @override
43
+ */
44
+ static get displayName() {
45
+ return 'GnssWifiPdr';
46
+ }
47
+
48
+ /**
49
+ * @override
50
+ */
51
+ static get eventsType() {
52
+ return [EventType.AbsoluteAttitude, EventType.AbsolutePosition];
53
+ }
54
+
55
+ /**
56
+ * @override
57
+ */
58
+ static get requiredProviders() {
59
+ return [PdrProvider, GnssWifiProvider, AbsoluteAttitudeProvider];
60
+ }
61
+
62
+
63
+ /**
64
+ * @override
65
+ */
66
+ startInternal() {
67
+ this.pdrProvider.start();
68
+ this.gnssWifiProvider.start();
69
+ this.absoluteAttitudeProvider.start();
70
+ }
71
+
72
+ /**
73
+ * @override
74
+ */
75
+ stop() {
76
+ this.imuProvider.stop();
77
+ this.gnssWifiProvider.stop();
78
+ this.absoluteAttitudeProvider.stop();
79
+ }
80
+
81
+ /**
82
+ * @private
83
+ */
84
+ onPdrEvent(pdrEvent) {
85
+ pdrEvent.forEach(event => {
86
+ if (event.dataType === EventType.AbsolutePosition) {
87
+ this.location = event.data;
88
+ } else if (event.dataType === EventType.AbsoluteAttitude) {
89
+ this.attitude = event.data;
90
+ }
91
+ });
92
+ this.notify(...pdrEvent);
93
+ }
94
+
95
+ /**
96
+ * @private
97
+ */
98
+ onGnssWifiEvent(events) {
99
+
100
+ const gnssWifiEvent = events[0];
101
+ const location = gnssWifiEvent.data;
102
+
103
+ // This should be called to update True North / Magnetic North declination
104
+ this.absoluteAttitudeProvider.setLocation(location);
105
+
106
+ if (location.accuracy > GPF_ACCURACY) {
107
+ return;
108
+ }
109
+
110
+ this.gnssLocation = location.clone();
111
+ this.gnssLocation.alt = Constants.DEFAULT_ALTITUDE;
112
+
113
+ if (!this.location || this.location
114
+ && this.location.distanceTo(this.gnssLocation) > GPF_DISTANCE) {
115
+
116
+ if (!this.mapMatching || !this.attitude) {
117
+ this.pdrProvider.setLocation(this.gnssLocation);
118
+ } else {
119
+
120
+ this.gnssLocation.bearing = this.attitude.headingDegrees;
121
+ const projection = this.mapMatching.getProjection(this.gnssLocation);
122
+
123
+ if (projection && projection.projection) {
124
+
125
+ // Create a new location from projection and new GNSS location.
126
+ const projectedLocation = WGS84UserPosition.fromWGS84(projection.projection, this.gnssLocation);
127
+ this.pdrProvider.setLocation(projectedLocation);
128
+
129
+ // // If nearest element is an edge, use its orientation to set heading
130
+ // if (projection.nearestElement instanceof Edge) {
131
+ // const edgeBearing = projection.nearestElement.bearing;
132
+ // const diff1 = MathUtils.diffAngle(MathUtils.deg2rad(this.gnssLocation.bearing), edgeBearing);
133
+ // const diff2 = MathUtils.diffAngle(MathUtils.deg2rad(this.gnssLocation.bearing), edgeBearing + Math.PI);
134
+ // this.pdrProvider.setHeading(diff1 < diff2 ? edgeBearing : edgeBearing + Math.PI);
135
+ // }
136
+
137
+ if (this.lastAttitude) {
138
+ this.pdrProvider.setHeading(this.lastAttitude.heading);
139
+ }
140
+
141
+ } else {
142
+ this.pdrProvider.setLocation(this.gnssLocation);
143
+ }
144
+ }
145
+ }
146
+
147
+ }
148
+
149
+ onAbsoluteAttitudeEvent(events) {
150
+
151
+ const attitude = events[0].data;
152
+
153
+ if (this.isFirstAttitudeUpdate) {
154
+ this.pdrProvider.setHeading(attitude.heading);
155
+ this.isFirstAttitudeUpdate = false;
156
+ }
157
+
158
+ this.lastAttitude = attitude;
159
+ }
160
+
161
+ onProviderError(errors) {
162
+ this.notifyError(...ProviderError.modifyArrayDataType(errors, EventType.AbsoluteAttitude));
163
+ this.notifyError(...ProviderError.modifyArrayDataType(errors, EventType.AbsolutePosition));
164
+ }
165
+
166
+ /**
167
+ * @override
168
+ */
169
+ static checkAvailabilityErrors() {
170
+ const errors = super.checkAvailabilityErrors();
171
+ return ProviderError.modifyArrayDataType(
172
+ errors,
173
+ EventType.AbsolutePosition
174
+ ).concat(
175
+ ProviderError.modifyArrayDataType(
176
+ errors,
177
+ EventType.AbsoluteAttitude
178
+ )
179
+ );
180
+ }
181
+
182
+ /**
183
+ * MapMatching
184
+ */
185
+
186
+ enableMapMatching(network = null, maxDistance = MM_GNSS_DIST, maxAngleBearing = MM_GNSS_ANGLE) {
187
+ this.pdrProvider.enableMapMatching(network, maxDistance, maxAngleBearing);
188
+ super.enableMapMatching(network, maxDistance, maxAngleBearing);
189
+ }
190
+
191
+
192
+ setNetwork(network) {
193
+ this.pdrProvider.setNetwork(network);
194
+ super.setNetwork(network);
195
+ }
196
+
197
+ /**
198
+ * Itinerary
199
+ */
200
+
201
+ setItinerary(itinerary) {
202
+
203
+ const isFirstItinerary = !this.itinerary;
204
+
205
+ super.setItinerary(itinerary);
206
+
207
+ if (isFirstItinerary && itinerary.length > 0) {
208
+
209
+ // When the first itinerary is received, first or second node (depending on MM_GNSS_DIST) is used as a starting point. No map-matching is needed here as router already provide the projection in the itinerary (node2 is node1 projection on OSRM network).
210
+
211
+ if (!this.gnssLocation
212
+ || itinerary.length < 2
213
+ || !itinerary.points[0].equalsTo(this.gnssLocation)) {
214
+ console.warn('Itinerary has not been calculated from GnssWifiPdrProvider and these is not recommanded');
215
+ }
216
+
217
+ let startEdge;
218
+ if (itinerary.firstEdge.getLength() <= MM_GNSS_DIST) {
219
+ startEdge = itinerary.firstEdge;
220
+ } else {
221
+ startEdge = itinerary.secondEdge;
222
+ }
223
+ const startPoint = WGS84UserPosition.fromWGS84(startEdge.node1);
224
+ startPoint.alt = Constants.DEFAULT_ALTITUDE;
225
+ this.pdrProvider.setLocation(startPoint);
226
+ this.pdrProvider.setStepDetectionLockerOrientation(startEdge.getBearing());
227
+ }
228
+
229
+ }
230
+
231
+ }
232
+
233
+ export default GnssWifiPdrProvider;
@@ -0,0 +1,90 @@
1
+ import Provider from '../Provider';
2
+ import EventType from '../../events/EventType';
3
+
4
+ import GnssWifiProvider from '../position/GnssWifiProvider';
5
+ import AbsoluteAttitudeProvider from '../attitude/AbsoluteAttitudeProvider';
6
+ import ProviderError from '../../events/ProviderError';
7
+
8
+ /**
9
+ * Pose provider is the provider used by the PositioningHandler. It uses the best fusion
10
+ * of what he can and provides an AbsoluteAttitude and an AbsolutePosition as an output.
11
+ */
12
+ class PoseProvider extends Provider {
13
+
14
+ /**
15
+ * @override
16
+ */
17
+ constructor(onEvent, onError, options) {
18
+ super(onEvent, onError, options);
19
+
20
+ this.absoluteAttitudeProvider = new AbsoluteAttitudeProvider(onEvent, e => this.onAbsoluteAttitudeError(e));
21
+ this.gnssWifiProvider = new GnssWifiProvider((events) => {
22
+ this.absoluteAttitudeProvider.setLocation(events[0].data);
23
+ onEvent(events);
24
+ }, e => this.onGnssWifiError(e));
25
+ }
26
+
27
+ /**
28
+ * @override
29
+ */
30
+ static get displayName() {
31
+ return 'Pose provider';
32
+ }
33
+
34
+ /**
35
+ * @override
36
+ */
37
+ static get eventsType() {
38
+ return [EventType.AbsoluteAttitude, EventType.AbsolutePosition];
39
+ }
40
+
41
+ /**
42
+ * Return the list of required providers
43
+ */
44
+ static get requiredProviders() {
45
+ return [AbsoluteAttitudeProvider, GnssWifiProvider];
46
+ }
47
+
48
+ /**
49
+ * @override
50
+ */
51
+ startInternal() {
52
+ this.absoluteAttitudeProvider.start();
53
+ this.gnssWifiProvider.start();
54
+ }
55
+
56
+ /**
57
+ * @override
58
+ */
59
+ stopInternal() {
60
+ this.absoluteAttitudeProvider.stop();
61
+ this.gnssWifiProvider.stop();
62
+ }
63
+
64
+ onAbsoluteAttitudeError(errors) {
65
+ this.notifyError(...ProviderError.modifyArrayDataType(errors, EventType.AbsoluteAttitude));
66
+ }
67
+
68
+ onGnssWifiError(errors) {
69
+ this.notifyError(...ProviderError.modifyArrayDataType(errors, EventType.AbsolutePosition));
70
+ }
71
+
72
+ /**
73
+ * @override
74
+ */
75
+ static checkAvailabilityErrors() {
76
+ const errorsAttitude = AbsoluteAttitudeProvider.checkAvailabilityErrors();
77
+ const errorsGnssWifi = GnssWifiProvider.checkAvailabilityErrors();
78
+ return ProviderError.modifyArrayDataType(
79
+ errorsGnssWifi,
80
+ EventType.AbsolutePosition
81
+ ).concat(
82
+ ProviderError.modifyArrayDataType(
83
+ errorsAttitude,
84
+ EventType.AbsoluteAttitude
85
+ )
86
+ );
87
+ }
88
+ }
89
+
90
+ export default PoseProvider;
@@ -0,0 +1,352 @@
1
+ import noop from 'lodash.noop';
2
+
3
+ import {
4
+ Constants as GeoConstants, Itinerary, WGS84UserPosition
5
+ } from '@wemap/geo';
6
+ import {
7
+ rad2deg, Quaternion
8
+ } from '@wemap/maths';
9
+
10
+ import StepDetection from './steps/StepDetection';
11
+ import HeadingUnlocker from './helpers/HeadingUnlocker';
12
+ import ThugDetector from './helpers/ThugDetector';
13
+ import Smoother from './helpers/Smoother';
14
+ import RelativeAttitudeProvider from '../../attitude/RelativeAttitudeProvider';
15
+ import ImuProvider from '../../others/ImuProvider';
16
+ import EventType from '../../../events/EventType';
17
+ import MapMatchingProvider from '../../others/MapMatchingProvider';
18
+ import ProviderError from '../../../events/ProviderError';
19
+
20
+
21
+ const MM_PDR_ANGLE = 20;
22
+ const MM_PDR_DIST = 15;
23
+ const MM_CONV_SPEED = 0.7;
24
+
25
+ const LO_SEGMENT_SIZE = 1.5;
26
+
27
+ const DEFAULT_OPTIONS = {
28
+ stepdetectionlocker: true,
29
+ smoother: true,
30
+ onThugEvent: noop
31
+ };
32
+
33
+ class PdrProvider extends MapMatchingProvider {
34
+
35
+ // pdrLocation can be different from this.location
36
+ // pdrLocation is raw location calculated by PdrProvider where
37
+ // this.location is smoothed location.
38
+ pdrLocation = null;
39
+
40
+ /**
41
+ * @override
42
+ */
43
+ constructor(onEvent, onError, options) {
44
+ super(onEvent, onError, Object.assign(DEFAULT_OPTIONS, options || {}));
45
+
46
+ // Input data providers
47
+ this.relativeAttitudeProvider = new RelativeAttitudeProvider(
48
+ e => this.onRelativeAttitudeEvent(e),
49
+ e => this.onProviderError(e),
50
+ options
51
+ );
52
+ this.imuProvider = new ImuProvider(
53
+ e => this.onImuEvent(e),
54
+ e => this.onProviderError(e),
55
+ Object.assign(options || {}, { require: [EventType.Acceleration, EventType.AngularRate] })
56
+ );
57
+
58
+ // Helpers for PDR
59
+ this.stepDetection = new StepDetection();
60
+ this.stepDetectionLocker = new HeadingUnlocker();
61
+ this.smoother = new Smoother();
62
+ this.thugDetector = new ThugDetector();
63
+
64
+ if (options.useMapMatching) {
65
+ this.enableMapMatching();
66
+ }
67
+ }
68
+
69
+
70
+ /**
71
+ * @override
72
+ */
73
+ static get displayName() {
74
+ return 'PDR';
75
+ }
76
+
77
+ /**
78
+ * @override
79
+ */
80
+ static get eventsType() {
81
+ return [EventType.AbsoluteAttitude, EventType.AbsolutePosition];
82
+ }
83
+
84
+ /**
85
+ * @override
86
+ */
87
+ static get requiredProviders() {
88
+ return [ImuProvider, RelativeAttitudeProvider];
89
+ }
90
+
91
+
92
+ /**
93
+ * @override
94
+ */
95
+ startInternal() {
96
+ this.imuProvider.start();
97
+ this.relativeAttitudeProvider.start();
98
+ }
99
+
100
+ /**
101
+ * @override
102
+ */
103
+ stopInternal() {
104
+ this.imuProvider.stop();
105
+ this.relativeAttitudeProvider.stop();
106
+ }
107
+
108
+ /**
109
+ * Get provider name
110
+ * @override
111
+ */
112
+ get nameWithOptions() {
113
+ let name = this.name;
114
+ name += ' (locker: ';
115
+ name += this.options.stepdetectionlocker ? 'Y' : 'N';
116
+ name += ', ';
117
+ name += 'smoo: ';
118
+ name += this.options.smoother ? 'Y' : 'N';
119
+ name += ', ';
120
+ name += 'mm: ';
121
+ name += this.options.useMapMatching ? 'Y' : 'N';
122
+ name += ')';
123
+ return name;
124
+ }
125
+
126
+ setHeading(heading) {
127
+ this.relativeAttitudeProvider.setOffset(heading);
128
+ }
129
+
130
+ setLocation(location) {
131
+ this.pdrLocation = location;
132
+
133
+ if (this.options.smoother) {
134
+ this.smoother.generateNextLocations(location, true);
135
+ // this.smoother.pullLocation(location.time) should never return null
136
+ this.location = this.smoother.pullLocation(location.time);
137
+ } else {
138
+ this.location = location.clone();
139
+ }
140
+
141
+ this.notify(this.createEvent(EventType.AbsolutePosition, this.location));
142
+ }
143
+
144
+ /**
145
+ * @override
146
+ */
147
+ onRelativeAttitudeEvent = events => {
148
+ const attitudeEvent = events[0];
149
+ this.attitude = attitudeEvent.data;
150
+
151
+ const newEvent = attitudeEvent.clone();
152
+ newEvent.dataType = EventType.AbsoluteAttitude;
153
+ this.notify(newEvent);
154
+ }
155
+
156
+ /**
157
+ * @private
158
+ */
159
+ onImuEvent = imuEvent => {
160
+
161
+ if (!this.attitude) {
162
+ // We should wait attitude for stepDetectionLocker and linear acceleration
163
+ return;
164
+ }
165
+
166
+ if (!this.pdrLocation) {
167
+ // We should wait at least an input location for PDR
168
+ return;
169
+ }
170
+
171
+ if (this.options.stepdetectionlocker
172
+ && this.stepDetectionLocker.locked
173
+ && this.stepDetectionLocker.feedHeading(this.attitude.heading)) {
174
+ // Step detection is locked by stepDetectionLocker
175
+ return;
176
+ }
177
+
178
+
179
+ let timestamp, acceleration, angularRate;
180
+ imuEvent.forEach(event => {
181
+ if (event.dataType === EventType.Acceleration) {
182
+ acceleration = event.data;
183
+ timestamp = event.timestamp;
184
+ } else if (event.dataType === EventType.AngularRate) {
185
+ angularRate = event.data;
186
+ }
187
+ });
188
+
189
+ const heading = this.attitude.heading;
190
+
191
+ if (this.onThugEvent && this.thugDetector.compute(timestamp, acceleration)) {
192
+ this.onThugEvent();
193
+ }
194
+
195
+ /**
196
+ * Step Detection and Step Size Detection
197
+ */
198
+ const linearAcc = PdrProvider.computeLinearAcceleration(
199
+ this.attitude.quaternion, acceleration);
200
+ const stepDetected = this.stepDetection.compute(timestamp, linearAcc, angularRate);
201
+
202
+ if (stepDetected) {
203
+
204
+ this.pdrLocation = this.calculateNewLocation(this.pdrLocation, timestamp,
205
+ heading, this.stepDetection.lastStepSize);
206
+
207
+ /**
208
+ * Update current_position
209
+ */
210
+ if (this.options.smoother) {
211
+ this.smoother.generateNextLocations(this.pdrLocation);
212
+ const newLocation = this.smoother.pullLocation(timestamp);
213
+ // At this time, newLocation can be null if a new step has been detected
214
+ // and smoother has not been completely consumed.
215
+ if (newLocation) {
216
+ this.location = newLocation;
217
+ }
218
+ } else {
219
+ this.location = this.pdrLocation;
220
+ }
221
+
222
+ } else if (this.options.smoother) {
223
+ // If no step is detected, we pull last known location from smoother until now (timestamp).
224
+ const smoothedLocation = this.smoother.pullLocation(timestamp);
225
+ if (!smoothedLocation) {
226
+ return;
227
+ }
228
+ this.location = smoothedLocation;
229
+ } else {
230
+ return;
231
+ }
232
+
233
+ this.notify(this.createEvent(EventType.AbsolutePosition, this.location));
234
+ }
235
+
236
+ onProviderError(errors) {
237
+ this.notifyError(...ProviderError.modifyArrayDataType(errors, EventType.AbsoluteAttitude));
238
+ this.notifyError(...ProviderError.modifyArrayDataType(errors, EventType.AbsolutePosition));
239
+ }
240
+
241
+ /**
242
+ * @override
243
+ */
244
+ static checkAvailabilityErrors() {
245
+ const errors = super.checkAvailabilityErrors();
246
+ return ProviderError.modifyArrayDataType(
247
+ errors,
248
+ EventType.AbsolutePosition
249
+ ).concat(
250
+ ProviderError.modifyArrayDataType(
251
+ errors,
252
+ EventType.AbsoluteAttitude
253
+ )
254
+ );
255
+ }
256
+
257
+ calculateNewLocation(previousLocation, timestamp, heading, stepSize) {
258
+
259
+ /**
260
+ * Compute new_position
261
+ */
262
+ const newLocationWithoutMM = previousLocation.clone();
263
+ newLocationWithoutMM.move(stepSize, heading);
264
+ newLocationWithoutMM.bearing = rad2deg(heading);
265
+ newLocationWithoutMM.time = timestamp;
266
+
267
+ if (!this.mapMatching) {
268
+ return newLocationWithoutMM;
269
+ }
270
+
271
+ const projection = this.mapMatching.getProjection(newLocationWithoutMM);
272
+
273
+ if (!projection || !projection.projection) {
274
+ return newLocationWithoutMM;
275
+ }
276
+
277
+ if (projection.distanceFromNearestElement < stepSize) {
278
+ return WGS84UserPosition.fromWGS84(projection.projection, newLocationWithoutMM);
279
+ }
280
+
281
+ /**
282
+ * Update new_position
283
+ */
284
+ const smoothedDistance = projection.distanceFromNearestElement * MM_CONV_SPEED;
285
+ const smoothedBearing = previousLocation.bearingTo(projection.projection);
286
+ const smoothedLocation = previousLocation.clone();
287
+ smoothedLocation.move(smoothedDistance, smoothedBearing);
288
+
289
+ return WGS84UserPosition.fromWGS84(smoothedLocation, newLocationWithoutMM);
290
+ }
291
+
292
+
293
+ /**
294
+ * MapMatching
295
+ */
296
+
297
+ enableMapMatching(network, maxDistance, maxAngleBearing) {
298
+ super.enableMapMatching(network, maxDistance || MM_PDR_DIST, maxAngleBearing || MM_PDR_ANGLE);
299
+ }
300
+
301
+
302
+ /**
303
+ * Itinerary and PDR Locker
304
+ */
305
+
306
+ setItinerary(itinerary) {
307
+ super.setItinerary(itinerary);
308
+
309
+ if (this.options.stepdetectionlocker) {
310
+ this.stepDetectionLocker.setWaitingOrientation(
311
+ PdrProvider.retrieveWaitingOrientationFromItinerary(itinerary)
312
+ );
313
+ }
314
+ }
315
+
316
+ static retrieveWaitingOrientationFromItinerary(itinerary) {
317
+
318
+ if (!itinerary) {
319
+ throw new Error('Empty itinerary');
320
+ }
321
+
322
+ if (!(itinerary instanceof Itinerary)) {
323
+ throw new TypeError(itinerary + ' is not an instance of itinerary');
324
+ }
325
+
326
+ if (itinerary.length < 1) {
327
+ throw new Error('Empty itinerary');
328
+ }
329
+
330
+ if (itinerary.firstEdge.getLength() > LO_SEGMENT_SIZE) {
331
+ return itinerary.firstEdge.getBearing();
332
+ }
333
+
334
+ if (itinerary.length < 2) {
335
+ throw new Error('Itinerary is too short');
336
+ }
337
+ return itinerary.secondEdge.getBearing();
338
+ }
339
+
340
+ setStepDetectionLockerOrientation(orientation) {
341
+ this.stepDetectionLocker.setWaitingOrientation(orientation);
342
+ }
343
+
344
+ // Linear acceleration in ENU
345
+ static computeLinearAcceleration(quaternion, acc) {
346
+ const linearAcc = Quaternion.rotate(Quaternion.inverse(quaternion), acc);
347
+ linearAcc[2] -= GeoConstants.EARTH_GRAVITY;
348
+ return linearAcc;
349
+ }
350
+ }
351
+
352
+ export default PdrProvider;
@@ -0,0 +1,41 @@
1
+ import isnumber from 'lodash.isnumber';
2
+
3
+ import { diffAngle } 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(diffAngle(heading, this.waitingOrientation)) < LO_ANGLE) {
33
+ this.locked = false;
34
+ }
35
+
36
+ return this.locked;
37
+ }
38
+
39
+ }
40
+
41
+ export default HeadingUnlocker;