@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.
- package/.eslintrc.json +479 -0
- package/.nvmrc +1 -0
- package/babel.config.js +11 -0
- package/config.json +7 -0
- package/debug/index.html +15 -0
- package/debug/index.old.html +37 -0
- package/package.json +82 -0
- package/scripts/release-github.js +216 -0
- package/src/Constants.js +11 -0
- package/src/NavigationHandler.js +244 -0
- package/src/Pose.js +8 -0
- package/src/attitude/Attitude.js +65 -0
- package/src/attitude/AttitudeHandler.js +343 -0
- package/src/attitude/EkfAttitude.js +238 -0
- package/src/attitude/EkfAttitude.spec.js +116 -0
- package/src/components/AbsoluteAttitude.jsx +136 -0
- package/src/components/Imu.jsx +89 -0
- package/src/components/LocationSource.jsx +434 -0
- package/src/components/Logger.jsx +113 -0
- package/src/components/NavigationDebugApp.jsx +106 -0
- package/src/components/Others.jsx +121 -0
- package/src/components/RelativeAttitude.jsx +104 -0
- package/src/components/Utils.js +35 -0
- package/src/components/index.js +13 -0
- package/src/index.js +9 -0
- package/src/providers/FixedLocationImuLocationSource.js +66 -0
- package/src/providers/GnssLocationSource.js +118 -0
- package/src/providers/GnssPdrLocationSource.js +182 -0
- package/src/providers/IPLocationSource.js +96 -0
- package/src/providers/LocationSource.js +290 -0
- package/src/providers/PdrLocationSource.js +312 -0
- package/src/providers/ProvidersLogger.js +77 -0
- package/src/providers/pdr/HeadingUnlocker.js +41 -0
- package/src/providers/pdr/HeadingUnlocker.spec.js +26 -0
- package/src/providers/pdr/Smoother.js +90 -0
- package/src/providers/pdr/Smoother.spec.js +424 -0
- package/src/providers/pdr/ThugDetector.js +37 -0
- package/src/providers/steps/StepDetection.js +7 -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/sensors/SensorsCompatibility.js +484 -0
- package/src/sensors/SensorsCompatibility.spec.js +270 -0
- package/src/sensors/SensorsLogger.js +94 -0
- package/src/sensors/SensorsLoggerUtils.js +35 -0
- package/src.new/NavigationHandler.js +62 -0
- package/src.new/index.js +3 -0
- package/src.new/providers/FakeLocationSource.js +39 -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,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;
|