@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.
- package/babel.config.js +11 -0
- package/config.json +4 -0
- package/debug/absolute-attitude.html +16 -0
- package/debug/absolute-position.html +16 -0
- package/debug/attitude.html +16 -0
- package/debug/components/AbsoluteAttitudeComponent.jsx +142 -0
- package/debug/components/AbsolutePositionComponent.jsx +79 -0
- package/debug/components/AttitudeComponent.jsx +40 -0
- package/debug/components/Common.css +27 -0
- package/debug/components/GnssWifiComponent.jsx +53 -0
- package/debug/components/ImuComponent.jsx +53 -0
- package/debug/components/InclinationComponent.jsx +68 -0
- package/debug/components/MapComponent.jsx +366 -0
- package/debug/components/NavigationConfig.js +112 -0
- package/debug/components/PoseComponent.jsx +168 -0
- package/debug/components/RelativeAttitudeComponent.jsx +85 -0
- package/debug/components/StartStopComponent.jsx +45 -0
- package/debug/components/StepDetectionComponent.jsx +39 -0
- package/debug/components/Utils.js +216 -0
- package/debug/components/index.js +30 -0
- package/debug/components/old/PositioningComponent.jsx +29 -0
- package/debug/components/old/PositioningInclinationComponent.jsx +82 -0
- package/debug/components/old/PositioningPoseComponent.jsx +117 -0
- package/debug/gnss-wifi.html +16 -0
- package/debug/imu.html +16 -0
- package/debug/inclination.html +16 -0
- package/debug/pose.html +16 -0
- package/debug/positioning-legacy.html +16 -0
- package/debug/relative-attitude.html +16 -0
- package/debug/step-detection.html +16 -0
- package/index.js +7 -0
- package/package.json +67 -0
- package/src/Providers.js +80 -0
- package/src/ProvidersInterface.js +125 -0
- package/src/ProvidersOptions.js +29 -0
- package/src/errors/AskImuOnDesktopError.js +9 -0
- package/src/errors/ContainsIgnoredProviderError.js +9 -0
- package/src/errors/GeolocationApiMissingError.js +9 -0
- package/src/errors/GeolocationPermissionDeniedError.js +9 -0
- package/src/errors/GeolocationPositionUnavailableError.js +9 -0
- package/src/errors/IpResolveServerError.js +9 -0
- package/src/errors/MissingAccelerometerError.js +11 -0
- package/src/errors/MissingArCoreError.js +9 -0
- package/src/errors/MissingGyroscopeError.js +11 -0
- package/src/errors/MissingMagnetometerError.js +9 -0
- package/src/errors/MissingNativeInterfaceError.js +9 -0
- package/src/errors/MissingSensorError.js +14 -0
- package/src/errors/NoProviderFoundError.js +9 -0
- package/src/events/Availability.js +44 -0
- package/src/events/EventType.js +33 -0
- package/src/events/ProviderEvent.js +32 -0
- package/src/events/ProvidersLogger.js +83 -0
- package/src/providers/Constants.js +5 -0
- package/src/providers/FakeProvider.spec.js +57 -0
- package/src/providers/MetaProvider.js +42 -0
- package/src/providers/Provider.js +314 -0
- package/src/providers/Provider.spec.js +136 -0
- package/src/providers/ProviderState.js +5 -0
- package/src/providers/attitude/AttitudeProvider.js +63 -0
- package/src/providers/attitude/EkfAttitude.js +224 -0
- package/src/providers/attitude/EkfAttitude.spec.js +114 -0
- package/src/providers/attitude/absolute/AbsoluteAttitudeFromBrowserProvider.js +224 -0
- package/src/providers/attitude/absolute/AbsoluteAttitudeFromRelAttProvider.js +134 -0
- package/src/providers/attitude/absolute/AbsoluteAttitudeProvider.js +143 -0
- package/src/providers/attitude/relative/RelativeAttitudeFromBrowserProvider.js +89 -0
- package/src/providers/attitude/relative/RelativeAttitudeFromEkfProvider.js +114 -0
- package/src/providers/attitude/relative/RelativeAttitudeProvider.js +103 -0
- package/src/providers/imu/AccelerometerProvider.js +61 -0
- package/src/providers/imu/GyroscopeProvider.js +61 -0
- package/src/providers/imu/ImuProvider.js +122 -0
- package/src/providers/inclination/InclinationFromAccProvider.js +87 -0
- package/src/providers/inclination/InclinationFromAttitudeProvider.js +77 -0
- package/src/providers/inclination/InclinationProvider.js +69 -0
- package/src/providers/legacy/AbsolutePdrProvider.js +258 -0
- package/src/providers/legacy/ArCoreAbsoluteProvider.js +230 -0
- package/src/providers/legacy/GnssWifiPdrProvider.js +217 -0
- package/src/providers/legacy/MapMatchingProvider.js +65 -0
- package/src/providers/legacy/PdrProvider.old.js +300 -0
- package/src/providers/legacy/PoseProvider.js +68 -0
- package/src/providers/legacy/helpers/HeadingUnlocker.js +47 -0
- package/src/providers/legacy/helpers/HeadingUnlocker.spec.js +53 -0
- package/src/providers/legacy/helpers/Smoother.js +92 -0
- package/src/providers/legacy/helpers/Smoother.spec.js +426 -0
- package/src/providers/legacy/helpers/ThugDetector.js +37 -0
- package/src/providers/others/CameraNativeProvider.js +44 -0
- package/src/providers/position/absolute/AbsolutePositionFromRelProvider.js +109 -0
- package/src/providers/position/absolute/AbsolutePositionProvider.js +172 -0
- package/src/providers/position/absolute/GnssWifiProvider.js +122 -0
- package/src/providers/position/absolute/IpProvider.js +68 -0
- package/src/providers/position/relative/ArCoreProvider.js +197 -0
- package/src/providers/position/relative/GeoRelativePositionFromArCoreProvider.js +85 -0
- package/src/providers/position/relative/GeoRelativePositionProvider.js +66 -0
- package/src/providers/position/relative/PdrProvider.js +132 -0
- package/src/providers/steps/StepDetectionLadetto.js +67 -0
- package/src/providers/steps/StepDetectionMinMaxPeaks.js +80 -0
- package/src/providers/steps/StepDetectionMinMaxPeaks2.js +108 -0
- package/src/providers/steps/StepDetectionProvider.js +100 -0
- package/src/smoothers/PositionSmoother.js +86 -0
- package/src/smoothers/PositionSmoother.spec.js +55 -0
- package/webpack/webpack.common.js +20 -0
- package/webpack/webpack.dev.js +24 -0
- package/webpack/webpack.prod.js +15 -0
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
/* eslint-disable max-statements */
|
|
2
|
+
import {
|
|
3
|
+
Coordinates, UserPosition
|
|
4
|
+
} from '@wemap/geo';
|
|
5
|
+
|
|
6
|
+
import { Itinerary } from '@wemap/graph';
|
|
7
|
+
import {deg2rad} from '@wemap/maths';
|
|
8
|
+
import { TimeUtils } from '@wemap/utils';
|
|
9
|
+
|
|
10
|
+
import StepDetectionProvider from '../steps/StepDetectionProvider';
|
|
11
|
+
import HeadingUnlocker from './helpers/HeadingUnlocker';
|
|
12
|
+
import ThugDetector from './helpers/ThugDetector';
|
|
13
|
+
import Smoother from './helpers/Smoother';
|
|
14
|
+
import Availability from '../../events/Availability';
|
|
15
|
+
import EventType from '../../events/EventType';
|
|
16
|
+
import MapMatchingProvider from '../others/MapMatchingProvider';
|
|
17
|
+
import AbsoluteAttitudeProvider from '../attitude/absolute/AbsoluteAttitudeProvider';
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
const MM_PDR_ANGLE = deg2rad(20);
|
|
21
|
+
const MM_PDR_DIST = 15;
|
|
22
|
+
const MM_CONV_SPEED = 0.7;
|
|
23
|
+
|
|
24
|
+
const LO_SEGMENT_SIZE = 1.5;
|
|
25
|
+
|
|
26
|
+
class AbsolutePdrProvider extends MapMatchingProvider {
|
|
27
|
+
|
|
28
|
+
// pdrPosition can be different from this.position
|
|
29
|
+
// pdrPosition is raw position calculated by PdrProvider where
|
|
30
|
+
// this.position is smoothed position.
|
|
31
|
+
pdrPosition = null;
|
|
32
|
+
|
|
33
|
+
static MM_PDR_ANGLE = MM_PDR_ANGLE;
|
|
34
|
+
static MM_PDR_DIST = MM_PDR_DIST;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* @override
|
|
38
|
+
*/
|
|
39
|
+
constructor() {
|
|
40
|
+
super();
|
|
41
|
+
|
|
42
|
+
// Helpers for PDR
|
|
43
|
+
this.stepDetectionLocker = new HeadingUnlocker();
|
|
44
|
+
this.smoother = new Smoother();
|
|
45
|
+
this.thugDetector = new ThugDetector();
|
|
46
|
+
|
|
47
|
+
if (this.options.useMapMatching) {
|
|
48
|
+
this.enableMapMatching();
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* @override
|
|
54
|
+
*/
|
|
55
|
+
static get displayName() {
|
|
56
|
+
return 'PDR';
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* @override
|
|
61
|
+
*/
|
|
62
|
+
static get eventsType() {
|
|
63
|
+
return [EventType.AbsoluteAttitude, EventType.AbsolutePosition];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* @override
|
|
68
|
+
*/
|
|
69
|
+
static get _availability() {
|
|
70
|
+
return Availability.merge(
|
|
71
|
+
StepDetectionProvider.availability,
|
|
72
|
+
AbsoluteAttitudeProvider.availability
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* @override
|
|
79
|
+
*/
|
|
80
|
+
_start() {
|
|
81
|
+
|
|
82
|
+
this.stepDetectionProviderId = this.scheduler.use(StepDetectionProvider,
|
|
83
|
+
events => this.onStepEvent(events[0]),
|
|
84
|
+
error => this.notifyError(error),
|
|
85
|
+
this.name);
|
|
86
|
+
|
|
87
|
+
this.absoluteAttitudeProviderId = this.scheduler.use(AbsoluteAttitudeProvider,
|
|
88
|
+
events => this.onAbsoluteAttitudeEvent(events),
|
|
89
|
+
error => this.notifyError(error),
|
|
90
|
+
this.name);
|
|
91
|
+
|
|
92
|
+
this.dataStore.addEventListener(EventType.AbsolutePosition, this.setPosition);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* @override
|
|
97
|
+
*/
|
|
98
|
+
_stop() {
|
|
99
|
+
this.dataStore.removeEventListener(EventType.AbsolutePosition, this.setPosition);
|
|
100
|
+
this.scheduler.release(this.stepDetectionProviderId, this.name);
|
|
101
|
+
this.scheduler.release(this.absoluteAttitudeProviderId, this.name);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
get nameWithOptions() {
|
|
105
|
+
let name = this.name;
|
|
106
|
+
name += ' (locker: ';
|
|
107
|
+
name += this.options.stepdetectionlocker ? 'Y' : 'N';
|
|
108
|
+
name += ', ';
|
|
109
|
+
name += 'smoo: ';
|
|
110
|
+
name += this.options.smoother ? 'Y' : 'N';
|
|
111
|
+
name += ', ';
|
|
112
|
+
name += 'mm: ';
|
|
113
|
+
name += this.options.useMapMatching ? 'Y' : 'N';
|
|
114
|
+
name += ')';
|
|
115
|
+
return name;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
setPosition = (position) => {
|
|
119
|
+
if (position instanceof Coordinates) {
|
|
120
|
+
this.pdrPosition = UserPosition.fromCoordinates(position);
|
|
121
|
+
if (window && window.performance) {
|
|
122
|
+
this.pdrPosition.time = TimeUtils.preciseTime / 1e3;
|
|
123
|
+
}
|
|
124
|
+
} else {
|
|
125
|
+
this.pdrPosition = position;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// if (this.options.smoother) {
|
|
129
|
+
// this.smoother.generateNextPositions(this.pdrPosition, true);
|
|
130
|
+
// // this.smoother.pullPosition(position.time) should never return null
|
|
131
|
+
// this.position = this.smoother.pullPosition(this.pdrPosition.time);
|
|
132
|
+
// } else {
|
|
133
|
+
this.position = this.pdrPosition.clone();
|
|
134
|
+
// }
|
|
135
|
+
|
|
136
|
+
this.notify(this.createEvent(EventType.AbsolutePosition, this.position));
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* @override
|
|
141
|
+
*/
|
|
142
|
+
onAbsoluteAttitudeEvent = events => {
|
|
143
|
+
const attitudeEvent = events[0];
|
|
144
|
+
this.attitude = attitudeEvent.data;
|
|
145
|
+
|
|
146
|
+
const newEvent = attitudeEvent.clone();
|
|
147
|
+
newEvent.dataType = EventType.AbsoluteAttitude;
|
|
148
|
+
this.notify(newEvent);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* @private
|
|
153
|
+
*/
|
|
154
|
+
onStepEvent = stepEvent => {
|
|
155
|
+
|
|
156
|
+
const timestamp = stepEvent.timestamp;
|
|
157
|
+
|
|
158
|
+
if (!this.pdrPosition) {
|
|
159
|
+
// We should wait at least an input position for PDR
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (this.options.stepdetectionlocker
|
|
164
|
+
&& this.stepDetectionLocker.locked
|
|
165
|
+
&& this.stepDetectionLocker.feedHeading(this.attitude.heading)) {
|
|
166
|
+
// Step detection is locked by stepDetectionLocker
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const heading = this.attitude.heading;
|
|
171
|
+
|
|
172
|
+
this.pdrPosition = this.calculateNewPosition(this.pdrPosition,
|
|
173
|
+
stepEvent.timestamp, heading, stepEvent.data.size);
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Update current_position
|
|
177
|
+
*/
|
|
178
|
+
if (this.options.smoother) {
|
|
179
|
+
this.smoother.generateNextPositions(this.pdrPosition);
|
|
180
|
+
const newPosition = this.smoother.pullPosition(timestamp);
|
|
181
|
+
// At this time, newPosition can be null if a new step has been detected
|
|
182
|
+
// and smoother has not been completely consumed.
|
|
183
|
+
if (newPosition) {
|
|
184
|
+
this.position = newPosition;
|
|
185
|
+
}
|
|
186
|
+
} else {
|
|
187
|
+
this.position = this.pdrPosition;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// If no step is detected, we pull last known position from smoother until now (timestamp).
|
|
191
|
+
const smoothedPosition = this.smoother.pullPosition(timestamp);
|
|
192
|
+
if (!smoothedPosition) {
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
this.position = smoothedPosition;
|
|
196
|
+
|
|
197
|
+
this.notify(this.createEvent(EventType.AbsolutePosition, this.position));
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
calculateNewPosition(previousPosition, timestamp, heading, stepSize) {
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Compute new_position
|
|
204
|
+
*/
|
|
205
|
+
const newPositionWithoutMM = previousPosition.clone();
|
|
206
|
+
newPositionWithoutMM.move(stepSize, heading);
|
|
207
|
+
newPositionWithoutMM.bearing = heading;
|
|
208
|
+
newPositionWithoutMM.time = timestamp;
|
|
209
|
+
|
|
210
|
+
if (!this.mapMatching) {
|
|
211
|
+
return newPositionWithoutMM;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Try map-matching
|
|
216
|
+
*/
|
|
217
|
+
const projection = this.mapMatching.getProjection(newPositionWithoutMM, true, true);
|
|
218
|
+
if (!projection || !projection.projection) {
|
|
219
|
+
return newPositionWithoutMM;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const projectedPosition = newPositionWithoutMM;
|
|
223
|
+
projectedPosition.level = projection.projection.level;
|
|
224
|
+
|
|
225
|
+
if (projection.distanceFromNearestElement < stepSize) {
|
|
226
|
+
projectedPosition.lat = projection.projection.lat;
|
|
227
|
+
projectedPosition.lng = projection.projection.lng;
|
|
228
|
+
return projectedPosition;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Adapt map-matching to not stick corridor directly
|
|
233
|
+
* /!\ This smoothed position is different from the one of Smoother
|
|
234
|
+
*/
|
|
235
|
+
const smoothedPosition = projectedPosition;
|
|
236
|
+
smoothedPosition.lat = previousPosition.lat;
|
|
237
|
+
smoothedPosition.lng = previousPosition.lng;
|
|
238
|
+
|
|
239
|
+
const smoothedDistance = projection.distanceFromNearestElement * MM_CONV_SPEED;
|
|
240
|
+
const smoothedBearing = previousPosition.bearingTo(projection.projection);
|
|
241
|
+
smoothedPosition.move(smoothedDistance, smoothedBearing);
|
|
242
|
+
|
|
243
|
+
return smoothedPosition;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* MapMatching
|
|
249
|
+
*/
|
|
250
|
+
|
|
251
|
+
enableMapMatching(network, maxDistance, maxAngleBearing) {
|
|
252
|
+
super.enableMapMatching(network, maxDistance || MM_PDR_DIST, maxAngleBearing || MM_PDR_ANGLE);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Itinerary and PDR Locker
|
|
258
|
+
*/
|
|
259
|
+
|
|
260
|
+
setItinerary(itinerary) {
|
|
261
|
+
super.setItinerary(itinerary);
|
|
262
|
+
|
|
263
|
+
if (this.options.stepdetectionlocker) {
|
|
264
|
+
this.stepDetectionLocker.setWaitingOrientation(
|
|
265
|
+
this.constructor.retrieveWaitingOrientationFromItinerary(itinerary)
|
|
266
|
+
);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
static retrieveWaitingOrientationFromItinerary(itinerary) {
|
|
271
|
+
|
|
272
|
+
if (!itinerary) {
|
|
273
|
+
throw new Error('Empty itinerary');
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
if (!(itinerary instanceof Itinerary)) {
|
|
277
|
+
throw new TypeError(itinerary + ' is not an instance of itinerary');
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if (itinerary.length < 1) {
|
|
281
|
+
throw new Error('Empty itinerary');
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const edgeAt = itinerary.getEdgeAt(LO_SEGMENT_SIZE);
|
|
285
|
+
if (edgeAt) {
|
|
286
|
+
return edgeAt.bearing;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
if (itinerary.length < 2) {
|
|
290
|
+
throw new Error('Itinerary is too short');
|
|
291
|
+
}
|
|
292
|
+
return itinerary.secondEdge.bearing;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
setStepDetectionLockerOrientation(orientation) {
|
|
296
|
+
this.stepDetectionLocker.setWaitingOrientation(orientation);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
export default AbsolutePdrProvider;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import Provider from '../Provider';
|
|
2
|
+
import Availability from '../../events/Availability';
|
|
3
|
+
import EventType from '../../events/EventType';
|
|
4
|
+
|
|
5
|
+
import GnssWifiProvider from '../position/GnssWifiProvider';
|
|
6
|
+
import AbsoluteAttitudeProvider from '../attitude/absolute/AbsoluteAttitudeProvider';
|
|
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, onError);
|
|
21
|
+
this.gnssWifiProvider = new GnssWifiProvider((events) => {
|
|
22
|
+
this.absoluteAttitudeProvider.setPosition(events[0].data);
|
|
23
|
+
onEvent(events);
|
|
24
|
+
}, onError);
|
|
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 _availability() {
|
|
45
|
+
return Availability.merge(
|
|
46
|
+
AbsoluteAttitudeProvider.availability,
|
|
47
|
+
GnssWifiProvider.availability
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* @override
|
|
53
|
+
*/
|
|
54
|
+
_start() {
|
|
55
|
+
this.absoluteAttitudeProvider.start();
|
|
56
|
+
this.gnssWifiProvider.start();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* @override
|
|
61
|
+
*/
|
|
62
|
+
_stop() {
|
|
63
|
+
this.absoluteAttitudeProvider.stop();
|
|
64
|
+
this.gnssWifiProvider.stop();
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export default PoseProvider;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import isnumber from 'lodash.isnumber';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
diffAngle, deg2rad
|
|
5
|
+
} from '@wemap/maths';
|
|
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
|
+
static LO_ANGLE = deg2rad(15);
|
|
13
|
+
|
|
14
|
+
constructor() {
|
|
15
|
+
// HeadingUnlocker status, by default it is unlocked
|
|
16
|
+
this.locked = false;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
unlock() {
|
|
20
|
+
this.locked = false;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
lock(waitingOrientation) {
|
|
24
|
+
this.waitingOrientation = waitingOrientation;
|
|
25
|
+
this.locked = true;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
feedHeading(heading) {
|
|
29
|
+
|
|
30
|
+
if (!isnumber(this.waitingOrientation)) {
|
|
31
|
+
throw new Error('HeadingUnlocker has not been initialized by lock');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (!this.locked) {
|
|
35
|
+
throw new Error('HeadingUnlocker is already unlocked');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (Math.abs(diffAngle(heading, this.waitingOrientation)) < this.constructor.LO_ANGLE) {
|
|
39
|
+
this.locked = false;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return this.locked;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export default HeadingUnlocker;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import chai from 'chai';
|
|
2
|
+
import chaiAlmost from 'chai-almost';
|
|
3
|
+
|
|
4
|
+
import { deg2rad } from '@wemap/maths';
|
|
5
|
+
|
|
6
|
+
import HeadingUnlocker from './HeadingUnlocker';
|
|
7
|
+
|
|
8
|
+
const expect = chai.expect;
|
|
9
|
+
chai.use(chaiAlmost());
|
|
10
|
+
|
|
11
|
+
describe('HeadingUnlocker', () => {
|
|
12
|
+
|
|
13
|
+
it('lock & feed', () => {
|
|
14
|
+
|
|
15
|
+
const headingUnlocker = new HeadingUnlocker();
|
|
16
|
+
headingUnlocker.lock(deg2rad(102));
|
|
17
|
+
|
|
18
|
+
headingUnlocker.feedHeading(deg2rad(0));
|
|
19
|
+
expect(headingUnlocker.locked).to.be.true;
|
|
20
|
+
|
|
21
|
+
headingUnlocker.feedHeading(deg2rad(130));
|
|
22
|
+
expect(headingUnlocker.locked).to.be.true;
|
|
23
|
+
|
|
24
|
+
headingUnlocker.feedHeading(deg2rad(100));
|
|
25
|
+
expect(headingUnlocker.locked).to.be.false;
|
|
26
|
+
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('lock & unlock', () => {
|
|
30
|
+
|
|
31
|
+
const headingUnlocker = new HeadingUnlocker();
|
|
32
|
+
headingUnlocker.lock(deg2rad(102));
|
|
33
|
+
|
|
34
|
+
expect(headingUnlocker.locked).to.be.true;
|
|
35
|
+
|
|
36
|
+
headingUnlocker.unlock();
|
|
37
|
+
expect(headingUnlocker.locked).to.be.false;
|
|
38
|
+
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('fails', () => {
|
|
42
|
+
|
|
43
|
+
const headingUnlocker = new HeadingUnlocker();
|
|
44
|
+
|
|
45
|
+
expect(() => headingUnlocker.feedHeading(0)).throw(Error);
|
|
46
|
+
|
|
47
|
+
headingUnlocker.lock(deg2rad(10));
|
|
48
|
+
headingUnlocker.feedHeading(deg2rad(20));
|
|
49
|
+
expect(headingUnlocker.locked).to.be.false;
|
|
50
|
+
expect(() => headingUnlocker.feedHeading(0)).throw(Error);
|
|
51
|
+
|
|
52
|
+
});
|
|
53
|
+
});
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/* eslint-disable max-statements */
|
|
2
|
+
import { UserPosition } from '@wemap/geo';
|
|
3
|
+
|
|
4
|
+
// Generated positions 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.positionsQueue = [];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Calculate smoothed positions for the next milliseconds given a new position
|
|
19
|
+
*/
|
|
20
|
+
generateNextPositions(newPosition, flyby = false) {
|
|
21
|
+
|
|
22
|
+
if (!(newPosition instanceof UserPosition)) {
|
|
23
|
+
throw new TypeError('newPosition is not instance of UserPosition');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (newPosition.time === null) {
|
|
27
|
+
throw new Error('newPosition does not have time property');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (!this.previousPosition) {
|
|
31
|
+
this.previousPosition = newPosition;
|
|
32
|
+
this.positionsQueue.push(this.previousPosition);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const distance = this.previousPosition.distanceTo(newPosition);
|
|
37
|
+
const azimuth = this.previousPosition.bearingTo(newPosition);
|
|
38
|
+
|
|
39
|
+
let refTimestamp = newPosition.time;
|
|
40
|
+
|
|
41
|
+
const queueLength = this.positionsQueue.length;
|
|
42
|
+
if (queueLength) {
|
|
43
|
+
refTimestamp = Math.max(refTimestamp, this.positionsQueue[queueLength - 1].time);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
let diffTime = newPosition.time - this.previousPosition.time;
|
|
47
|
+
|
|
48
|
+
if (flyby) {
|
|
49
|
+
diffTime = MAX_FLYBY_TIME;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const nSamples = GEN_FREQUENCY * Math.min(Math.max(MIN_FLYBY_TIME, diffTime), MAX_FLYBY_TIME);
|
|
53
|
+
|
|
54
|
+
let i = 1;
|
|
55
|
+
while (i < nSamples + 1) {
|
|
56
|
+
i = Math.min(i, nSamples);
|
|
57
|
+
const smoothedPosition = this.previousPosition.destinationPoint(distance * i / nSamples, azimuth);
|
|
58
|
+
smoothedPosition.time = refTimestamp + (i - 1) / GEN_FREQUENCY;
|
|
59
|
+
smoothedPosition.bearing = newPosition.bearing;
|
|
60
|
+
this.positionsQueue.push(smoothedPosition);
|
|
61
|
+
i++;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
this.positionsQueue[this.positionsQueue.length - 1].level = newPosition.level;
|
|
65
|
+
|
|
66
|
+
this.previousPosition = newPosition;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Pull positions until timestamp and return the last one.
|
|
71
|
+
* @param {*} timestamp position returned is the last before this timestamp
|
|
72
|
+
* @returns last known position before timestamp, undefined otherwise
|
|
73
|
+
*/
|
|
74
|
+
pullPosition(timestamp) {
|
|
75
|
+
|
|
76
|
+
if ((typeof timestamp === 'undefined' || !timestamp)
|
|
77
|
+
&& this.positionsQueue.length > 0) {
|
|
78
|
+
const output = this.positionsQueue[this.positionsQueue.length - 1];
|
|
79
|
+
this.positionsQueue = [];
|
|
80
|
+
return output;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
let position;
|
|
84
|
+
while (this.positionsQueue.length && this.positionsQueue[0].time <= timestamp) {
|
|
85
|
+
position = this.positionsQueue.shift();
|
|
86
|
+
}
|
|
87
|
+
return position;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export default Smoother;
|