@wemap/providers 5.1.12 → 5.3.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/dist/wemap-providers.es.js +4526 -0
- package/dist/wemap-providers.es.js.map +1 -0
- package/package.json +4 -4
|
@@ -0,0 +1,4526 @@
|
|
|
1
|
+
import { deg2rad, Vector3, Matrix, Quaternion, Matrix3, Matrix4, Vector, Rotations, std, diffAngleLines, diffAngle, rad2deg } from '@wemap/maths';
|
|
2
|
+
import { BrowserUtils, Browser, PromiseUtils, TimeUtils } from '@wemap/utils';
|
|
3
|
+
import Logger from '@wemap/logger';
|
|
4
|
+
import { Attitude, RelativePosition, AbsoluteHeading, Constants as Constants$1, MapMatching, UserPosition, GraphEdge, GeoRelativePosition as GeoRelativePosition$1, Network } from '@wemap/geo';
|
|
5
|
+
import geomagnetism from '@wemap/geomagnetism';
|
|
6
|
+
import { Itinerary } from '@wemap/osm';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Event data types handled by {@link ProviderEvent}
|
|
10
|
+
*/
|
|
11
|
+
var EventType = {
|
|
12
|
+
Unknown: 'UNKNOWN',
|
|
13
|
+
|
|
14
|
+
MagneticField: 'MAGNETIC_FIELD',
|
|
15
|
+
AngularRate: 'ANGULAR_RATE',
|
|
16
|
+
Acceleration: 'ACCELERATION',
|
|
17
|
+
|
|
18
|
+
RelativeAttitude: 'RELATIVE_ATTITUDE',
|
|
19
|
+
AbsoluteAttitude: 'ABSOLUTE_ATTITUDE',
|
|
20
|
+
Attitude: 'ATTITUDE',
|
|
21
|
+
|
|
22
|
+
Inclination: 'INCLINATION',
|
|
23
|
+
AbsoluteHeading: 'ABSOLUTE_HEADING',
|
|
24
|
+
Turn: 'TURN',
|
|
25
|
+
HighRotation: 'HIGH_ROTATION',
|
|
26
|
+
|
|
27
|
+
RelativePosition: 'RELATIVE_POSITION',
|
|
28
|
+
GeoRelativePosition: 'GEO_RELATIVE_POSITION',
|
|
29
|
+
AbsolutePosition: 'ABSOLUTE_POSITION',
|
|
30
|
+
|
|
31
|
+
Step: 'STEP',
|
|
32
|
+
StraightLine: 'STRAIGHT_LINE',
|
|
33
|
+
|
|
34
|
+
Pressure: 'PRESSURE',
|
|
35
|
+
BluetoothSignals: 'BLUETOOTH_SIGNALS',
|
|
36
|
+
WifiSignals: 'WIFI_SIGNALS',
|
|
37
|
+
ScanId: 'SCAN_ID',
|
|
38
|
+
Barcode: 'BARCODE',
|
|
39
|
+
CameraProjectionMatrix: 'CAMERA_PROJECTION_MATRIX',
|
|
40
|
+
CameraNative: 'CAMERA_NATIVE',
|
|
41
|
+
|
|
42
|
+
Itinerary: 'ITINERARY',
|
|
43
|
+
Network: 'NETWORK'
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* A provider event is an event which can be triggered by device sensors
|
|
48
|
+
* or can be computed from raw providers.
|
|
49
|
+
*
|
|
50
|
+
* @template T
|
|
51
|
+
*/
|
|
52
|
+
class ProviderEvent {
|
|
53
|
+
|
|
54
|
+
dataType = EventType.Unknown;
|
|
55
|
+
providersStack = [];
|
|
56
|
+
|
|
57
|
+
/** @type {T} */
|
|
58
|
+
data = null;
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Create a Provider Event with the minimum information
|
|
62
|
+
* @param {EventType} dataType the type of event
|
|
63
|
+
* @param {Object} data the event data
|
|
64
|
+
*/
|
|
65
|
+
constructor(dataType, data) {
|
|
66
|
+
this.dataType = dataType;
|
|
67
|
+
this.data = data;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* @return {ProviderEvent<T>}
|
|
72
|
+
*/
|
|
73
|
+
clone() {
|
|
74
|
+
const evt = new ProviderEvent(this.dataType, this.data);
|
|
75
|
+
evt.providersStack = this.providersStack.slice(0);
|
|
76
|
+
return evt;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
class ProvidersLoggerOld {
|
|
81
|
+
|
|
82
|
+
_enabled = false;
|
|
83
|
+
|
|
84
|
+
currentId = 0;
|
|
85
|
+
objectsIdMap = new WeakMap();
|
|
86
|
+
|
|
87
|
+
pushEvents = {};
|
|
88
|
+
pushEventsRef = {};
|
|
89
|
+
|
|
90
|
+
interval;
|
|
91
|
+
initDate;
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
get enabled() {
|
|
95
|
+
return this._enabled;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
set enabled(_newVal) {
|
|
99
|
+
this._enabled = _newVal;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
initializeInterval() {
|
|
103
|
+
|
|
104
|
+
if (this.interval) {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
this.interval = setInterval(() => {
|
|
109
|
+
|
|
110
|
+
for (const [key, value] of Object.entries(this.pushEvents)) {
|
|
111
|
+
Logger.debug('Received ' + value + ' notifications from ' + this.pushEventsRef[key].pname + ' last second');
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
this.pushEvents = {};
|
|
115
|
+
this.pushEventsRef = {};
|
|
116
|
+
}, 1000);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
getObjectId(object) {
|
|
120
|
+
if (!this.objectsIdMap.has(object)) {
|
|
121
|
+
this.objectsIdMap.set(object, ++this.currentId);
|
|
122
|
+
}
|
|
123
|
+
return this.objectsIdMap.get(object);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
addEvent(object, method) {
|
|
127
|
+
|
|
128
|
+
if (!this.enabled) {
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (!this.initDate) {
|
|
133
|
+
this.initDate = Date.now();
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
this.initializeInterval();
|
|
137
|
+
|
|
138
|
+
const objectId = this.getObjectId(object);
|
|
139
|
+
const objectClassName = object.pname;
|
|
140
|
+
|
|
141
|
+
Logger.debug(objectClassName + '[' + objectId + '].' + method);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
incrementNotifications(object) {
|
|
145
|
+
|
|
146
|
+
if (!this.enabled) {
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const objectId = this.getObjectId(object);
|
|
151
|
+
|
|
152
|
+
let counter = this.pushEvents[objectId];
|
|
153
|
+
if (!counter) {
|
|
154
|
+
counter = 0;
|
|
155
|
+
this.pushEventsRef[objectId] = object;
|
|
156
|
+
}
|
|
157
|
+
this.pushEvents[objectId] = counter + 1;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
}
|
|
161
|
+
var ProvidersLoggerOld$1 = new ProvidersLoggerOld();
|
|
162
|
+
|
|
163
|
+
class ContainsIgnoredProviderError extends Error {
|
|
164
|
+
|
|
165
|
+
static DEFAULT_MESSAGE = 'Contains ignored provider';
|
|
166
|
+
|
|
167
|
+
constructor(message) {
|
|
168
|
+
super(message || ContainsIgnoredProviderError.DEFAULT_MESSAGE);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const ProvidersOptions = {
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Does provider will use map to
|
|
176
|
+
*/
|
|
177
|
+
useMapMatching: true,
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Providers listed here will not be used by the system
|
|
181
|
+
* List of {@link Provider#name}
|
|
182
|
+
*/
|
|
183
|
+
ignoreProviders: [],
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Define the list of EventType that are optionals
|
|
187
|
+
* List of {@link EventType}
|
|
188
|
+
*/
|
|
189
|
+
optionalEvents: [],
|
|
190
|
+
|
|
191
|
+
stopOnError: true,
|
|
192
|
+
|
|
193
|
+
checkAvailabilityOnStart: true,
|
|
194
|
+
|
|
195
|
+
stepdetectionlocker: true,
|
|
196
|
+
|
|
197
|
+
smoother: true
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
var ProviderState = {
|
|
201
|
+
STARTING: 0,
|
|
202
|
+
STARTED: 1,
|
|
203
|
+
STOPPPED: 2
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* A provider is a meta class to define an entity which can be
|
|
208
|
+
* started / stopped and provides a data of {@link ProviderEvent#DataType}
|
|
209
|
+
*/
|
|
210
|
+
class Provider {
|
|
211
|
+
|
|
212
|
+
/** @type {number} */
|
|
213
|
+
static _callbackUniqueId = 0;
|
|
214
|
+
|
|
215
|
+
/** @type {number} */
|
|
216
|
+
static _uniqueId = 1;
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
/** @type {number} */
|
|
220
|
+
id;
|
|
221
|
+
|
|
222
|
+
/** @type {number} */
|
|
223
|
+
state = ProviderState.STOPPPED;
|
|
224
|
+
|
|
225
|
+
/** @type {ProviderEvent[]} */
|
|
226
|
+
_lastEvents = null;
|
|
227
|
+
|
|
228
|
+
_eventsCallbacks = [];
|
|
229
|
+
_monitoringCallbacks = [];
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Provider constructor
|
|
233
|
+
*/
|
|
234
|
+
constructor() {
|
|
235
|
+
|
|
236
|
+
if (this.constructor === Provider) {
|
|
237
|
+
throw new Error('Can\'t instantiate Provider directly');
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
this.id = Provider._uniqueId++;
|
|
241
|
+
|
|
242
|
+
ProvidersLoggerOld$1.addEvent(this, 'constructor');
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Get the provider name
|
|
247
|
+
* @type {String} the provider name
|
|
248
|
+
*/
|
|
249
|
+
static get pname() {
|
|
250
|
+
return 'Unknown';
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/** @type {String} */
|
|
254
|
+
get pname() {
|
|
255
|
+
return this.constructor.pname;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Get the list of events type which can be returned by the provider
|
|
260
|
+
* @returns {EventType[]} the list of events type
|
|
261
|
+
* @public
|
|
262
|
+
* @abstract
|
|
263
|
+
*/
|
|
264
|
+
get eventsType() {
|
|
265
|
+
return [];
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* @type {Promise} returns an availability promise
|
|
270
|
+
*/
|
|
271
|
+
get availability() {
|
|
272
|
+
if (ProvidersOptions.ignoreProviders.includes(this.pname)) {
|
|
273
|
+
return Promise.reject(new ContainsIgnoredProviderError());
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
return this._availability;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/** @type {Promise} */
|
|
280
|
+
get _availability() {
|
|
281
|
+
return Promise.resolve();
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Create an event from current provider
|
|
286
|
+
* @param {String} dataType type of ProviderEvent (from EventType)
|
|
287
|
+
* @param {Object} data data exported to ProviderEvent
|
|
288
|
+
* @returns {ProviderEvent}
|
|
289
|
+
* @protected
|
|
290
|
+
*/
|
|
291
|
+
static createEvent(dataType, data, fromEvents = []) {
|
|
292
|
+
const event = new ProviderEvent(dataType, data);
|
|
293
|
+
const newStack = fromEvents.reduce((acc, _event) => acc.concat(_event.providersStack), []);
|
|
294
|
+
// Remove duplicates and keep lasts
|
|
295
|
+
event.providersStack = [...new Set(newStack.reverse())].reverse();
|
|
296
|
+
return event;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Create an event from current provider
|
|
301
|
+
* @param {EventType} dataType type of ProviderEvent
|
|
302
|
+
* @param {Object} data data exported to ProviderEvent
|
|
303
|
+
* @param {ProviderEvent[]} fromEvents events used for the creation of the new one
|
|
304
|
+
* @returns {ProviderEvent}
|
|
305
|
+
* @protected
|
|
306
|
+
*/
|
|
307
|
+
createEvent(dataType, data, fromEvents) {
|
|
308
|
+
return this.constructor.createEvent(dataType, data, fromEvents);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
get hasNativeInterface() {
|
|
312
|
+
return Boolean(this.nativeInterface);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
get nativeInterface() {
|
|
316
|
+
return typeof window !== 'undefined' ? (window.__nativeProviders || null) : null;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
get useCameraNatively() {
|
|
320
|
+
return false;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
*
|
|
325
|
+
* @param {Function} onEvents
|
|
326
|
+
* @param {Function} onError
|
|
327
|
+
* @param {Boolean} startIfNecessary
|
|
328
|
+
* @returns {Number}
|
|
329
|
+
*/
|
|
330
|
+
addEventListener(onEvents = () => {}, onError = () => {}, startIfNecessary = true) {
|
|
331
|
+
const id = ++Provider._callbackUniqueId;
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Build callback
|
|
335
|
+
*/
|
|
336
|
+
this._eventsCallbacks.push({
|
|
337
|
+
id,
|
|
338
|
+
onEvents,
|
|
339
|
+
onError,
|
|
340
|
+
optional: !startIfNecessary
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
// The caller just want to have callbacks without starting the provider,
|
|
345
|
+
// the routine can be stopped here
|
|
346
|
+
if (!startIfNecessary) {
|
|
347
|
+
return id;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
// If the provider is already started do not go further
|
|
352
|
+
if (this.state !== ProviderState.STOPPPED) {
|
|
353
|
+
return id;
|
|
354
|
+
}
|
|
355
|
+
this.state = ProviderState.STARTING;
|
|
356
|
+
|
|
357
|
+
// Check availability on start if defined in options
|
|
358
|
+
let availabilityPromise = Promise.resolve();
|
|
359
|
+
if (ProvidersOptions.checkAvailabilityOnStart) {
|
|
360
|
+
availabilityPromise = this.availability;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
availabilityPromise
|
|
364
|
+
.then(() => {
|
|
365
|
+
|
|
366
|
+
ProvidersLoggerOld$1.addEvent(this, 'start');
|
|
367
|
+
// Call the child start function
|
|
368
|
+
this.start();
|
|
369
|
+
|
|
370
|
+
this.state = ProviderState.STARTED;
|
|
371
|
+
|
|
372
|
+
this._monitoringCallbacks.forEach(({ onStarted }) => onStarted());
|
|
373
|
+
})
|
|
374
|
+
.catch(e => {
|
|
375
|
+
this.state = ProviderState.STOPPPED;
|
|
376
|
+
this.notifyError(e);
|
|
377
|
+
})
|
|
378
|
+
// notifyError can throw an error if onStop is not defined
|
|
379
|
+
.catch(() => {});
|
|
380
|
+
|
|
381
|
+
return id;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
*
|
|
386
|
+
* @param {Number} callbackUniqueId
|
|
387
|
+
*/
|
|
388
|
+
removeEventListener(callbackUniqueId) {
|
|
389
|
+
|
|
390
|
+
// Search the caller object in callbacks list
|
|
391
|
+
const callback = this._eventsCallbacks.find(_callback => _callback.id === callbackUniqueId);
|
|
392
|
+
if (!callback) {
|
|
393
|
+
// The callback object is not found. Maybe it is already stopped.
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// Remove the current callback from the list of the callbacks
|
|
398
|
+
this._eventsCallbacks = this._eventsCallbacks.filter(_callback => _callback !== callback);
|
|
399
|
+
|
|
400
|
+
// If this callback was optional, do not go further to stop the provider
|
|
401
|
+
if (callback.optional) {
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// If there is callbacks that are not optionals for start, do not stop the provider
|
|
406
|
+
if (this._eventsCallbacks.find(_callback => !_callback.optional)) {
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// If the provider is already stopped, do not stop it again.
|
|
411
|
+
// This condition can be true if checkAvailabilityOnStart is true and returns an error
|
|
412
|
+
if (this.state === ProviderState.STOPPPED) {
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
ProvidersLoggerOld$1.addEvent(this, 'stop');
|
|
417
|
+
// Call the child stop function
|
|
418
|
+
this.stop();
|
|
419
|
+
|
|
420
|
+
this.state = ProviderState.STOPPPED;
|
|
421
|
+
|
|
422
|
+
this._monitoringCallbacks.forEach(({ onStopped }) => onStopped());
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
*
|
|
428
|
+
* @param {Function} onStarted
|
|
429
|
+
* @param {Function} onStopped
|
|
430
|
+
* @returns {Number}
|
|
431
|
+
*/
|
|
432
|
+
addMonitoringListener(onStarted = () => {}, onStopped = () => {}) {
|
|
433
|
+
const id = ++this.constructor._callbackUniqueId;
|
|
434
|
+
this._monitoringCallbacks.push({
|
|
435
|
+
id,
|
|
436
|
+
onStarted,
|
|
437
|
+
onStopped
|
|
438
|
+
});
|
|
439
|
+
return id;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
/**
|
|
443
|
+
*
|
|
444
|
+
* @param {Number} callbackUniqueId
|
|
445
|
+
*/
|
|
446
|
+
removeMonitoringListener(callbackUniqueId) {
|
|
447
|
+
this._monitoringCallbacks = this._monitoringCallbacks.filter(
|
|
448
|
+
_callback => _callback.id !== callbackUniqueId
|
|
449
|
+
);
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
/**
|
|
453
|
+
* @abstract
|
|
454
|
+
*/
|
|
455
|
+
start() {
|
|
456
|
+
throw new Error('Provider "' + this.pname + '" does not @override start()');
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
/**
|
|
460
|
+
* @abstract
|
|
461
|
+
*/
|
|
462
|
+
stop() {
|
|
463
|
+
throw new Error('Provider "' + this.pname + '" does not @override stop()');
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
|
|
467
|
+
/**
|
|
468
|
+
* Notify the subscriber defined in {@link addEventListener}
|
|
469
|
+
* @param {ProviderEvent[]} events events to send to subscriber
|
|
470
|
+
*/
|
|
471
|
+
notify(...events) {
|
|
472
|
+
// Logging
|
|
473
|
+
ProvidersLoggerOld$1.incrementNotifications(this);
|
|
474
|
+
|
|
475
|
+
// Add current provider to the list of providers for this event.
|
|
476
|
+
events.forEach(event => event.providersStack.unshift(this.pname));
|
|
477
|
+
|
|
478
|
+
// Notify callbacks
|
|
479
|
+
this._eventsCallbacks.forEach(({ onEvents }) => onEvents(events));
|
|
480
|
+
|
|
481
|
+
// Keep a trace of the last events.
|
|
482
|
+
this._lastEvents = events;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
/**
|
|
486
|
+
* Notify the subscriber defined in {@link addEventListener}
|
|
487
|
+
* @param {Error} error The error raised
|
|
488
|
+
*/
|
|
489
|
+
notifyError(error) {
|
|
490
|
+
this._eventsCallbacks.forEach(({
|
|
491
|
+
id, onError
|
|
492
|
+
}) => {
|
|
493
|
+
if (ProvidersOptions.stopOnError) {
|
|
494
|
+
this.removeEventListener(id);
|
|
495
|
+
}
|
|
496
|
+
onError(error);
|
|
497
|
+
});
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
/** @type {ProviderEvent} */
|
|
501
|
+
get lastEvent() {
|
|
502
|
+
return this._lastEvents ? this._lastEvents[0] : null;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
/** @type {ProviderEvent[]} */
|
|
506
|
+
get lastEvents() {
|
|
507
|
+
return this._lastEvents;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
/**
|
|
511
|
+
* Input data from external interface
|
|
512
|
+
* @param {ProviderEvent} event
|
|
513
|
+
* @param {EventType} eventType
|
|
514
|
+
*/
|
|
515
|
+
// eslint-disable-next-line no-unused-vars
|
|
516
|
+
feed(event, eventType) {
|
|
517
|
+
throw new Error('Not implemented');
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
class AskImuOnDesktopError extends Error {
|
|
522
|
+
|
|
523
|
+
static DEFAULT_MESSAGE = 'It seems that you ask for IMU events on a desktop browser';
|
|
524
|
+
|
|
525
|
+
constructor(message) {
|
|
526
|
+
super(message || AskImuOnDesktopError.DEFAULT_MESSAGE);
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
/**
|
|
531
|
+
* Imu (Inertial Measurement Unit) provider retrieve acceleration data
|
|
532
|
+
* and/or angular rate data from inertial sensors.
|
|
533
|
+
*
|
|
534
|
+
* -----------------------------------
|
|
535
|
+
* Overview of compatibilities:
|
|
536
|
+
* -----------------------------------
|
|
537
|
+
*
|
|
538
|
+
* Chrome Android (v72.0.3626): YES (via devicemotion)
|
|
539
|
+
* Safari iOS (v12.0): YES (via devicemotion)
|
|
540
|
+
* Opera Android (v50.2.2426): NO {@link https://developer.mozilla.org/en-US/docs/Web/API/DeviceMotionEvent}
|
|
541
|
+
* Firefox Android (v65.0.1): YES (via devicemotion)
|
|
542
|
+
*
|
|
543
|
+
* -----------------------------------
|
|
544
|
+
*/
|
|
545
|
+
class Imu extends Provider {
|
|
546
|
+
|
|
547
|
+
/**
|
|
548
|
+
* @override
|
|
549
|
+
*/
|
|
550
|
+
static get pname() {
|
|
551
|
+
return 'IMU';
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
/**
|
|
555
|
+
* @override
|
|
556
|
+
*/
|
|
557
|
+
static get eventsType() {
|
|
558
|
+
return [EventType.AngularRate, EventType.Acceleration];
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
/**
|
|
562
|
+
* @override
|
|
563
|
+
*/
|
|
564
|
+
static get _availability() {
|
|
565
|
+
return BrowserUtils.isMobile
|
|
566
|
+
? Promise.resolve()
|
|
567
|
+
: Promise.reject(new AskImuOnDesktopError());
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
/**
|
|
571
|
+
* @override
|
|
572
|
+
*/
|
|
573
|
+
start() {
|
|
574
|
+
const subscribe = () => window.addEventListener('devicemotion', this.parseDeviceMotionEvent, true);
|
|
575
|
+
|
|
576
|
+
if (typeof (DeviceMotionEvent) !== 'undefined' && typeof (DeviceMotionEvent.requestPermission) === 'function') {
|
|
577
|
+
DeviceMotionEvent
|
|
578
|
+
.requestPermission()
|
|
579
|
+
.then(response => {
|
|
580
|
+
if (response !== 'granted') {
|
|
581
|
+
throw new Error('Permission not granted');
|
|
582
|
+
}
|
|
583
|
+
subscribe();
|
|
584
|
+
})
|
|
585
|
+
.catch(error => this.notifyError(error));
|
|
586
|
+
} else {
|
|
587
|
+
subscribe();
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
/**
|
|
592
|
+
* @override
|
|
593
|
+
*/
|
|
594
|
+
stop() {
|
|
595
|
+
window.removeEventListener('devicemotion', this.parseDeviceMotionEvent, true);
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
/**
|
|
599
|
+
* devicemotion callback
|
|
600
|
+
* @param {DeviceMotionEvent} e device motion event
|
|
601
|
+
* @returns {ProviderEvent[]} an array of provider events
|
|
602
|
+
* @private
|
|
603
|
+
*/
|
|
604
|
+
parseDeviceMotionEvent = e => {
|
|
605
|
+
const events = [];
|
|
606
|
+
|
|
607
|
+
const timestamp = e.timeStamp / 1e3;
|
|
608
|
+
|
|
609
|
+
let acc;
|
|
610
|
+
if (e.accelerationIncludingGravity) {
|
|
611
|
+
const {
|
|
612
|
+
x, y, z
|
|
613
|
+
} = e.accelerationIncludingGravity;
|
|
614
|
+
|
|
615
|
+
if (typeof x === 'number' && typeof y === 'number' && typeof z === 'number') {
|
|
616
|
+
acc = [x, y, z];
|
|
617
|
+
|
|
618
|
+
if (BrowserUtils.name === Browser.SAFARI
|
|
619
|
+
|| BrowserUtils.name === Browser.IOS_WEBVIEW) {
|
|
620
|
+
acc[0] *= -1;
|
|
621
|
+
acc[1] *= -1;
|
|
622
|
+
acc[2] *= -1;
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
if (acc) {
|
|
628
|
+
events.push(this.createEvent(EventType.Acceleration, {
|
|
629
|
+
timestamp,
|
|
630
|
+
values: acc
|
|
631
|
+
}));
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
let gyr;
|
|
635
|
+
if (e.rotationRate) {
|
|
636
|
+
const {
|
|
637
|
+
alpha, beta, gamma
|
|
638
|
+
} = e.rotationRate;
|
|
639
|
+
|
|
640
|
+
if (typeof alpha === 'number' && typeof beta === 'number' && typeof gamma === 'number') {
|
|
641
|
+
gyr = [deg2rad(alpha), deg2rad(beta), deg2rad(gamma)];
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
if (gyr) {
|
|
646
|
+
events.push(this.createEvent(EventType.AngularRate, {
|
|
647
|
+
timestamp,
|
|
648
|
+
values: gyr
|
|
649
|
+
}));
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
if (events.length !== 0) {
|
|
653
|
+
this.notify(...events);
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
var Imu$1 = new Imu();
|
|
660
|
+
|
|
661
|
+
class MissingSensorError extends Error {
|
|
662
|
+
|
|
663
|
+
static DEFAULT_MESSAGE = 'Impossible to retrieve events, a sensor is missing';
|
|
664
|
+
|
|
665
|
+
constructor(message) {
|
|
666
|
+
super(message || MissingSensorError.DEFAULT_MESSAGE);
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
from(fromMessage) {
|
|
670
|
+
this.message += ' from ' + fromMessage;
|
|
671
|
+
return this;
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
class MissingAccelerometerError extends MissingSensorError {
|
|
676
|
+
|
|
677
|
+
static DEFAULT_MESSAGE = 'Impossible to retrieve Acceleration data';
|
|
678
|
+
|
|
679
|
+
constructor(message) {
|
|
680
|
+
super(message || MissingAccelerometerError.DEFAULT_MESSAGE);
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
class Accelerometer extends Provider {
|
|
685
|
+
|
|
686
|
+
/**
|
|
687
|
+
* @override
|
|
688
|
+
*/
|
|
689
|
+
static get pname() {
|
|
690
|
+
return 'Accelerometer';
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
/**
|
|
694
|
+
* @override
|
|
695
|
+
*/
|
|
696
|
+
static get eventsType() {
|
|
697
|
+
return [EventType.Acceleration];
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
/**
|
|
701
|
+
* @override
|
|
702
|
+
*/
|
|
703
|
+
get _availability() {
|
|
704
|
+
return Imu$1.availability;
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
/**
|
|
708
|
+
* @override
|
|
709
|
+
*/
|
|
710
|
+
start() {
|
|
711
|
+
this.providerId = Imu$1.addEventListener(
|
|
712
|
+
events => this.parseImuEvents(events),
|
|
713
|
+
error => this.notifyError(error)
|
|
714
|
+
);
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
/**
|
|
718
|
+
* @override
|
|
719
|
+
*/
|
|
720
|
+
stop() {
|
|
721
|
+
Imu$1.removeEventListener(this.providerId);
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
/**
|
|
725
|
+
* @private
|
|
726
|
+
*/
|
|
727
|
+
parseImuEvents = events => {
|
|
728
|
+
|
|
729
|
+
const accelerationEvent = events.find(event => event.dataType === EventType.Acceleration);
|
|
730
|
+
if (!accelerationEvent) {
|
|
731
|
+
this.notifyError(new MissingAccelerometerError().from('devicemotion'));
|
|
732
|
+
return;
|
|
733
|
+
}
|
|
734
|
+
this.notify(accelerationEvent.clone());
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
var Accelerometer$1 = new Accelerometer();
|
|
740
|
+
|
|
741
|
+
class MissingGyroscopeError extends MissingSensorError {
|
|
742
|
+
|
|
743
|
+
static DEFAULT_MESSAGE = 'Impossible to retrieve Angular Rate data';
|
|
744
|
+
|
|
745
|
+
constructor(message) {
|
|
746
|
+
super(message || MissingSensorError.DEFAULT_MESSAGE);
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
class Gyroscope extends Provider {
|
|
751
|
+
|
|
752
|
+
/**
|
|
753
|
+
* @override
|
|
754
|
+
*/
|
|
755
|
+
static get pname() {
|
|
756
|
+
return 'Gyroscope';
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
/**
|
|
760
|
+
* @override
|
|
761
|
+
*/
|
|
762
|
+
static get eventsType() {
|
|
763
|
+
return [EventType.AngularRate];
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
/**
|
|
767
|
+
* @override
|
|
768
|
+
*/
|
|
769
|
+
get _availability() {
|
|
770
|
+
return Imu$1.availability;
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
/**
|
|
774
|
+
* @override
|
|
775
|
+
*/
|
|
776
|
+
start() {
|
|
777
|
+
this.providerId = Imu$1.addEventListener(
|
|
778
|
+
events => this.parseImuEvents(events),
|
|
779
|
+
error => this.notifyError(error)
|
|
780
|
+
);
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
/**
|
|
784
|
+
* @override
|
|
785
|
+
*/
|
|
786
|
+
stop() {
|
|
787
|
+
Imu$1.removeEventListener(this.providerId);
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
/**
|
|
791
|
+
* @private
|
|
792
|
+
*/
|
|
793
|
+
parseImuEvents = events => {
|
|
794
|
+
|
|
795
|
+
const gyroscopeEvent = events.find(event => event.dataType === EventType.AngularRate);
|
|
796
|
+
if (!gyroscopeEvent) {
|
|
797
|
+
this.notifyError(new MissingGyroscopeError().from('devicemotion'));
|
|
798
|
+
return;
|
|
799
|
+
}
|
|
800
|
+
this.notify(gyroscopeEvent.clone());
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
var Gyroscope$1 = new Gyroscope();
|
|
806
|
+
|
|
807
|
+
class HighRotationsDetector extends Provider {
|
|
808
|
+
|
|
809
|
+
// in radians by second
|
|
810
|
+
static THRESHOLD = 10;
|
|
811
|
+
|
|
812
|
+
/** @type {number} in seconds */
|
|
813
|
+
static DELAY_CONSIDERATION = 3;
|
|
814
|
+
|
|
815
|
+
/**
|
|
816
|
+
* @override
|
|
817
|
+
*/
|
|
818
|
+
static get pname() {
|
|
819
|
+
return 'HighRotationsDetector';
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
|
|
823
|
+
/**
|
|
824
|
+
* @override
|
|
825
|
+
*/
|
|
826
|
+
static get eventsType() {
|
|
827
|
+
return [EventType.HighRotation];
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
/**
|
|
831
|
+
* @override
|
|
832
|
+
*/
|
|
833
|
+
get _availability() {
|
|
834
|
+
return Gyroscope$1.availability;
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
/**
|
|
838
|
+
* @override
|
|
839
|
+
*/
|
|
840
|
+
start() {
|
|
841
|
+
this.providerId = Gyroscope$1.addEventListener(
|
|
842
|
+
events => this._parseGyroscopeEvent(events[0]),
|
|
843
|
+
error => this.notifyError(error)
|
|
844
|
+
);
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
/**
|
|
848
|
+
* @override
|
|
849
|
+
*/
|
|
850
|
+
stop() {
|
|
851
|
+
Gyroscope$1.removeEventListener(this.providerId);
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
isInProgress() {
|
|
855
|
+
if (!this.lastEvent || !Gyroscope$1.lastEvent) {
|
|
856
|
+
return false;
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
const diffTime = Gyroscope$1.lastEvent.data.timestamp - this.lastEvent.data.timestamp;
|
|
860
|
+
return diffTime < HighRotationsDetector.DELAY_CONSIDERATION;
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
/**
|
|
864
|
+
* @private
|
|
865
|
+
*/
|
|
866
|
+
_parseGyroscopeEvent = gyroscopeEvent => {
|
|
867
|
+
|
|
868
|
+
const { values, timestamp } = gyroscopeEvent.data;
|
|
869
|
+
const speed = Vector3.norm(values);
|
|
870
|
+
if (speed > HighRotationsDetector.THRESHOLD) {
|
|
871
|
+
this.notify(this.createEvent(EventType.HighRotation, { timestamp }, [gyroscopeEvent]));
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
var HighRotationsDetector$1 = new HighRotationsDetector();
|
|
878
|
+
|
|
879
|
+
const DEFAULT_RELATIVE_NOISES = {
|
|
880
|
+
acc: 0.5,
|
|
881
|
+
gyr: 0.3
|
|
882
|
+
};
|
|
883
|
+
|
|
884
|
+
const DEFAULT_ABSOLUTE_NOISES = {
|
|
885
|
+
acc: 0.5,
|
|
886
|
+
gyr: 0.3,
|
|
887
|
+
yc: 2
|
|
888
|
+
};
|
|
889
|
+
|
|
890
|
+
class EkfAttitude {
|
|
891
|
+
|
|
892
|
+
constructor(accRef = [0, 0, 1], ycRef = [-1, 0, 0]) {
|
|
893
|
+
|
|
894
|
+
this.accRef = accRef;
|
|
895
|
+
this.cRef = ycRef;
|
|
896
|
+
|
|
897
|
+
this.P = Matrix.diag(Array(4).fill(0.1 ** 2));
|
|
898
|
+
|
|
899
|
+
this.quaternion = null;
|
|
900
|
+
|
|
901
|
+
|
|
902
|
+
this.noises = {
|
|
903
|
+
relative: null,
|
|
904
|
+
absolute: null
|
|
905
|
+
};
|
|
906
|
+
this.setRelativeNoises(DEFAULT_RELATIVE_NOISES);
|
|
907
|
+
this.setAbsoluteNoises(DEFAULT_ABSOLUTE_NOISES);
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
setRelativeNoises(relativeNoises) {
|
|
911
|
+
this.noises.relative = {
|
|
912
|
+
accelerometer: Matrix.diag(Array(3).fill(relativeNoises.acc ** 2)),
|
|
913
|
+
gyroscope: Matrix.diag(Array(3).fill(relativeNoises.gyr ** 2))
|
|
914
|
+
};
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
|
|
918
|
+
setAbsoluteNoises(absoluteNoises) {
|
|
919
|
+
this.noises.absolute = {
|
|
920
|
+
accelerometer: Matrix.diag(Array(3).fill(absoluteNoises.acc ** 2)),
|
|
921
|
+
gyroscope: Matrix.diag(Array(3).fill(absoluteNoises.gyr ** 2)),
|
|
922
|
+
yc: Matrix.diag(Array(3).fill(absoluteNoises.yc ** 2))
|
|
923
|
+
};
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
/**
|
|
927
|
+
* Try to initialize filter.
|
|
928
|
+
* To initialize, we need at least current acceleration (acc)
|
|
929
|
+
*/
|
|
930
|
+
tryInitialize(acc, mag) {
|
|
931
|
+
|
|
932
|
+
const accNormalized = Vector3.normalize(acc);
|
|
933
|
+
|
|
934
|
+
if (mag) {
|
|
935
|
+
const magNormalized = Vector3.normalize(mag);
|
|
936
|
+
|
|
937
|
+
const H = Vector3.normalize(Vector3.cross(magNormalized, accNormalized));
|
|
938
|
+
const M = Vector3.cross(accNormalized, H);
|
|
939
|
+
|
|
940
|
+
const R = [
|
|
941
|
+
[H[0], M[0], accNormalized[0]],
|
|
942
|
+
[H[1], M[1], accNormalized[1]],
|
|
943
|
+
[H[2], M[2], accNormalized[2]]
|
|
944
|
+
];
|
|
945
|
+
|
|
946
|
+
this.quaternion = Quaternion.fromMatrix3(R);
|
|
947
|
+
|
|
948
|
+
} else {
|
|
949
|
+
|
|
950
|
+
const r = Vector3.dot(accNormalized, this.accRef) + 1;
|
|
951
|
+
const v = Vector3.cross(accNormalized, this.accRef);
|
|
952
|
+
|
|
953
|
+
let quaternion = [r, v[0], v[1], v[2]];
|
|
954
|
+
quaternion = Quaternion.normalize(quaternion);
|
|
955
|
+
|
|
956
|
+
this.quaternion = quaternion;
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
return this.quaternion;
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
update(diffTime, acc, gyr, mag) {
|
|
963
|
+
|
|
964
|
+
if (!this.quaternion) {
|
|
965
|
+
return this.tryInitialize(acc, mag);
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
let q = this.quaternion;
|
|
969
|
+
|
|
970
|
+
/* ------------
|
|
971
|
+
* ESTIMATION
|
|
972
|
+
* ------------*/
|
|
973
|
+
|
|
974
|
+
const qArray = q;
|
|
975
|
+
const gyrInt = Vector3.multiplyScalar(gyr, 0.5 * diffTime);
|
|
976
|
+
const F = this.computeC([1, gyrInt[0], gyrInt[1], gyrInt[2]]);
|
|
977
|
+
const qAPriori = Matrix.multiplyVector(F, q);
|
|
978
|
+
const E1 = Matrix.diag([qArray[0], qArray[0], qArray[0]]);
|
|
979
|
+
const eSkew = Matrix3.skew([qArray[1], qArray[2], qArray[3]]);
|
|
980
|
+
|
|
981
|
+
const qPart = [-1 * qArray[1], -1 * qArray[2], -1 * qArray[3]];
|
|
982
|
+
const E = Matrix.concatRow([qPart], Matrix3.add(eSkew, E1));
|
|
983
|
+
|
|
984
|
+
const Qk = Matrix.multiplyScalar(
|
|
985
|
+
Matrix.multiply(
|
|
986
|
+
Matrix.multiply(E, this.noises[mag ? 'absolute' : 'relative'].gyroscope),
|
|
987
|
+
Matrix.transpose(E)
|
|
988
|
+
),
|
|
989
|
+
(diffTime / 2) ** 2
|
|
990
|
+
);
|
|
991
|
+
|
|
992
|
+
const pAPriori = Matrix4.add(
|
|
993
|
+
Matrix.multiply(
|
|
994
|
+
Matrix.multiply(F, this.P),
|
|
995
|
+
Matrix.transpose(F)
|
|
996
|
+
),
|
|
997
|
+
Qk
|
|
998
|
+
);
|
|
999
|
+
|
|
1000
|
+
/* ------------
|
|
1001
|
+
* CORRECTION
|
|
1002
|
+
* ------------*/
|
|
1003
|
+
|
|
1004
|
+
const accNormalized = Vector3.normalize(acc);
|
|
1005
|
+
let dz, K, H;
|
|
1006
|
+
|
|
1007
|
+
if (mag) {
|
|
1008
|
+
|
|
1009
|
+
const magNormalized = Vector3.normalize(mag);
|
|
1010
|
+
const yc = Vector3.cross(accNormalized, magNormalized);
|
|
1011
|
+
const ycNormalized = Vector3.normalize(yc);
|
|
1012
|
+
|
|
1013
|
+
const dzYc = Vector3.subtract(ycNormalized, Quaternion.rotate(qAPriori, this.cRef));
|
|
1014
|
+
const dzAcc = Vector3.subtract(accNormalized, Quaternion.rotate(qAPriori, this.accRef));
|
|
1015
|
+
dz = Vector.concat(dzYc, dzAcc);
|
|
1016
|
+
|
|
1017
|
+
const HYc = this.jacobianES(qAPriori, this.cRef);
|
|
1018
|
+
const HAcc = this.jacobianES(qAPriori, this.accRef);
|
|
1019
|
+
H = Matrix.concatRow(HYc, HAcc);
|
|
1020
|
+
|
|
1021
|
+
const RYc = Matrix.concatLine(this.noises.absolute.yc, Matrix3.zeros);
|
|
1022
|
+
const RAcc = Matrix.concatLine(Matrix3.zeros, this.noises.absolute.accelerometer);
|
|
1023
|
+
const R = Matrix.concatRow(RYc, RAcc);
|
|
1024
|
+
|
|
1025
|
+
K = Matrix.multiply(
|
|
1026
|
+
Matrix.multiply(pAPriori, Matrix.transpose(H)),
|
|
1027
|
+
Matrix.inverse(
|
|
1028
|
+
Matrix.add(
|
|
1029
|
+
Matrix.multiply(
|
|
1030
|
+
Matrix.multiply(H, pAPriori),
|
|
1031
|
+
Matrix.transpose(H)
|
|
1032
|
+
),
|
|
1033
|
+
R
|
|
1034
|
+
)
|
|
1035
|
+
)
|
|
1036
|
+
);
|
|
1037
|
+
} else {
|
|
1038
|
+
dz = Vector3.subtract(accNormalized, Quaternion.rotate(qAPriori, this.accRef));
|
|
1039
|
+
H = this.jacobianES(qAPriori, this.accRef);
|
|
1040
|
+
const R = this.noises.relative.accelerometer;
|
|
1041
|
+
|
|
1042
|
+
K = Matrix.multiply(
|
|
1043
|
+
Matrix.multiply(pAPriori, Matrix.transpose(H)),
|
|
1044
|
+
Matrix3.inverse(
|
|
1045
|
+
Matrix3.add(
|
|
1046
|
+
Matrix.multiply(
|
|
1047
|
+
Matrix.multiply(H, pAPriori),
|
|
1048
|
+
Matrix.transpose(H)
|
|
1049
|
+
),
|
|
1050
|
+
R
|
|
1051
|
+
)
|
|
1052
|
+
)
|
|
1053
|
+
);
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
q = Quaternion.add(
|
|
1057
|
+
qAPriori,
|
|
1058
|
+
Matrix.multiplyVector(K, dz)
|
|
1059
|
+
);
|
|
1060
|
+
const P = Matrix.multiply(
|
|
1061
|
+
Matrix4.subtract(
|
|
1062
|
+
Matrix4.identity,
|
|
1063
|
+
Matrix.multiply(K, H)
|
|
1064
|
+
),
|
|
1065
|
+
pAPriori
|
|
1066
|
+
);
|
|
1067
|
+
|
|
1068
|
+
q = Quaternion.normalize(q);
|
|
1069
|
+
this.quaternion = q;
|
|
1070
|
+
this.P = P;
|
|
1071
|
+
|
|
1072
|
+
return q;
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
computeC(b) {
|
|
1076
|
+
return [
|
|
1077
|
+
[b[0], -b[1], -b[2], -b[3]],
|
|
1078
|
+
[b[1], b[0], b[3], -b[2]],
|
|
1079
|
+
[b[2], -b[3], b[0], b[1]],
|
|
1080
|
+
[b[3], b[2], -b[1], b[0]]
|
|
1081
|
+
];
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
jacobianES(q, v) {
|
|
1085
|
+
|
|
1086
|
+
const [qw, qx, qy, qz] = q;
|
|
1087
|
+
const [vx, vy, vz] = v;
|
|
1088
|
+
|
|
1089
|
+
return [
|
|
1090
|
+
[2 * qz * vy - 2 * qy * vz, 2 * qy * vy + 2 * qz * vz, 2 * qx * vy - 2 * qw * vz - 4 * qy * vx, 2 * qw * vy + 2 * qx * vz - 4 * qz * vx],
|
|
1091
|
+
[2 * qx * vz - 2 * qz * vx, 2 * qw * vz - 4 * qx * vy + 2 * qy * vx, 2 * qx * vx + 2 * qz * vz, 2 * qy * vz - 2 * qw * vx - 4 * qz * vy],
|
|
1092
|
+
[2 * qy * vx - 2 * qx * vy, 2 * qz * vx - 4 * qx * vz - 2 * qw * vy, 2 * qw * vx - 4 * qy * vz + 2 * qz * vy, 2 * qx * vx + 2 * qy * vy]
|
|
1093
|
+
];
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
/**
|
|
1098
|
+
* Relative attitude provider gives the device attitude in East-North-Up (ENU) frame using
|
|
1099
|
+
* browser deviceorientation
|
|
1100
|
+
* The provider does not work until an offset is given.
|
|
1101
|
+
*/
|
|
1102
|
+
class RelativeAttitudeFromEkf extends Provider {
|
|
1103
|
+
|
|
1104
|
+
lastTimestamp = 0;
|
|
1105
|
+
|
|
1106
|
+
/**
|
|
1107
|
+
* @override
|
|
1108
|
+
*/
|
|
1109
|
+
constructor(context) {
|
|
1110
|
+
super(context);
|
|
1111
|
+
this.ekfAttitude = new EkfAttitude();
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
/**
|
|
1115
|
+
* @override
|
|
1116
|
+
*/
|
|
1117
|
+
static get pname() {
|
|
1118
|
+
return 'RelativeAttitudeFromEkf';
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
/**
|
|
1122
|
+
* @override
|
|
1123
|
+
*/
|
|
1124
|
+
static get eventsType() {
|
|
1125
|
+
return [EventType.RelativeAttitude];
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
/**
|
|
1129
|
+
* @override
|
|
1130
|
+
*/
|
|
1131
|
+
get _availability() {
|
|
1132
|
+
return Promise.all([
|
|
1133
|
+
Accelerometer$1.availability,
|
|
1134
|
+
Gyroscope$1.availability
|
|
1135
|
+
]);
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
/**
|
|
1139
|
+
* @override
|
|
1140
|
+
*/
|
|
1141
|
+
start() {
|
|
1142
|
+
this.accelerometerProviderId = Accelerometer$1.addEventListener(
|
|
1143
|
+
events => this.onAccelerometerEvent(events[0]),
|
|
1144
|
+
error => this.notifyError(error)
|
|
1145
|
+
);
|
|
1146
|
+
|
|
1147
|
+
this.gyroscopeProviderId = Gyroscope$1.addEventListener(
|
|
1148
|
+
events => (this.gyroscopeEvent = events[0]),
|
|
1149
|
+
error => this.notifyError(error)
|
|
1150
|
+
);
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
/**
|
|
1154
|
+
* @override
|
|
1155
|
+
*/
|
|
1156
|
+
stop() {
|
|
1157
|
+
Accelerometer$1.removeEventListener(this.accelerometerProviderId);
|
|
1158
|
+
Gyroscope$1.removeEventListener(this.gyroscopeProviderId);
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
/**
|
|
1162
|
+
* @private
|
|
1163
|
+
*/
|
|
1164
|
+
onAccelerometerEvent = accelerationEvent => {
|
|
1165
|
+
|
|
1166
|
+
if (!this.gyroscopeEvent) {
|
|
1167
|
+
return;
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
const {
|
|
1171
|
+
values: acceleration, timestamp
|
|
1172
|
+
} = accelerationEvent.data;
|
|
1173
|
+
|
|
1174
|
+
// Handle timestamps and dt
|
|
1175
|
+
if (this.lastTimestamp === 0) {
|
|
1176
|
+
this.lastTimestamp = timestamp;
|
|
1177
|
+
return;
|
|
1178
|
+
}
|
|
1179
|
+
const diffTime = timestamp - this.lastTimestamp;
|
|
1180
|
+
this.lastTimestamp = timestamp;
|
|
1181
|
+
|
|
1182
|
+
const quaternion = this.ekfAttitude.update(diffTime, acceleration, this.gyroscopeEvent.data.values);
|
|
1183
|
+
|
|
1184
|
+
if (quaternion) {
|
|
1185
|
+
const attitude = new Attitude(quaternion,
|
|
1186
|
+
timestamp,
|
|
1187
|
+
RelativeAttitudeFromInertial$1.DEFAULT_DRIFT
|
|
1188
|
+
);
|
|
1189
|
+
this.notify(this.createEvent(
|
|
1190
|
+
EventType.RelativeAttitude,
|
|
1191
|
+
attitude,
|
|
1192
|
+
[accelerationEvent, this.gyroscopeEvent]));
|
|
1193
|
+
}
|
|
1194
|
+
};
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
var RelativeAttitudeFromEkf$1 = new RelativeAttitudeFromEkf();
|
|
1198
|
+
|
|
1199
|
+
class RelativeAttitudeFromInertial extends Provider {
|
|
1200
|
+
|
|
1201
|
+
/**
|
|
1202
|
+
* default relative attitude drift in rad.second-1
|
|
1203
|
+
*/
|
|
1204
|
+
DEFAULT_DRIFT = deg2rad(5) / 60;
|
|
1205
|
+
|
|
1206
|
+
|
|
1207
|
+
/**
|
|
1208
|
+
* @override
|
|
1209
|
+
*/
|
|
1210
|
+
static get pname() {
|
|
1211
|
+
return 'RelativeAttitudeFromInertial';
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
/**
|
|
1215
|
+
* @override
|
|
1216
|
+
*/
|
|
1217
|
+
static get eventsType() {
|
|
1218
|
+
return [EventType.RelativeAttitude];
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1221
|
+
/**
|
|
1222
|
+
* @override
|
|
1223
|
+
*/
|
|
1224
|
+
get _availability() {
|
|
1225
|
+
return PromiseUtils.any([
|
|
1226
|
+
RelativeAttitudeFromEkf$1.availability,
|
|
1227
|
+
RelativeAttitudeFromBrowser$1.availability,
|
|
1228
|
+
HighRotationsDetector$1.availability
|
|
1229
|
+
]);
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1232
|
+
/**
|
|
1233
|
+
* @override
|
|
1234
|
+
*/
|
|
1235
|
+
start() {
|
|
1236
|
+
|
|
1237
|
+
RelativeAttitudeFromEkf$1.availability
|
|
1238
|
+
.then(() => (this.provider = RelativeAttitudeFromEkf$1))
|
|
1239
|
+
.catch(() => (this.provider = RelativeAttitudeFromBrowser$1))
|
|
1240
|
+
.finally(() => {
|
|
1241
|
+
this.listenerId = this.provider.addEventListener(
|
|
1242
|
+
events => this._parseEvent(events[0]),
|
|
1243
|
+
error => this.notifyError(error)
|
|
1244
|
+
);
|
|
1245
|
+
this._highRotationsDetector = HighRotationsDetector$1.addEventListener(
|
|
1246
|
+
() => {},
|
|
1247
|
+
error => this.notifyError(error)
|
|
1248
|
+
);
|
|
1249
|
+
});
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
/**
|
|
1253
|
+
* @param {ProviderEvent<Attitude>} event
|
|
1254
|
+
*/
|
|
1255
|
+
_parseEvent(event) {
|
|
1256
|
+
const relativeAttitudeEvent = event.clone();
|
|
1257
|
+
if (HighRotationsDetector$1.isInProgress()) {
|
|
1258
|
+
let accuracy = relativeAttitudeEvent.data.accuracy + (this.DEFAULT_DRIFT * 100);
|
|
1259
|
+
accuracy = Math.min(accuracy, Math.PI);
|
|
1260
|
+
relativeAttitudeEvent.data.accuracy = accuracy;
|
|
1261
|
+
}
|
|
1262
|
+
this.notify(relativeAttitudeEvent);
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
/**
|
|
1266
|
+
* @override
|
|
1267
|
+
*/
|
|
1268
|
+
stop() {
|
|
1269
|
+
if (this.provider) {
|
|
1270
|
+
this.provider.removeEventListener(this.listenerId);
|
|
1271
|
+
this.provider = null;
|
|
1272
|
+
}
|
|
1273
|
+
HighRotationsDetector$1.removeEventListener(this._highRotationsDetector);
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
var RelativeAttitudeFromInertial$1 = new RelativeAttitudeFromInertial();
|
|
1278
|
+
|
|
1279
|
+
/**
|
|
1280
|
+
* Relative attitude provider gives the device attitude in a custom frame (x-right, y-front, z-up) using
|
|
1281
|
+
* browser deviceorientation
|
|
1282
|
+
*
|
|
1283
|
+
* -----------------------------------
|
|
1284
|
+
* Overview of compatibilities:
|
|
1285
|
+
* -----------------------------------
|
|
1286
|
+
*
|
|
1287
|
+
* Chrome Android (v72.0.3626): YES (via deviceorientation but deviceorientation.alpha is unreliable! Sometimes it starts at 0°, sometimes at 270°)
|
|
1288
|
+
* Safari iOS (v12.0): YES (via deviceorientation)
|
|
1289
|
+
* Opera Android (v50.2.2426): NO {@link https://developer.mozilla.org/en-US/docs/Web/API/Window/ondeviceorientation}
|
|
1290
|
+
* Firefox Android (v65.0.1): YES (via deviceorientation)
|
|
1291
|
+
*
|
|
1292
|
+
* -----------------------------------
|
|
1293
|
+
*/
|
|
1294
|
+
class RelativeAttitudeFromBrowser extends Provider {
|
|
1295
|
+
|
|
1296
|
+
/**
|
|
1297
|
+
* @override
|
|
1298
|
+
*/
|
|
1299
|
+
static get pname() {
|
|
1300
|
+
return 'RelativeAttitudeFromBrowser';
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1303
|
+
/**
|
|
1304
|
+
* @override
|
|
1305
|
+
*/
|
|
1306
|
+
static get eventsType() {
|
|
1307
|
+
return [EventType.RelativeAttitude];
|
|
1308
|
+
}
|
|
1309
|
+
|
|
1310
|
+
/**
|
|
1311
|
+
* @override
|
|
1312
|
+
*/
|
|
1313
|
+
get _availability() {
|
|
1314
|
+
return BrowserUtils.isMobile
|
|
1315
|
+
? Promise.resolve()
|
|
1316
|
+
: Promise.reject(new AskImuOnDesktopError());
|
|
1317
|
+
}
|
|
1318
|
+
|
|
1319
|
+
/**
|
|
1320
|
+
* @override
|
|
1321
|
+
*/
|
|
1322
|
+
start() {
|
|
1323
|
+
const subscribe = () => window.addEventListener('deviceorientation', this.onDeviceOrientationEvent, true);
|
|
1324
|
+
|
|
1325
|
+
if (typeof (DeviceOrientationEvent) !== 'undefined' && typeof (DeviceOrientationEvent.requestPermission) === 'function') {
|
|
1326
|
+
DeviceOrientationEvent
|
|
1327
|
+
.requestPermission()
|
|
1328
|
+
.then(response => {
|
|
1329
|
+
if (response !== 'granted') {
|
|
1330
|
+
throw new Error('Permission not granted');
|
|
1331
|
+
}
|
|
1332
|
+
subscribe();
|
|
1333
|
+
})
|
|
1334
|
+
.catch(error => this.notifyError(error));
|
|
1335
|
+
} else {
|
|
1336
|
+
subscribe();
|
|
1337
|
+
}
|
|
1338
|
+
}
|
|
1339
|
+
|
|
1340
|
+
/**
|
|
1341
|
+
* @override
|
|
1342
|
+
*/
|
|
1343
|
+
stop() {
|
|
1344
|
+
window.removeEventListener('deviceorientation', this.onDeviceOrientationEvent, true);
|
|
1345
|
+
}
|
|
1346
|
+
|
|
1347
|
+
|
|
1348
|
+
onDeviceOrientationEvent = e => {
|
|
1349
|
+
|
|
1350
|
+
if (typeof e.alpha !== 'number' || typeof e.beta !== 'number' || typeof e.gamma !== 'number') {
|
|
1351
|
+
this.notifyError(new MissingSensorError().from('deviceorientation'));
|
|
1352
|
+
return;
|
|
1353
|
+
}
|
|
1354
|
+
|
|
1355
|
+
const quaternion = Rotations.eulerToQuaternionZXYDegrees([e.alpha, e.beta, e.gamma]);
|
|
1356
|
+
const attitude = new Attitude(quaternion,
|
|
1357
|
+
e.timeStamp / 1e3,
|
|
1358
|
+
RelativeAttitudeFromInertial$1.DEFAULT_DRIFT
|
|
1359
|
+
);
|
|
1360
|
+
this.notify(this.createEvent(EventType.RelativeAttitude, attitude));
|
|
1361
|
+
};
|
|
1362
|
+
}
|
|
1363
|
+
|
|
1364
|
+
var RelativeAttitudeFromBrowser$1 = new RelativeAttitudeFromBrowser();
|
|
1365
|
+
|
|
1366
|
+
class MissingArCoreError extends Error {
|
|
1367
|
+
|
|
1368
|
+
static DEFAULT_MESSAGE = 'ARCore is missing';
|
|
1369
|
+
|
|
1370
|
+
constructor(message) {
|
|
1371
|
+
super(message || MissingArCoreError.DEFAULT_MESSAGE);
|
|
1372
|
+
}
|
|
1373
|
+
}
|
|
1374
|
+
|
|
1375
|
+
class MissingNativeInterfaceError extends Error {
|
|
1376
|
+
|
|
1377
|
+
static DEFAULT_MESSAGE = 'Native interface is missing';
|
|
1378
|
+
|
|
1379
|
+
constructor(message) {
|
|
1380
|
+
super(message || MissingNativeInterfaceError.DEFAULT_MESSAGE);
|
|
1381
|
+
}
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1384
|
+
/* eslint-disable no-bitwise */
|
|
1385
|
+
class ArCore extends Provider {
|
|
1386
|
+
|
|
1387
|
+
static Payload = {
|
|
1388
|
+
Pose: {
|
|
1389
|
+
ref: 2 ** 0,
|
|
1390
|
+
size: 7
|
|
1391
|
+
},
|
|
1392
|
+
Barcode: {
|
|
1393
|
+
ref: 2 ** 1,
|
|
1394
|
+
size: 1
|
|
1395
|
+
},
|
|
1396
|
+
ProjMat: {
|
|
1397
|
+
ref: 2 ** 2,
|
|
1398
|
+
size: 16
|
|
1399
|
+
},
|
|
1400
|
+
ImageRef: {
|
|
1401
|
+
ref: 2 ** 3,
|
|
1402
|
+
size: 1
|
|
1403
|
+
}
|
|
1404
|
+
};
|
|
1405
|
+
|
|
1406
|
+
/**
|
|
1407
|
+
* default relative attitude drift in rad.second-1
|
|
1408
|
+
*/
|
|
1409
|
+
static RELATIVE_ATTITUDE_DRIFT = deg2rad(3) / 60;
|
|
1410
|
+
|
|
1411
|
+
previousPosition = [0, 0, 0];
|
|
1412
|
+
|
|
1413
|
+
/**
|
|
1414
|
+
* @override
|
|
1415
|
+
*/
|
|
1416
|
+
static get pname() {
|
|
1417
|
+
return 'ArCore';
|
|
1418
|
+
}
|
|
1419
|
+
|
|
1420
|
+
/**
|
|
1421
|
+
* @override
|
|
1422
|
+
*/
|
|
1423
|
+
static get eventsType() {
|
|
1424
|
+
return [EventType.RelativeAttitude, EventType.RelativePosition, EventType.Barcode];
|
|
1425
|
+
}
|
|
1426
|
+
|
|
1427
|
+
|
|
1428
|
+
/**
|
|
1429
|
+
* @override
|
|
1430
|
+
*/
|
|
1431
|
+
start() {
|
|
1432
|
+
if (this.nativeProvider) {
|
|
1433
|
+
this.nativeProvider.start();
|
|
1434
|
+
}
|
|
1435
|
+
|
|
1436
|
+
this.pullDataLoop();
|
|
1437
|
+
}
|
|
1438
|
+
|
|
1439
|
+
/**
|
|
1440
|
+
* @override
|
|
1441
|
+
*/
|
|
1442
|
+
stop() {
|
|
1443
|
+
if (this.nativeProvider) {
|
|
1444
|
+
this.nativeProvider.stop();
|
|
1445
|
+
}
|
|
1446
|
+
}
|
|
1447
|
+
|
|
1448
|
+
pullDataLoop = () => {
|
|
1449
|
+
|
|
1450
|
+
if (this.state === ProviderState.STOPPPED) {
|
|
1451
|
+
return;
|
|
1452
|
+
}
|
|
1453
|
+
|
|
1454
|
+
const payload = JSON.parse(this.nativeProvider.getInfo());
|
|
1455
|
+
if (payload.length > 1) {
|
|
1456
|
+
this.parsePayload(payload);
|
|
1457
|
+
}
|
|
1458
|
+
requestAnimationFrame(this.pullDataLoop);
|
|
1459
|
+
}
|
|
1460
|
+
|
|
1461
|
+
parsePayload(payload) {
|
|
1462
|
+
|
|
1463
|
+
const ref = payload[0];
|
|
1464
|
+
let bufferIndex = 1;
|
|
1465
|
+
|
|
1466
|
+
const events = [];
|
|
1467
|
+
|
|
1468
|
+
const time = TimeUtils.preciseTime() / 1e3;
|
|
1469
|
+
|
|
1470
|
+
if (ref & ArCore.Payload.Pose.ref) {
|
|
1471
|
+
|
|
1472
|
+
const attitude = new Attitude(
|
|
1473
|
+
payload.slice(bufferIndex, bufferIndex + 4),
|
|
1474
|
+
time,
|
|
1475
|
+
this.constructor.RELATIVE_ATTITUDE_DRIFT,
|
|
1476
|
+
);
|
|
1477
|
+
events.push(this.createEvent(EventType.RelativeAttitude, attitude));
|
|
1478
|
+
|
|
1479
|
+
const newPosition = [payload[bufferIndex + 4], payload[bufferIndex + 5], payload[bufferIndex + 6]];
|
|
1480
|
+
const position = new RelativePosition(
|
|
1481
|
+
newPosition[0] - this.previousPosition[0],
|
|
1482
|
+
newPosition[1] - this.previousPosition[1],
|
|
1483
|
+
newPosition[2] - this.previousPosition[2],
|
|
1484
|
+
time,
|
|
1485
|
+
1e-4
|
|
1486
|
+
);
|
|
1487
|
+
this.previousPosition = newPosition;
|
|
1488
|
+
events.push(this.createEvent(EventType.RelativePosition, position));
|
|
1489
|
+
|
|
1490
|
+
bufferIndex += ArCore.Payload.Pose.size;
|
|
1491
|
+
}
|
|
1492
|
+
|
|
1493
|
+
if (ref & ArCore.Payload.Barcode.ref) {
|
|
1494
|
+
events.push(this.createEvent(EventType.Barcode, payload[bufferIndex]));
|
|
1495
|
+
bufferIndex += ArCore.Payload.Barcode.size;
|
|
1496
|
+
}
|
|
1497
|
+
|
|
1498
|
+
if (ref & ArCore.Payload.ProjMat.ref) {
|
|
1499
|
+
const projMatrix = payload.slice(bufferIndex, bufferIndex + ArCore.Payload.ProjMat.size);
|
|
1500
|
+
events.push(this.createEvent(EventType.CameraProjectionMatrix, projMatrix));
|
|
1501
|
+
bufferIndex += ArCore.Payload.ProjMat.size;
|
|
1502
|
+
}
|
|
1503
|
+
|
|
1504
|
+
if (events.length !== 0) {
|
|
1505
|
+
this.notify(...events);
|
|
1506
|
+
}
|
|
1507
|
+
}
|
|
1508
|
+
|
|
1509
|
+
|
|
1510
|
+
get nativeProvider() {
|
|
1511
|
+
|
|
1512
|
+
if (!this._nativeProvider) {
|
|
1513
|
+
|
|
1514
|
+
if (!this.nativeInterface) {
|
|
1515
|
+
throw new MissingNativeInterfaceError();
|
|
1516
|
+
}
|
|
1517
|
+
|
|
1518
|
+
this._nativeProvider = this.nativeInterface.getArCoreProvider();
|
|
1519
|
+
if (!this._nativeProvider) {
|
|
1520
|
+
throw new MissingArCoreError();
|
|
1521
|
+
}
|
|
1522
|
+
|
|
1523
|
+
}
|
|
1524
|
+
|
|
1525
|
+
return this._nativeProvider;
|
|
1526
|
+
}
|
|
1527
|
+
|
|
1528
|
+
/**
|
|
1529
|
+
* @override
|
|
1530
|
+
*/
|
|
1531
|
+
get _availability() {
|
|
1532
|
+
try {
|
|
1533
|
+
const nativeProvider = this.nativeProvider;
|
|
1534
|
+
|
|
1535
|
+
if (!nativeProvider.checkAvailability()) {
|
|
1536
|
+
return Promise.reject(new MissingArCoreError());
|
|
1537
|
+
}
|
|
1538
|
+
|
|
1539
|
+
} catch (e) {
|
|
1540
|
+
return Promise.reject(e);
|
|
1541
|
+
}
|
|
1542
|
+
|
|
1543
|
+
return Promise.resolve();
|
|
1544
|
+
}
|
|
1545
|
+
|
|
1546
|
+
enableBarcodeScanner() {
|
|
1547
|
+
try {
|
|
1548
|
+
this.nativeProvider.enableBarcodeScanner();
|
|
1549
|
+
} catch (e) {
|
|
1550
|
+
this.notifyError(e);
|
|
1551
|
+
}
|
|
1552
|
+
}
|
|
1553
|
+
|
|
1554
|
+
disableBarcodeScanner() {
|
|
1555
|
+
try {
|
|
1556
|
+
this.nativeProvider.disableBarcodeScanner();
|
|
1557
|
+
} catch (e) {
|
|
1558
|
+
this.notifyError(e);
|
|
1559
|
+
}
|
|
1560
|
+
}
|
|
1561
|
+
|
|
1562
|
+
/**
|
|
1563
|
+
* @override
|
|
1564
|
+
*/
|
|
1565
|
+
static get useCameraNatively() {
|
|
1566
|
+
return true;
|
|
1567
|
+
}
|
|
1568
|
+
}
|
|
1569
|
+
|
|
1570
|
+
var ArCore$1 = new ArCore();
|
|
1571
|
+
|
|
1572
|
+
/**
|
|
1573
|
+
* Relative attitude provider gives the device attitude in East-North-Up (ENU) frame using
|
|
1574
|
+
* browser deviceorientation
|
|
1575
|
+
* The provider does not work until an offset is given.
|
|
1576
|
+
*/
|
|
1577
|
+
class RelativeAttitude extends Provider {
|
|
1578
|
+
|
|
1579
|
+
/**
|
|
1580
|
+
* @override
|
|
1581
|
+
*/
|
|
1582
|
+
static get pname() {
|
|
1583
|
+
return 'RelativeAttitude';
|
|
1584
|
+
}
|
|
1585
|
+
|
|
1586
|
+
/**
|
|
1587
|
+
* @override
|
|
1588
|
+
*/
|
|
1589
|
+
static get eventsType() {
|
|
1590
|
+
return [EventType.RelativeAttitude];
|
|
1591
|
+
}
|
|
1592
|
+
|
|
1593
|
+
/**
|
|
1594
|
+
* @override
|
|
1595
|
+
*/
|
|
1596
|
+
get _availability() {
|
|
1597
|
+
return RelativeAttitudeFromInertial$1.availability;
|
|
1598
|
+
}
|
|
1599
|
+
|
|
1600
|
+
/**
|
|
1601
|
+
* @override
|
|
1602
|
+
*/
|
|
1603
|
+
start() {
|
|
1604
|
+
this.arCoreMonitoringId = ArCore$1.addMonitoringListener(
|
|
1605
|
+
() => {
|
|
1606
|
+
this.listenArCore();
|
|
1607
|
+
this.unlistenInertial();
|
|
1608
|
+
}, () => {
|
|
1609
|
+
this.unlistenArCore();
|
|
1610
|
+
this.listenInertial();
|
|
1611
|
+
});
|
|
1612
|
+
|
|
1613
|
+
if (ArCore$1.state === ProviderState.STARTED) {
|
|
1614
|
+
this.listenArCore();
|
|
1615
|
+
} else {
|
|
1616
|
+
this.listenInertial();
|
|
1617
|
+
}
|
|
1618
|
+
}
|
|
1619
|
+
|
|
1620
|
+
/**
|
|
1621
|
+
* @override
|
|
1622
|
+
*/
|
|
1623
|
+
stop() {
|
|
1624
|
+
ArCore$1.removeMonitoringListener(this.arCoreMonitoringId);
|
|
1625
|
+
this.unlistenArCore();
|
|
1626
|
+
this.unlistenInertial();
|
|
1627
|
+
}
|
|
1628
|
+
|
|
1629
|
+
|
|
1630
|
+
listenInertial = () => {
|
|
1631
|
+
this.inertialProviderId = RelativeAttitudeFromInertial$1.addEventListener(
|
|
1632
|
+
events => this.notify(events[0].clone()),
|
|
1633
|
+
error => this.notifyError(error)
|
|
1634
|
+
);
|
|
1635
|
+
}
|
|
1636
|
+
|
|
1637
|
+
unlistenInertial = () => {
|
|
1638
|
+
RelativeAttitudeFromInertial$1.removeEventListener(this.inertialProviderId);
|
|
1639
|
+
}
|
|
1640
|
+
|
|
1641
|
+
listenArCore = () => {
|
|
1642
|
+
this.arCoreProviderId = ArCore$1.addEventListener(
|
|
1643
|
+
events => {
|
|
1644
|
+
const relativeAttitudeEvent = events.find(event => event.dataType === EventType.RelativeAttitude);
|
|
1645
|
+
if (relativeAttitudeEvent) {
|
|
1646
|
+
this.notify(relativeAttitudeEvent.clone());
|
|
1647
|
+
}
|
|
1648
|
+
},
|
|
1649
|
+
() => {},
|
|
1650
|
+
false
|
|
1651
|
+
);
|
|
1652
|
+
};
|
|
1653
|
+
|
|
1654
|
+
unlistenArCore = () => {
|
|
1655
|
+
ArCore$1.removeEventListener(this.arCoreProviderId);
|
|
1656
|
+
}
|
|
1657
|
+
}
|
|
1658
|
+
|
|
1659
|
+
var RelativeAttitude$1 = new RelativeAttitude();
|
|
1660
|
+
|
|
1661
|
+
class MissingMagnetometerError extends MissingSensorError {
|
|
1662
|
+
constructor(message) {
|
|
1663
|
+
super(message);
|
|
1664
|
+
}
|
|
1665
|
+
}
|
|
1666
|
+
|
|
1667
|
+
/**
|
|
1668
|
+
* Absolute attitude provider gives the device attitude in East-North-Up (ENU) frame
|
|
1669
|
+
*/
|
|
1670
|
+
class AbsoluteAttitude extends Provider {
|
|
1671
|
+
|
|
1672
|
+
/** @type {boolean} */
|
|
1673
|
+
_attitudeFromBrowserErrored = false;
|
|
1674
|
+
|
|
1675
|
+
/** @type {boolean} */
|
|
1676
|
+
_attitudeFromRelativeErrored = false;
|
|
1677
|
+
|
|
1678
|
+
/*
|
|
1679
|
+
* RELATIVE
|
|
1680
|
+
*/
|
|
1681
|
+
|
|
1682
|
+
/** @type {Attitude} */
|
|
1683
|
+
_relativeAttitude = null;
|
|
1684
|
+
|
|
1685
|
+
/** @type {number} in radians/s */
|
|
1686
|
+
_relativeAccuracy = 0;
|
|
1687
|
+
|
|
1688
|
+
/** @type {ProviderEvent<Attitude|AbsoluteHeading>} */
|
|
1689
|
+
_lastForcedHeadingEvent;
|
|
1690
|
+
|
|
1691
|
+
/** @type {number[]} quaternion offset from relative to absolute */
|
|
1692
|
+
_relAbsQuat;
|
|
1693
|
+
|
|
1694
|
+
|
|
1695
|
+
constructor() {
|
|
1696
|
+
super();
|
|
1697
|
+
|
|
1698
|
+
this._attitudeFromBrowserErrored = false;
|
|
1699
|
+
this._attitudeFromRelativeErrored = false;
|
|
1700
|
+
}
|
|
1701
|
+
|
|
1702
|
+
/**
|
|
1703
|
+
* @override
|
|
1704
|
+
*/
|
|
1705
|
+
static get pname() {
|
|
1706
|
+
return 'AbsoluteAttitude';
|
|
1707
|
+
}
|
|
1708
|
+
|
|
1709
|
+
/**
|
|
1710
|
+
* @override
|
|
1711
|
+
*/
|
|
1712
|
+
static get eventsType() {
|
|
1713
|
+
return [EventType.AbsoluteAttitude];
|
|
1714
|
+
}
|
|
1715
|
+
|
|
1716
|
+
/**
|
|
1717
|
+
* @override
|
|
1718
|
+
*/
|
|
1719
|
+
get _availability() {
|
|
1720
|
+
return PromiseUtils.any([
|
|
1721
|
+
AbsoluteAttitudeFromBrowser$1.availability,
|
|
1722
|
+
RelativeAttitude$1.availability
|
|
1723
|
+
]);
|
|
1724
|
+
}
|
|
1725
|
+
|
|
1726
|
+
/**
|
|
1727
|
+
* @override
|
|
1728
|
+
*/
|
|
1729
|
+
start() {
|
|
1730
|
+
|
|
1731
|
+
this.fromBrowserProviderId = AbsoluteAttitudeFromBrowser$1.addEventListener(
|
|
1732
|
+
events => this._onAttitudeFromBrowser(events[0]),
|
|
1733
|
+
error => {
|
|
1734
|
+
this._attitudeFromBrowserErrored = true;
|
|
1735
|
+
this.onError(error);
|
|
1736
|
+
}
|
|
1737
|
+
);
|
|
1738
|
+
|
|
1739
|
+
this.relativeAttitudeProviderId = RelativeAttitude$1.addEventListener(
|
|
1740
|
+
events => this._onRelativeAttitudeEvent(events[0]),
|
|
1741
|
+
error => {
|
|
1742
|
+
this._attitudeFromRelativeErrored = true;
|
|
1743
|
+
this.onError(error);
|
|
1744
|
+
}
|
|
1745
|
+
);
|
|
1746
|
+
|
|
1747
|
+
}
|
|
1748
|
+
|
|
1749
|
+
/**
|
|
1750
|
+
* @override
|
|
1751
|
+
*/
|
|
1752
|
+
stop() {
|
|
1753
|
+
AbsoluteAttitudeFromBrowser$1.removeEventListener(this.fromBrowserProviderId);
|
|
1754
|
+
RelativeAttitude$1.removeEventListener(this.relativeAttitudeProviderId);
|
|
1755
|
+
}
|
|
1756
|
+
|
|
1757
|
+
onError(error) {
|
|
1758
|
+
if (this._attitudeFromBrowserErrored && this._attitudeFromRelativeErrored) {
|
|
1759
|
+
this.notifyError(error);
|
|
1760
|
+
}
|
|
1761
|
+
}
|
|
1762
|
+
|
|
1763
|
+
|
|
1764
|
+
/**
|
|
1765
|
+
* @param {ProviderEvent<Attitude|AbsoluteHeading>} absoluteHeadingEvent
|
|
1766
|
+
*/
|
|
1767
|
+
_forceHeadingForRelative = (absoluteHeadingEvent) => {
|
|
1768
|
+
|
|
1769
|
+
if (this.lastEvent && absoluteHeadingEvent.data.accuracy > this.lastEvent.data.accuracy) {
|
|
1770
|
+
return;
|
|
1771
|
+
}
|
|
1772
|
+
|
|
1773
|
+
|
|
1774
|
+
/**
|
|
1775
|
+
* @param {Attitude} relativeAttitude
|
|
1776
|
+
*/
|
|
1777
|
+
const calcForceHeading = (relativeAttitude) => {
|
|
1778
|
+
const currentRelativeHeading = relativeAttitude.heading;
|
|
1779
|
+
|
|
1780
|
+
this._relAbsQuat = Quaternion.fromAxisAngle(
|
|
1781
|
+
[0, 0, 1],
|
|
1782
|
+
currentRelativeHeading - absoluteHeadingEvent.data.heading
|
|
1783
|
+
);
|
|
1784
|
+
|
|
1785
|
+
this._relativeAccuracy = 0;
|
|
1786
|
+
this._lastForcedHeadingEvent = absoluteHeadingEvent;
|
|
1787
|
+
};
|
|
1788
|
+
|
|
1789
|
+
// If relativeAttitude does not exist yet, wait the next
|
|
1790
|
+
if (this._relativeAttitude) {
|
|
1791
|
+
calcForceHeading(this._relativeAttitude);
|
|
1792
|
+
return;
|
|
1793
|
+
}
|
|
1794
|
+
|
|
1795
|
+
// If this._relativeAttitude does not exist, wait the first value
|
|
1796
|
+
// /!\ This works only if the relative attitude event is provided rapidly.
|
|
1797
|
+
const providerId = RelativeAttitude$1.addEventListener(
|
|
1798
|
+
events => {
|
|
1799
|
+
calcForceHeading(events[0].data);
|
|
1800
|
+
RelativeAttitude$1.removeEventListener(providerId);
|
|
1801
|
+
}
|
|
1802
|
+
);
|
|
1803
|
+
|
|
1804
|
+
};
|
|
1805
|
+
|
|
1806
|
+
/**
|
|
1807
|
+
* @param {ProviderEvent<Attitude>} event
|
|
1808
|
+
*/
|
|
1809
|
+
_onRelativeAttitudeEvent(event) {
|
|
1810
|
+
|
|
1811
|
+
const { quaternion, accuracy, time } = event.data;
|
|
1812
|
+
|
|
1813
|
+
// Calculate relative accuracy
|
|
1814
|
+
if (this._relativeAttitude) {
|
|
1815
|
+
this._relativeAccuracy += (time - this._relativeAttitude.time) * accuracy;
|
|
1816
|
+
}
|
|
1817
|
+
|
|
1818
|
+
// Keep the relative attitude event for the calculation of relAbsQuat
|
|
1819
|
+
// /!\ Keep this even if forced heading is not set /!\
|
|
1820
|
+
this._relativeAttitude = event.data;
|
|
1821
|
+
|
|
1822
|
+
|
|
1823
|
+
if (!this._lastForcedHeadingEvent) {
|
|
1824
|
+
return;
|
|
1825
|
+
}
|
|
1826
|
+
|
|
1827
|
+
const accuracyWithRelative = Math.min(
|
|
1828
|
+
this._lastForcedHeadingEvent.data.accuracy + this._relativeAccuracy,
|
|
1829
|
+
Math.PI
|
|
1830
|
+
);
|
|
1831
|
+
|
|
1832
|
+
let newAccuracy = accuracyWithRelative;
|
|
1833
|
+
let relAbsQuat = this._relAbsQuat;
|
|
1834
|
+
|
|
1835
|
+
if (this._eventFromBrowser) {
|
|
1836
|
+
|
|
1837
|
+
const {
|
|
1838
|
+
accuracy: accuracyWithAbsolute,
|
|
1839
|
+
heading: headingFromAbsolute
|
|
1840
|
+
} = this._eventFromBrowser.data;
|
|
1841
|
+
|
|
1842
|
+
if (accuracyWithAbsolute < accuracyWithRelative) {
|
|
1843
|
+
newAccuracy = accuracyWithAbsolute;
|
|
1844
|
+
relAbsQuat = Quaternion.fromAxisAngle([0, 0, 1], event.data.heading - headingFromAbsolute);
|
|
1845
|
+
}
|
|
1846
|
+
}
|
|
1847
|
+
|
|
1848
|
+
const absoluteQuat = Quaternion.multiply(relAbsQuat, quaternion);
|
|
1849
|
+
const attitude = new Attitude(absoluteQuat, time, newAccuracy);
|
|
1850
|
+
|
|
1851
|
+
this.notify(this.createEvent(
|
|
1852
|
+
EventType.AbsoluteAttitude,
|
|
1853
|
+
attitude,
|
|
1854
|
+
[event, this._lastForcedHeadingEvent]
|
|
1855
|
+
));
|
|
1856
|
+
}
|
|
1857
|
+
|
|
1858
|
+
|
|
1859
|
+
/**
|
|
1860
|
+
* @param {ProviderEvent<Attitude>} event
|
|
1861
|
+
*/
|
|
1862
|
+
_onAttitudeFromBrowser(event) {
|
|
1863
|
+
this._eventFromBrowser = event;
|
|
1864
|
+
|
|
1865
|
+
if (!this._lastForcedHeadingEvent) {
|
|
1866
|
+
this._forceHeadingForRelative(event);
|
|
1867
|
+
return;
|
|
1868
|
+
}
|
|
1869
|
+
|
|
1870
|
+
// Absolute attitude from browser is not used anymore due to magnetometer inaccuracy on pitch and roll
|
|
1871
|
+
}
|
|
1872
|
+
|
|
1873
|
+
/**
|
|
1874
|
+
* @override
|
|
1875
|
+
* @param {AbsoluteHeading|Attitude|ProviderEvent} data
|
|
1876
|
+
*/
|
|
1877
|
+
feed(data) {
|
|
1878
|
+
|
|
1879
|
+
if (data instanceof AbsoluteHeading) {
|
|
1880
|
+
|
|
1881
|
+
if (data.time === null) {
|
|
1882
|
+
throw Error('the time of the absolute heading is not defined');
|
|
1883
|
+
}
|
|
1884
|
+
if (data.accuracy === null) {
|
|
1885
|
+
throw Error('the accuracy of the absolute heading is not defined');
|
|
1886
|
+
}
|
|
1887
|
+
|
|
1888
|
+
this._forceHeadingForRelative(new ProviderEvent(EventType.AbsoluteHeading, data));
|
|
1889
|
+
|
|
1890
|
+
} else if (data instanceof Attitude) {
|
|
1891
|
+
|
|
1892
|
+
if (data.time === null) {
|
|
1893
|
+
throw Error('the time of the attitude is not defined');
|
|
1894
|
+
}
|
|
1895
|
+
if (data.accuracy === null) {
|
|
1896
|
+
throw Error('the accuracy of the attitude is not defined');
|
|
1897
|
+
}
|
|
1898
|
+
|
|
1899
|
+
this._forceHeadingForRelative(new ProviderEvent(EventType.AbsoluteHeading, data));
|
|
1900
|
+
|
|
1901
|
+
} else if (data instanceof ProviderEvent) {
|
|
1902
|
+
|
|
1903
|
+
if (data.dataType !== EventType.AbsoluteHeading
|
|
1904
|
+
|| !(data.data instanceof AbsoluteHeading)) {
|
|
1905
|
+
throw Error('the provider event is not an AbsoluteHeading');
|
|
1906
|
+
}
|
|
1907
|
+
|
|
1908
|
+
this._forceHeadingForRelative(data);
|
|
1909
|
+
|
|
1910
|
+
} else {
|
|
1911
|
+
throw new Error('data is nor an AbsoluteHeading or an Attitude object');
|
|
1912
|
+
}
|
|
1913
|
+
}
|
|
1914
|
+
}
|
|
1915
|
+
|
|
1916
|
+
var AbsoluteAttitude$1 = new AbsoluteAttitude();
|
|
1917
|
+
|
|
1918
|
+
class TurnDetector extends Provider {
|
|
1919
|
+
|
|
1920
|
+
// in seconds
|
|
1921
|
+
static SLIDING_WINDOW_TIME = 0.3;
|
|
1922
|
+
|
|
1923
|
+
static STD_THRESHOLD = 0.075;
|
|
1924
|
+
|
|
1925
|
+
/** @type {number} in seconds */
|
|
1926
|
+
static CONSIDER_TURN_UNTIL = 1;
|
|
1927
|
+
|
|
1928
|
+
/** @type {number[]} */
|
|
1929
|
+
slidingWindow = [];
|
|
1930
|
+
|
|
1931
|
+
/**
|
|
1932
|
+
* @override
|
|
1933
|
+
*/
|
|
1934
|
+
static get pname() {
|
|
1935
|
+
return 'TurnDetector';
|
|
1936
|
+
}
|
|
1937
|
+
|
|
1938
|
+
/**
|
|
1939
|
+
* @override
|
|
1940
|
+
*/
|
|
1941
|
+
static get eventsType() {
|
|
1942
|
+
return [EventType.Turn];
|
|
1943
|
+
}
|
|
1944
|
+
|
|
1945
|
+
/**
|
|
1946
|
+
* @override
|
|
1947
|
+
*/
|
|
1948
|
+
get _availability() {
|
|
1949
|
+
return RelativeAttitude$1.availability;
|
|
1950
|
+
}
|
|
1951
|
+
|
|
1952
|
+
/**
|
|
1953
|
+
* @override
|
|
1954
|
+
*/
|
|
1955
|
+
start() {
|
|
1956
|
+
this.providerId = RelativeAttitude$1.addEventListener(
|
|
1957
|
+
events => this._parseRelativeAttitude(events[0]),
|
|
1958
|
+
error => this.notifyError(error)
|
|
1959
|
+
);
|
|
1960
|
+
}
|
|
1961
|
+
|
|
1962
|
+
/**
|
|
1963
|
+
* @override
|
|
1964
|
+
*/
|
|
1965
|
+
stop() {
|
|
1966
|
+
RelativeAttitude$1.removeEventListener(this.providerId);
|
|
1967
|
+
}
|
|
1968
|
+
|
|
1969
|
+
isTurning() {
|
|
1970
|
+
if (!this.lastEvent || !RelativeAttitude$1.lastEvent) {
|
|
1971
|
+
return false;
|
|
1972
|
+
}
|
|
1973
|
+
|
|
1974
|
+
const diffTime = RelativeAttitude$1.lastEvent.data.time - this.lastEvent.data.timestamp;
|
|
1975
|
+
return diffTime < TurnDetector.CONSIDER_TURN_UNTIL;
|
|
1976
|
+
}
|
|
1977
|
+
|
|
1978
|
+
/**
|
|
1979
|
+
* @private
|
|
1980
|
+
*/
|
|
1981
|
+
_parseRelativeAttitude = relativeAttitudeEvent => {
|
|
1982
|
+
|
|
1983
|
+
const { heading, time: timestamp } = relativeAttitudeEvent.data;
|
|
1984
|
+
|
|
1985
|
+
this.slidingWindow = this.slidingWindow.filter(item => item[0] >= timestamp - TurnDetector.SLIDING_WINDOW_TIME);
|
|
1986
|
+
this.slidingWindow.push([timestamp, heading]);
|
|
1987
|
+
|
|
1988
|
+
const stdVal = std(this.slidingWindow.map(item => item[1]));
|
|
1989
|
+
if (stdVal > TurnDetector.STD_THRESHOLD) {
|
|
1990
|
+
this.notify(this.createEvent(EventType.Turn, { timestamp }, [relativeAttitudeEvent]));
|
|
1991
|
+
}
|
|
1992
|
+
|
|
1993
|
+
}
|
|
1994
|
+
}
|
|
1995
|
+
|
|
1996
|
+
var TurnDectector = new TurnDetector();
|
|
1997
|
+
|
|
1998
|
+
const Constants = {
|
|
1999
|
+
DEFAULT_ALTITUDE: 1.6
|
|
2000
|
+
};
|
|
2001
|
+
|
|
2002
|
+
class StepDetectionMinMaxPeaks2 {
|
|
2003
|
+
|
|
2004
|
+
// in seconds
|
|
2005
|
+
static WINDOW_TIME = 0.3;
|
|
2006
|
+
|
|
2007
|
+
// in seconds
|
|
2008
|
+
static MIN_TIME_BETWEEN_STEPS = 0.4;
|
|
2009
|
+
|
|
2010
|
+
// in Hz
|
|
2011
|
+
static MAX_FRENQUENCY = 4;
|
|
2012
|
+
static MIN_FRENQUENCY = 1;
|
|
2013
|
+
|
|
2014
|
+
// in m.s-2
|
|
2015
|
+
static VERTICAL_ACC_POSITIVE_PEAK_THRESHOLD = 0.75;
|
|
2016
|
+
static VERTICAL_ACC_NEGATIVE_PEAK_THRESHOLD = -0.3;
|
|
2017
|
+
|
|
2018
|
+
|
|
2019
|
+
constructor() {
|
|
2020
|
+
this.slidingWindow = [];
|
|
2021
|
+
|
|
2022
|
+
this.lastStepTimestamp = -StepDetectionMinMaxPeaks2.MIN_TIME_BETWEEN_STEPS;
|
|
2023
|
+
this.previousVerticalAcc = 0;
|
|
2024
|
+
this.influence = 0.2;
|
|
2025
|
+
}
|
|
2026
|
+
|
|
2027
|
+
|
|
2028
|
+
compute(timestamp, linearAcc, angularRate) {
|
|
2029
|
+
|
|
2030
|
+
const verticalAcc = this.influence * (linearAcc[2] * 2) + (1 - this.influence) * this.previousVerticalAcc;
|
|
2031
|
+
this.previousVerticalAcc = verticalAcc;
|
|
2032
|
+
|
|
2033
|
+
|
|
2034
|
+
if (Math.sqrt(angularRate[0] ** 2 + angularRate[1] ** 2 + angularRate[2] ** 2) > 0.75) {
|
|
2035
|
+
return false;
|
|
2036
|
+
}
|
|
2037
|
+
|
|
2038
|
+
if (this.lastStepTimestamp && this.lastStepTimestamp + StepDetectionMinMaxPeaks2.MIN_TIME_BETWEEN_STEPS > timestamp) {
|
|
2039
|
+
return false;
|
|
2040
|
+
}
|
|
2041
|
+
|
|
2042
|
+
let maxValue = Number.MIN_SAFE_INTEGER;
|
|
2043
|
+
let minValue = Number.MAX_SAFE_INTEGER;
|
|
2044
|
+
|
|
2045
|
+
this.slidingWindow.forEach(function(item, index, object) {
|
|
2046
|
+
if (item.timestamp < timestamp - StepDetectionMinMaxPeaks2.WINDOW_TIME) {
|
|
2047
|
+
object.splice(index, 1);
|
|
2048
|
+
} else {
|
|
2049
|
+
maxValue = Math.max(item.verticalAcc, maxValue);
|
|
2050
|
+
minValue = Math.min(item.verticalAcc, minValue);
|
|
2051
|
+
}
|
|
2052
|
+
});
|
|
2053
|
+
this.slidingWindow.push({
|
|
2054
|
+
timestamp: timestamp,
|
|
2055
|
+
verticalAcc: verticalAcc
|
|
2056
|
+
});
|
|
2057
|
+
|
|
2058
|
+
|
|
2059
|
+
if (maxValue > StepDetectionMinMaxPeaks2.VERTICAL_ACC_POSITIVE_PEAK_THRESHOLD
|
|
2060
|
+
&& minValue < StepDetectionMinMaxPeaks2.VERTICAL_ACC_NEGATIVE_PEAK_THRESHOLD) {
|
|
2061
|
+
|
|
2062
|
+
const timeInterval = this.lastStepTimestamp ? timestamp - this.lastStepTimestamp : 1;
|
|
2063
|
+
this.frequency = Math.min(Math.max((1 / timeInterval), StepDetectionMinMaxPeaks2.MIN_FRENQUENCY), StepDetectionMinMaxPeaks2.MAX_FRENQUENCY);
|
|
2064
|
+
|
|
2065
|
+
this.lastStepTimestamp = timestamp;
|
|
2066
|
+
return true;
|
|
2067
|
+
}
|
|
2068
|
+
|
|
2069
|
+
return false;
|
|
2070
|
+
}
|
|
2071
|
+
|
|
2072
|
+
get lastStepSize() {
|
|
2073
|
+
|
|
2074
|
+
if (!this.frequency) {
|
|
2075
|
+
return 0;
|
|
2076
|
+
}
|
|
2077
|
+
|
|
2078
|
+
const kParamA = 0.45;
|
|
2079
|
+
const kParamB = 0.2;
|
|
2080
|
+
return kParamA + kParamB * this.frequency;
|
|
2081
|
+
}
|
|
2082
|
+
|
|
2083
|
+
get speed() {
|
|
2084
|
+
return this.lastStepSize && this.frequency ? this.lastStepSize * this.frequency : 0;
|
|
2085
|
+
}
|
|
2086
|
+
|
|
2087
|
+
mean(data) {
|
|
2088
|
+
let sum = 0.0, mean = 0.0;
|
|
2089
|
+
|
|
2090
|
+
for (let i = 0; i < data.length; ++i) {
|
|
2091
|
+
sum += data[i].verticalAcc;
|
|
2092
|
+
}
|
|
2093
|
+
mean = sum / data.length;
|
|
2094
|
+
return mean;
|
|
2095
|
+
}
|
|
2096
|
+
|
|
2097
|
+
stddev(data) {
|
|
2098
|
+
const theMean = this.mean(data);
|
|
2099
|
+
let standardDeviation = 0;
|
|
2100
|
+
for (let i = 0; i < data.length; ++i) {
|
|
2101
|
+
standardDeviation += (data[i].verticalAcc - theMean) ** 2;
|
|
2102
|
+
}
|
|
2103
|
+
|
|
2104
|
+
return Math.sqrt(standardDeviation / data.length);
|
|
2105
|
+
}
|
|
2106
|
+
}
|
|
2107
|
+
|
|
2108
|
+
class StepDetector extends Provider {
|
|
2109
|
+
|
|
2110
|
+
constructor() {
|
|
2111
|
+
super();
|
|
2112
|
+
this.stepDetector = new StepDetectionMinMaxPeaks2();
|
|
2113
|
+
}
|
|
2114
|
+
|
|
2115
|
+
/**
|
|
2116
|
+
* @override
|
|
2117
|
+
*/
|
|
2118
|
+
static get pname() {
|
|
2119
|
+
return 'StepDetector';
|
|
2120
|
+
}
|
|
2121
|
+
|
|
2122
|
+
/**
|
|
2123
|
+
* @override
|
|
2124
|
+
*/
|
|
2125
|
+
get _availability() {
|
|
2126
|
+
return Promise.all([
|
|
2127
|
+
Accelerometer$1.availability,
|
|
2128
|
+
Gyroscope$1.availability,
|
|
2129
|
+
RelativeAttitudeFromInertial$1.availability
|
|
2130
|
+
]);
|
|
2131
|
+
}
|
|
2132
|
+
|
|
2133
|
+
/**
|
|
2134
|
+
* @override
|
|
2135
|
+
*/
|
|
2136
|
+
start() {
|
|
2137
|
+
|
|
2138
|
+
this.numOfSteps = 0;
|
|
2139
|
+
|
|
2140
|
+
this.accelerometerProviderId = Accelerometer$1.addEventListener(
|
|
2141
|
+
events => this.onAccelerometerEvent(events[0]),
|
|
2142
|
+
error => this.notifyError(error)
|
|
2143
|
+
);
|
|
2144
|
+
|
|
2145
|
+
this.gyroscopeProviderId = Gyroscope$1.addEventListener(
|
|
2146
|
+
events => (this.angularRateEvent = events[0]),
|
|
2147
|
+
error => this.notifyError(error)
|
|
2148
|
+
);
|
|
2149
|
+
|
|
2150
|
+
this.attitudeProviderId = RelativeAttitudeFromInertial$1.addEventListener(
|
|
2151
|
+
events => (this.attitudeEvent = events[0]),
|
|
2152
|
+
error => this.notifyError(error)
|
|
2153
|
+
);
|
|
2154
|
+
}
|
|
2155
|
+
|
|
2156
|
+
/**
|
|
2157
|
+
* @override
|
|
2158
|
+
*/
|
|
2159
|
+
stop() {
|
|
2160
|
+
Accelerometer$1.removeEventListener(this.accelerometerProviderId);
|
|
2161
|
+
Gyroscope$1.removeEventListener(this.gyroscopeProviderId);
|
|
2162
|
+
RelativeAttitudeFromInertial$1.removeEventListener(this.attitudeProviderId);
|
|
2163
|
+
}
|
|
2164
|
+
|
|
2165
|
+
onAccelerometerEvent(accelerationEvent) {
|
|
2166
|
+
|
|
2167
|
+
if (!this.attitudeEvent || !this.angularRateEvent) {
|
|
2168
|
+
return;
|
|
2169
|
+
}
|
|
2170
|
+
|
|
2171
|
+
const {
|
|
2172
|
+
values: acceleration, timestamp
|
|
2173
|
+
} = accelerationEvent.data;
|
|
2174
|
+
|
|
2175
|
+
/**
|
|
2176
|
+
* Step Detection and Step Size Detection
|
|
2177
|
+
*/
|
|
2178
|
+
const linearAcc = this.constructor.computeLinearAcceleration(
|
|
2179
|
+
this.attitudeEvent.data.quaternion, acceleration);
|
|
2180
|
+
const stepDetected = this.stepDetector.compute(timestamp, linearAcc, this.angularRateEvent.data.values);
|
|
2181
|
+
|
|
2182
|
+
if (stepDetected) {
|
|
2183
|
+
const size = this.stepDetector.lastStepSize;
|
|
2184
|
+
this.numOfSteps++;
|
|
2185
|
+
this.notify(this.createEvent(
|
|
2186
|
+
EventType.Step, {
|
|
2187
|
+
size,
|
|
2188
|
+
number: this.numOfSteps
|
|
2189
|
+
},
|
|
2190
|
+
[accelerationEvent, this.angularRateEvent, this.attitudeEvent]
|
|
2191
|
+
));
|
|
2192
|
+
}
|
|
2193
|
+
}
|
|
2194
|
+
|
|
2195
|
+
// Linear acceleration in ENU
|
|
2196
|
+
static computeLinearAcceleration(quaternion, acc) {
|
|
2197
|
+
const linearAcc = Quaternion.rotate(Quaternion.inverse(quaternion), acc);
|
|
2198
|
+
linearAcc[2] -= Constants$1.EARTH_GRAVITY;
|
|
2199
|
+
return linearAcc;
|
|
2200
|
+
}
|
|
2201
|
+
}
|
|
2202
|
+
|
|
2203
|
+
var StepDetector$1 = new StepDetector();
|
|
2204
|
+
|
|
2205
|
+
class StraightLineDetector extends Provider {
|
|
2206
|
+
|
|
2207
|
+
/** @type {number} */
|
|
2208
|
+
static STEPS_CONSIDERED_FOR_STRAIGHT_LINE = 2;
|
|
2209
|
+
|
|
2210
|
+
/** @type {number?} */
|
|
2211
|
+
_turnDetectorProviderId = null;
|
|
2212
|
+
|
|
2213
|
+
/** @type {number?} */
|
|
2214
|
+
_stepDetectorProviderId = null;
|
|
2215
|
+
|
|
2216
|
+
/** @type {number} */
|
|
2217
|
+
_countSteps = 0;
|
|
2218
|
+
|
|
2219
|
+
/**
|
|
2220
|
+
* @override
|
|
2221
|
+
*/
|
|
2222
|
+
static get pname() {
|
|
2223
|
+
return 'StraightLineDetector';
|
|
2224
|
+
}
|
|
2225
|
+
|
|
2226
|
+
/**
|
|
2227
|
+
* @override
|
|
2228
|
+
*/
|
|
2229
|
+
start() {
|
|
2230
|
+
this._turnDetectorProviderId = TurnDectector.addEventListener(this._onTurn);
|
|
2231
|
+
this._stepDetectorProviderId = StepDetector$1.addEventListener(this._onStep);
|
|
2232
|
+
}
|
|
2233
|
+
|
|
2234
|
+
/**
|
|
2235
|
+
* @override
|
|
2236
|
+
*/
|
|
2237
|
+
stop() {
|
|
2238
|
+
TurnDectector.removeEventListener(this._turnDetectorProviderId);
|
|
2239
|
+
StepDetector$1.removeEventListener(this._stepDetectorProviderId);
|
|
2240
|
+
}
|
|
2241
|
+
|
|
2242
|
+
_onTurn = (event) => {
|
|
2243
|
+
if (this._countSteps >= StraightLineDetector.STEPS_CONSIDERED_FOR_STRAIGHT_LINE) {
|
|
2244
|
+
|
|
2245
|
+
const fromEvents = [event];
|
|
2246
|
+
if (StepDetector$1.lastEvent !== null) {
|
|
2247
|
+
fromEvents.push(StepDetector$1.lastEvent);
|
|
2248
|
+
}
|
|
2249
|
+
|
|
2250
|
+
this.notify(this.createEvent(EventType.StraightLine, false, fromEvents));
|
|
2251
|
+
|
|
2252
|
+
}
|
|
2253
|
+
this._countSteps = 0;
|
|
2254
|
+
}
|
|
2255
|
+
|
|
2256
|
+
_onStep = (event) => {
|
|
2257
|
+
this._countSteps++;
|
|
2258
|
+
|
|
2259
|
+
if (this._countSteps === StraightLineDetector.STEPS_CONSIDERED_FOR_STRAIGHT_LINE) {
|
|
2260
|
+
|
|
2261
|
+
const fromEvents = [event];
|
|
2262
|
+
if (TurnDectector.lastEvent !== null) {
|
|
2263
|
+
fromEvents.push(TurnDectector.lastEvent);
|
|
2264
|
+
}
|
|
2265
|
+
this.notify(this.createEvent(EventType.StraightLine, true, fromEvents));
|
|
2266
|
+
|
|
2267
|
+
}
|
|
2268
|
+
|
|
2269
|
+
}
|
|
2270
|
+
|
|
2271
|
+
/**
|
|
2272
|
+
* @returns {boolean}
|
|
2273
|
+
*/
|
|
2274
|
+
isStraight() {
|
|
2275
|
+
return this._countSteps >= StraightLineDetector.STEPS_CONSIDERED_FOR_STRAIGHT_LINE;
|
|
2276
|
+
}
|
|
2277
|
+
|
|
2278
|
+
get numStepsDetectedFromLastTurn() {
|
|
2279
|
+
return this._countSteps;
|
|
2280
|
+
}
|
|
2281
|
+
}
|
|
2282
|
+
|
|
2283
|
+
var StraightLineDetector$1 = new StraightLineDetector();
|
|
2284
|
+
|
|
2285
|
+
/* eslint-disable max-statements */
|
|
2286
|
+
|
|
2287
|
+
class MapMatchingHandler extends Provider {
|
|
2288
|
+
|
|
2289
|
+
/** @type {number} in radians */
|
|
2290
|
+
static MM_MAX_ANGLE = deg2rad(30);
|
|
2291
|
+
|
|
2292
|
+
/** @type {number} in meters */
|
|
2293
|
+
static MM_MAX_DIST = 30;
|
|
2294
|
+
|
|
2295
|
+
/** @type {number} in meters */
|
|
2296
|
+
static MM_MIN_DIST = 0;
|
|
2297
|
+
|
|
2298
|
+
/** @type {boolean} */
|
|
2299
|
+
static ORIENTATION_MATCHING = true;
|
|
2300
|
+
|
|
2301
|
+
/** @type {boolean} */
|
|
2302
|
+
static USE_ITINERARY_START_AS_POSITION = true;
|
|
2303
|
+
|
|
2304
|
+
/** @type {number} in meters */
|
|
2305
|
+
static MM_HUGE_JUMP_DISTANCE = 3;
|
|
2306
|
+
|
|
2307
|
+
/** @type {number} */
|
|
2308
|
+
static MIN_STEPS_BETWEEN_ORIENTATION_MATCHING = 3;
|
|
2309
|
+
|
|
2310
|
+
/** @type {number} */
|
|
2311
|
+
static MIN_STEPS_FOR_ORIENTATION_MATCHING = 5;
|
|
2312
|
+
|
|
2313
|
+
/** @type {number} */
|
|
2314
|
+
static LAST_PROJECTIONS_WINDOW_SIZE = 3;
|
|
2315
|
+
|
|
2316
|
+
/** @type {number} */
|
|
2317
|
+
static LAST_PROJECTIONS_EDGE_ANGLE_THRESHOLD = deg2rad(3);
|
|
2318
|
+
|
|
2319
|
+
/** @type {MapMatching} */
|
|
2320
|
+
_mapMatching;
|
|
2321
|
+
|
|
2322
|
+
/** @type {number} */
|
|
2323
|
+
_mapMatchingMinDistance;
|
|
2324
|
+
|
|
2325
|
+
/** @type {boolean} */
|
|
2326
|
+
_internalProvidersStarted = false;
|
|
2327
|
+
|
|
2328
|
+
/** @type {number?} */
|
|
2329
|
+
_straightLineDetectorProviderId;
|
|
2330
|
+
|
|
2331
|
+
/** @type {number?} */
|
|
2332
|
+
_turnDetectorProviderId;
|
|
2333
|
+
|
|
2334
|
+
/** @type {number?} */
|
|
2335
|
+
_stepDetectorProviderId;
|
|
2336
|
+
|
|
2337
|
+
/** @type {GraphProjection[]} */
|
|
2338
|
+
_projectionsWithAbsAndWithoutRelAttitudeInARow = [];
|
|
2339
|
+
|
|
2340
|
+
/** @type {number} */
|
|
2341
|
+
_countStepsFromLastMatching = 0;
|
|
2342
|
+
|
|
2343
|
+
/** @type {GraphProjection[]} */
|
|
2344
|
+
_lastProjections = [];
|
|
2345
|
+
|
|
2346
|
+
|
|
2347
|
+
constructor() {
|
|
2348
|
+
super();
|
|
2349
|
+
|
|
2350
|
+
this._mapMatching = new MapMatching();
|
|
2351
|
+
this._mapMatching.maxDistance = MapMatchingHandler.MM_MAX_DIST;
|
|
2352
|
+
this._mapMatching.maxAngleBearing = MapMatchingHandler.MM_MAX_ANGLE;
|
|
2353
|
+
this._mapMatchingMinDistance = MapMatchingHandler.MM_MIN_DIST;
|
|
2354
|
+
}
|
|
2355
|
+
|
|
2356
|
+
/**
|
|
2357
|
+
* @override
|
|
2358
|
+
*/
|
|
2359
|
+
start() {
|
|
2360
|
+
if (this.network) {
|
|
2361
|
+
this._startInternalProviders();
|
|
2362
|
+
}
|
|
2363
|
+
}
|
|
2364
|
+
|
|
2365
|
+
/**
|
|
2366
|
+
* @override
|
|
2367
|
+
*/
|
|
2368
|
+
stop() {
|
|
2369
|
+
this._stopInternalProviders();
|
|
2370
|
+
}
|
|
2371
|
+
|
|
2372
|
+
_manageStartStop = () => {
|
|
2373
|
+
if (this.network && !this._internalProvidersStarted) {
|
|
2374
|
+
this._startInternalProviders();
|
|
2375
|
+
} else if (!this.network && this._internalProvidersStarted) {
|
|
2376
|
+
this._stopInternalProviders();
|
|
2377
|
+
}
|
|
2378
|
+
}
|
|
2379
|
+
|
|
2380
|
+
_startInternalProviders() {
|
|
2381
|
+
|
|
2382
|
+
if (this.enabled && this._internalProvidersStarted) {
|
|
2383
|
+
return;
|
|
2384
|
+
}
|
|
2385
|
+
|
|
2386
|
+
this._straightLineDetectorProviderId = StraightLineDetector$1.addEventListener();
|
|
2387
|
+
this._turnDetectorProviderId = TurnDectector.addEventListener();
|
|
2388
|
+
this._stepDetectorProviderId = StepDetector$1.addEventListener(() => (this._countStepsFromLastMatching++));
|
|
2389
|
+
|
|
2390
|
+
this._internalProvidersStarted = true;
|
|
2391
|
+
}
|
|
2392
|
+
|
|
2393
|
+
_stopInternalProviders() {
|
|
2394
|
+
|
|
2395
|
+
if (this.enabled && !this._internalProvidersStarted) {
|
|
2396
|
+
return;
|
|
2397
|
+
}
|
|
2398
|
+
|
|
2399
|
+
StraightLineDetector$1.removeEventListener(this._straightLineDetectorProviderId);
|
|
2400
|
+
TurnDectector.removeEventListener(this._turnDetectorProviderId);
|
|
2401
|
+
StepDetector$1.removeEventListener(this._stepDetectorProviderId);
|
|
2402
|
+
|
|
2403
|
+
this._internalProvidersStarted = false;
|
|
2404
|
+
}
|
|
2405
|
+
|
|
2406
|
+
get enabled() {
|
|
2407
|
+
return ProvidersOptions.useMapMatching;
|
|
2408
|
+
}
|
|
2409
|
+
|
|
2410
|
+
/** @type {?Network} */
|
|
2411
|
+
get network() {
|
|
2412
|
+
return this._mapMatching.network;
|
|
2413
|
+
}
|
|
2414
|
+
|
|
2415
|
+
/** @type {?Network} */
|
|
2416
|
+
set network(network) {
|
|
2417
|
+
|
|
2418
|
+
this._mapMatching.network = network;
|
|
2419
|
+
this.notify(this.createEvent(EventType.Network, network));
|
|
2420
|
+
|
|
2421
|
+
this._manageStartStop();
|
|
2422
|
+
|
|
2423
|
+
if (this.canUseMapMatching()) ;
|
|
2424
|
+
}
|
|
2425
|
+
|
|
2426
|
+
/** @type {?Itinerary} */
|
|
2427
|
+
set itinerary(itinerary) {
|
|
2428
|
+
|
|
2429
|
+
this._mapMatching.network = itinerary ? itinerary.toNetwork() : null;
|
|
2430
|
+
this.notify(this.createEvent(EventType.Itinerary, itinerary));
|
|
2431
|
+
|
|
2432
|
+
this._manageStartStop();
|
|
2433
|
+
|
|
2434
|
+
if (this.canUseMapMatching()) {
|
|
2435
|
+
this._notifyPositionFromItineraryInput(itinerary);
|
|
2436
|
+
}
|
|
2437
|
+
}
|
|
2438
|
+
|
|
2439
|
+
/**
|
|
2440
|
+
* @returns {boolean}
|
|
2441
|
+
*/
|
|
2442
|
+
canUseMapMatching() {
|
|
2443
|
+
return this.enabled && this.network;
|
|
2444
|
+
}
|
|
2445
|
+
|
|
2446
|
+
/**
|
|
2447
|
+
* @param {Itinerary} itinerary
|
|
2448
|
+
*/
|
|
2449
|
+
_notifyPositionFromItineraryInput(itinerary) {
|
|
2450
|
+
|
|
2451
|
+
if (!MapMatchingHandler.USE_ITINERARY_START_AS_POSITION || itinerary.start) {
|
|
2452
|
+
return;
|
|
2453
|
+
}
|
|
2454
|
+
|
|
2455
|
+
const lastEvent = AbsolutePosition$1.lastEvent;
|
|
2456
|
+
const lastPosition = lastEvent ? lastEvent.data : null;
|
|
2457
|
+
|
|
2458
|
+
const notify = (pos) => {
|
|
2459
|
+
const newEvent = lastEvent
|
|
2460
|
+
? lastEvent.clone()
|
|
2461
|
+
: new ProviderEvent(EventType.AbsolutePosition);
|
|
2462
|
+
newEvent.data = pos;
|
|
2463
|
+
AbsolutePosition$1.notify(newEvent);
|
|
2464
|
+
};
|
|
2465
|
+
|
|
2466
|
+
// In case of an itinerary, use itinerary start as new position,
|
|
2467
|
+
// but add the distance between lastPosition and itinerary start for the accuracy
|
|
2468
|
+
const newPosition = UserPosition.fromCoordinates(itinerary.from);
|
|
2469
|
+
|
|
2470
|
+
if (lastPosition) {
|
|
2471
|
+
newPosition.alt = lastPosition.alt;
|
|
2472
|
+
newPosition.time = lastPosition.time;
|
|
2473
|
+
newPosition.accuracy = lastPosition.accuracy + newPosition.distanceTo(lastPosition);
|
|
2474
|
+
newPosition.bearing = lastPosition.bearing;
|
|
2475
|
+
} else if (itinerary.coords >= 2) {
|
|
2476
|
+
newPosition.alt = Constants.DEFAULT_ALTITUDE;
|
|
2477
|
+
newPosition.time = TimeUtils.preciseTime();
|
|
2478
|
+
newPosition.accuracy = 0;
|
|
2479
|
+
newPosition.bearing = itinerary.coords[0].bearingTo(itinerary.coords[1]);
|
|
2480
|
+
} else {
|
|
2481
|
+
return;
|
|
2482
|
+
}
|
|
2483
|
+
|
|
2484
|
+
// if the distance between itinerary.start and itinerary.nodes[0] is less than MM_MAX_DIST,
|
|
2485
|
+
// projection.projection is itinerary.nodes[0]
|
|
2486
|
+
const projection = this.getProjection(newPosition, true);
|
|
2487
|
+
if (projection) {
|
|
2488
|
+
notify(projection.projection);
|
|
2489
|
+
// Do not notify for attitude projection because bearing has not been used.
|
|
2490
|
+
} else {
|
|
2491
|
+
// This means the first position is far from the network and the user has
|
|
2492
|
+
// to reach itinerary.nodes[0] before to continue
|
|
2493
|
+
notify(newPosition);
|
|
2494
|
+
}
|
|
2495
|
+
return;
|
|
2496
|
+
}
|
|
2497
|
+
|
|
2498
|
+
/**
|
|
2499
|
+
* @param {ProviderEvent<UserPosition>} newPositionEvent
|
|
2500
|
+
*/
|
|
2501
|
+
notifyPositionFromFeed(newPositionEvent) {
|
|
2502
|
+
|
|
2503
|
+
const projection = this.getProjection(newPositionEvent.data, true, false);
|
|
2504
|
+
if (!projection) {
|
|
2505
|
+
AbsolutePosition$1.notify(newPositionEvent);
|
|
2506
|
+
return;
|
|
2507
|
+
}
|
|
2508
|
+
|
|
2509
|
+
AbsolutePosition$1.notify(this.createEvent(
|
|
2510
|
+
EventType.AbsolutePosition,
|
|
2511
|
+
projection.projection,
|
|
2512
|
+
[newPositionEvent]
|
|
2513
|
+
));
|
|
2514
|
+
}
|
|
2515
|
+
|
|
2516
|
+
|
|
2517
|
+
/**
|
|
2518
|
+
* @param {ProviderEvent<UserPosition>} positionEvent
|
|
2519
|
+
*/
|
|
2520
|
+
notifyPositionFromAbsolute(positionEvent) {
|
|
2521
|
+
|
|
2522
|
+
const newPosition = positionEvent.data;
|
|
2523
|
+
|
|
2524
|
+
let projectionWithBearing = null;
|
|
2525
|
+
if (newPosition.bearing !== null) {
|
|
2526
|
+
projectionWithBearing = this.getProjection(newPosition, true, true);
|
|
2527
|
+
}
|
|
2528
|
+
|
|
2529
|
+
if (!projectionWithBearing) {
|
|
2530
|
+
// Verify if the newPosition is far enough from the network to be used by the system.
|
|
2531
|
+
const projectionWithoutBearing = this.getProjection(newPosition, true, false);
|
|
2532
|
+
if (!projectionWithoutBearing) {
|
|
2533
|
+
// In this case, the newPosition is far enough and can be used safely.
|
|
2534
|
+
AbsolutePosition$1.notify(positionEvent);
|
|
2535
|
+
}
|
|
2536
|
+
return;
|
|
2537
|
+
}
|
|
2538
|
+
|
|
2539
|
+
// newPosition must not be used after this line
|
|
2540
|
+
|
|
2541
|
+
const thisWillBeAHugeJump = projectionWithBearing.distanceFromNearestElement > MapMatchingHandler.MM_HUGE_JUMP_DISTANCE;
|
|
2542
|
+
|
|
2543
|
+
// In case of a huge jump, be sure the user is in a straight line
|
|
2544
|
+
if (thisWillBeAHugeJump && !StraightLineDetector$1.isStraight()) {
|
|
2545
|
+
return;
|
|
2546
|
+
}
|
|
2547
|
+
|
|
2548
|
+
// Detector to avoid big jumps in the wrong direction
|
|
2549
|
+
if (thisWillBeAHugeJump && this._detectWrongBigJump(projectionWithBearing)) {
|
|
2550
|
+
return;
|
|
2551
|
+
}
|
|
2552
|
+
|
|
2553
|
+
AbsolutePosition$1.notify(this.createEvent(
|
|
2554
|
+
EventType.AbsolutePosition,
|
|
2555
|
+
projectionWithBearing.projection,
|
|
2556
|
+
[positionEvent]
|
|
2557
|
+
));
|
|
2558
|
+
|
|
2559
|
+
this.tryOrientationMatching(projectionWithBearing);
|
|
2560
|
+
|
|
2561
|
+
}
|
|
2562
|
+
|
|
2563
|
+
|
|
2564
|
+
/**
|
|
2565
|
+
* @param {ProviderEvent<UserPosition>} positionEvent
|
|
2566
|
+
*/
|
|
2567
|
+
notifyPositionFromRelative(positionEvent) {
|
|
2568
|
+
|
|
2569
|
+
if (TurnDectector.isTurning()) {
|
|
2570
|
+
this._projectionsWithAbsAndWithoutRelAttitudeInARow = [];
|
|
2571
|
+
this._lastProjections = [];
|
|
2572
|
+
AbsolutePosition$1.notify(positionEvent);
|
|
2573
|
+
return;
|
|
2574
|
+
}
|
|
2575
|
+
|
|
2576
|
+
const newPosition = positionEvent.data;
|
|
2577
|
+
const projection = this.getProjection(newPosition, true, true);
|
|
2578
|
+
|
|
2579
|
+
if (projection) {
|
|
2580
|
+
this._projectionsWithAbsAndWithoutRelAttitudeInARow = [];
|
|
2581
|
+
this._lastProjections.push(projection);
|
|
2582
|
+
if (this._lastProjections.length > MapMatchingHandler.LAST_PROJECTIONS_WINDOW_SIZE) {
|
|
2583
|
+
this._lastProjections.shift();
|
|
2584
|
+
}
|
|
2585
|
+
|
|
2586
|
+
const thisWillBeAHugeJump = projection.distanceFromNearestElement > MapMatchingHandler.MM_HUGE_JUMP_DISTANCE;
|
|
2587
|
+
|
|
2588
|
+
// In case of a huge jump, be sure the user is in a straight line
|
|
2589
|
+
if (thisWillBeAHugeJump && !StraightLineDetector$1.isStraight()) {
|
|
2590
|
+
AbsolutePosition$1.notify(positionEvent);
|
|
2591
|
+
return;
|
|
2592
|
+
}
|
|
2593
|
+
|
|
2594
|
+
// Detector to avoid big jumps in the wrong direction
|
|
2595
|
+
if (thisWillBeAHugeJump && this._detectWrongBigJump(projection)) {
|
|
2596
|
+
AbsolutePosition$1.notify(positionEvent);
|
|
2597
|
+
return;
|
|
2598
|
+
}
|
|
2599
|
+
|
|
2600
|
+
// Do not use projection if the neareast element is not the same direction than previous
|
|
2601
|
+
if (!this._areLastProjectionsInTheSameDirection()) {
|
|
2602
|
+
AbsolutePosition$1.notify(positionEvent);
|
|
2603
|
+
return;
|
|
2604
|
+
}
|
|
2605
|
+
|
|
2606
|
+
AbsolutePosition$1.notify(this.createEvent(
|
|
2607
|
+
EventType.AbsolutePosition,
|
|
2608
|
+
projection.projection,
|
|
2609
|
+
[positionEvent]
|
|
2610
|
+
));
|
|
2611
|
+
this.tryOrientationMatching(projection);
|
|
2612
|
+
|
|
2613
|
+
} else {
|
|
2614
|
+
|
|
2615
|
+
// Sometimes, the newPosition.bearing diverges due to the Absolute Attitude offset
|
|
2616
|
+
// and the Orientation Matching. So, we created this detector to check if projection
|
|
2617
|
+
// with bearing from "AbsoluteAttitudeFromBrowser" is better than the current bearing.
|
|
2618
|
+
// /!\ This works only if the user is waking in the same direction than the smartphone orientation /!\
|
|
2619
|
+
if (StraightLineDetector$1.isStraight()) {
|
|
2620
|
+
|
|
2621
|
+
const testedPosition = newPosition.clone();
|
|
2622
|
+
testedPosition.bearing = AbsoluteAttitudeFromBrowser$1.lastEvent.data.heading;
|
|
2623
|
+
const projectionWithAbs = this.getProjection(testedPosition, true, true);
|
|
2624
|
+
|
|
2625
|
+
if (projectionWithAbs) {
|
|
2626
|
+
|
|
2627
|
+
this._projectionsWithAbsAndWithoutRelAttitudeInARow.push(projectionWithAbs);
|
|
2628
|
+
if (this._projectionsWithAbsAndWithoutRelAttitudeInARow.length < 3) {
|
|
2629
|
+
AbsolutePosition$1.notify(positionEvent);
|
|
2630
|
+
return;
|
|
2631
|
+
}
|
|
2632
|
+
|
|
2633
|
+
this._lastProjections.push(projectionWithAbs);
|
|
2634
|
+
if (this._lastProjections.length > MapMatchingHandler.LAST_PROJECTIONS_WINDOW_SIZE) {
|
|
2635
|
+
this._lastProjections.shift();
|
|
2636
|
+
}
|
|
2637
|
+
|
|
2638
|
+
|
|
2639
|
+
if (!this._areLastProjectionsInTheSameDirection()) {
|
|
2640
|
+
AbsolutePosition$1.notify(positionEvent);
|
|
2641
|
+
return;
|
|
2642
|
+
}
|
|
2643
|
+
|
|
2644
|
+
AbsolutePosition$1.notify(this.createEvent(
|
|
2645
|
+
EventType.AbsolutePosition,
|
|
2646
|
+
projectionWithAbs.projection,
|
|
2647
|
+
[positionEvent]
|
|
2648
|
+
));
|
|
2649
|
+
|
|
2650
|
+
this.tryOrientationMatching(projectionWithAbs);
|
|
2651
|
+
return;
|
|
2652
|
+
}
|
|
2653
|
+
}
|
|
2654
|
+
|
|
2655
|
+
this._projectionsWithAbsAndWithoutRelAttitudeInARow = [];
|
|
2656
|
+
this._lastProjections = [];
|
|
2657
|
+
|
|
2658
|
+
// If no projection found with both projection methods, simply use the newPosition.
|
|
2659
|
+
AbsolutePosition$1.notify(positionEvent);
|
|
2660
|
+
}
|
|
2661
|
+
|
|
2662
|
+
}
|
|
2663
|
+
|
|
2664
|
+
/**
|
|
2665
|
+
* @param {Projection} projection
|
|
2666
|
+
*/
|
|
2667
|
+
_detectWrongBigJump(projection) {
|
|
2668
|
+
|
|
2669
|
+
if (this.network instanceof Itinerary && AbsolutePosition$1.lastEvent) {
|
|
2670
|
+
const itinerary = this.network;
|
|
2671
|
+
const infoPrevious = itinerary.getInfo(AbsolutePosition$1.lastEvent.data);
|
|
2672
|
+
const infoProjection = itinerary.getInfo(projection.projection);
|
|
2673
|
+
if (infoPrevious
|
|
2674
|
+
&& infoProjection
|
|
2675
|
+
&& infoPrevious.traveledDistance > infoProjection.traveledDistance
|
|
2676
|
+
&& (infoPrevious.traveledDistance - infoProjection.traveledDistance) > projection.origin.accuracy
|
|
2677
|
+
&& projection.distanceFromNearestElement > projection.origin.accuracy
|
|
2678
|
+
&& projection.origin.distanceTo(AbsolutePosition$1.lastEvent.data) < projection.origin.accuracy + AbsolutePosition$1.lastEvent.data.accuracy) {
|
|
2679
|
+
|
|
2680
|
+
return true;
|
|
2681
|
+
}
|
|
2682
|
+
}
|
|
2683
|
+
|
|
2684
|
+
return false;
|
|
2685
|
+
}
|
|
2686
|
+
|
|
2687
|
+
_areLastProjectionsInTheSameDirection() {
|
|
2688
|
+
|
|
2689
|
+
if (this._lastProjections.length === 0) {
|
|
2690
|
+
return false;
|
|
2691
|
+
}
|
|
2692
|
+
|
|
2693
|
+
const firstProjection = this._lastProjections[0];
|
|
2694
|
+
return !this._lastProjections.some(projection =>
|
|
2695
|
+
!(projection.nearestElement instanceof GraphEdge)
|
|
2696
|
+
|| (diffAngleLines(projection.nearestElement.bearing, firstProjection.nearestElement.bearing)
|
|
2697
|
+
> MapMatchingHandler.LAST_PROJECTIONS_EDGE_ANGLE_THRESHOLD)
|
|
2698
|
+
);
|
|
2699
|
+
}
|
|
2700
|
+
|
|
2701
|
+
/**
|
|
2702
|
+
* @param {Projection} projection
|
|
2703
|
+
*/
|
|
2704
|
+
tryOrientationMatching(projection) {
|
|
2705
|
+
|
|
2706
|
+
if (!MapMatchingHandler.ORIENTATION_MATCHING) {
|
|
2707
|
+
return;
|
|
2708
|
+
}
|
|
2709
|
+
|
|
2710
|
+
if (this.state !== ProviderState.STARTED
|
|
2711
|
+
|| this._countStepsFromLastMatching < MapMatchingHandler.MIN_STEPS_BETWEEN_ORIENTATION_MATCHING
|
|
2712
|
+
|| StraightLineDetector$1.numStepsDetectedFromLastTurn < MapMatchingHandler.MIN_STEPS_FOR_ORIENTATION_MATCHING) {
|
|
2713
|
+
return;
|
|
2714
|
+
}
|
|
2715
|
+
|
|
2716
|
+
const { nearestElement, origin } = projection;
|
|
2717
|
+
if (!(nearestElement instanceof GraphEdge)) {
|
|
2718
|
+
return;
|
|
2719
|
+
}
|
|
2720
|
+
|
|
2721
|
+
let matchingDirection;
|
|
2722
|
+
const matchingDirectionAngle1 = diffAngle(nearestElement.bearing, origin.bearing);
|
|
2723
|
+
const matchingDirectionAngle2 = diffAngle(nearestElement.bearing + Math.PI, origin.bearing);
|
|
2724
|
+
|
|
2725
|
+
if (Math.abs(matchingDirectionAngle1) < Math.abs(matchingDirectionAngle2)) {
|
|
2726
|
+
matchingDirection = nearestElement.bearing;
|
|
2727
|
+
} else {
|
|
2728
|
+
matchingDirection = (nearestElement.bearing + Math.PI) % (2 * Math.PI);
|
|
2729
|
+
}
|
|
2730
|
+
|
|
2731
|
+
const matchedHeading = new AbsoluteHeading(
|
|
2732
|
+
matchingDirection,
|
|
2733
|
+
origin.time,
|
|
2734
|
+
0
|
|
2735
|
+
// Math.min(Math.abs(matchingDirectionAngle1), Math.abs(matchingDirectionAngle2))
|
|
2736
|
+
);
|
|
2737
|
+
|
|
2738
|
+
AbsoluteAttitude$1._forceHeadingForRelative(
|
|
2739
|
+
new ProviderEvent(EventType.AbsoluteHeading, matchedHeading)
|
|
2740
|
+
);
|
|
2741
|
+
|
|
2742
|
+
this._countStepsFromLastMatching = 0;
|
|
2743
|
+
}
|
|
2744
|
+
|
|
2745
|
+
getProjection(position, useDistance, useBearing) {
|
|
2746
|
+
return this._mapMatching.getProjection(position, useDistance, useBearing);
|
|
2747
|
+
}
|
|
2748
|
+
|
|
2749
|
+
get maxDistance() {
|
|
2750
|
+
return this._mapMatching.maxDistance;
|
|
2751
|
+
}
|
|
2752
|
+
|
|
2753
|
+
set maxDistance(maxDistance) {
|
|
2754
|
+
this._mapMatching.maxDistance = maxDistance;
|
|
2755
|
+
}
|
|
2756
|
+
|
|
2757
|
+
get minDistance() {
|
|
2758
|
+
return this._mapMatchingMinDistance;
|
|
2759
|
+
}
|
|
2760
|
+
|
|
2761
|
+
set minDistance(minDistance) {
|
|
2762
|
+
this._mapMatchingMinDistance = minDistance;
|
|
2763
|
+
}
|
|
2764
|
+
|
|
2765
|
+
get maxAngleBearing() {
|
|
2766
|
+
return this._mapMatching.maxAngleBearing;
|
|
2767
|
+
}
|
|
2768
|
+
|
|
2769
|
+
set maxAngleBearing(maxAngleBearing) {
|
|
2770
|
+
this._mapMatching.maxAngleBearing = maxAngleBearing;
|
|
2771
|
+
}
|
|
2772
|
+
}
|
|
2773
|
+
|
|
2774
|
+
var MapMatchingHandler$1 = new MapMatchingHandler();
|
|
2775
|
+
|
|
2776
|
+
class GeolocationApiMissingError extends Error {
|
|
2777
|
+
|
|
2778
|
+
static DEFAULT_MESSAGE = 'Geolocation api is missing';
|
|
2779
|
+
|
|
2780
|
+
constructor(message) {
|
|
2781
|
+
super(message || GeolocationApiMissingError.DEFAULT_MESSAGE);
|
|
2782
|
+
}
|
|
2783
|
+
}
|
|
2784
|
+
|
|
2785
|
+
class GeolocationPermissionDeniedError extends Error {
|
|
2786
|
+
|
|
2787
|
+
static DEFAULT_MESSAGE = 'Geolocation permission denied';
|
|
2788
|
+
|
|
2789
|
+
constructor(message) {
|
|
2790
|
+
super(message || GeolocationPermissionDeniedError.DEFAULT_MESSAGE);
|
|
2791
|
+
}
|
|
2792
|
+
}
|
|
2793
|
+
|
|
2794
|
+
class GeolocationPositionUnavailableError extends Error {
|
|
2795
|
+
|
|
2796
|
+
static DEFAULT_MESSAGE = 'Geolocation position unavailable';
|
|
2797
|
+
|
|
2798
|
+
constructor(message) {
|
|
2799
|
+
super(message || GeolocationPositionUnavailableError.DEFAULT_MESSAGE);
|
|
2800
|
+
}
|
|
2801
|
+
}
|
|
2802
|
+
|
|
2803
|
+
/**
|
|
2804
|
+
* GnssWifiProvider is a provider based on navigator.geolocation.
|
|
2805
|
+
* This API does not allow us to know if the position returned is provided
|
|
2806
|
+
* by Wifi Fingerprinting algorithms or by GNSS. That is why the name is
|
|
2807
|
+
* "GnssWifi".
|
|
2808
|
+
*/
|
|
2809
|
+
class GnssWifi extends Provider {
|
|
2810
|
+
|
|
2811
|
+
static POSITION_OPTIONS = {
|
|
2812
|
+
enableHighAccuracy: true,
|
|
2813
|
+
timeout: Infinity,
|
|
2814
|
+
maximumAge: 0
|
|
2815
|
+
};
|
|
2816
|
+
|
|
2817
|
+
/**
|
|
2818
|
+
* @override
|
|
2819
|
+
*/
|
|
2820
|
+
static get pname() {
|
|
2821
|
+
return 'GnssWifi';
|
|
2822
|
+
}
|
|
2823
|
+
|
|
2824
|
+
/**
|
|
2825
|
+
* @override
|
|
2826
|
+
*/
|
|
2827
|
+
static get eventsType() {
|
|
2828
|
+
return [EventType.AbsolutePosition];
|
|
2829
|
+
}
|
|
2830
|
+
|
|
2831
|
+
/**
|
|
2832
|
+
* @override
|
|
2833
|
+
*/
|
|
2834
|
+
get _availability() {
|
|
2835
|
+
return typeof (navigator) === 'object' && navigator.geolocation
|
|
2836
|
+
? Promise.resolve()
|
|
2837
|
+
: Promise.reject(new GeolocationApiMissingError());
|
|
2838
|
+
}
|
|
2839
|
+
|
|
2840
|
+
/**
|
|
2841
|
+
* @override
|
|
2842
|
+
*/
|
|
2843
|
+
start() {
|
|
2844
|
+
|
|
2845
|
+
this.geoLocationId = navigator.geolocation.watchPosition(
|
|
2846
|
+
this.onNewPosition,
|
|
2847
|
+
this.onPositionError,
|
|
2848
|
+
GnssWifi.POSITION_OPTIONS
|
|
2849
|
+
);
|
|
2850
|
+
|
|
2851
|
+
}
|
|
2852
|
+
|
|
2853
|
+
/**
|
|
2854
|
+
* @override
|
|
2855
|
+
*/
|
|
2856
|
+
stop() {
|
|
2857
|
+
|
|
2858
|
+
navigator.geolocation.clearWatch(this.geoLocationId);
|
|
2859
|
+
|
|
2860
|
+
}
|
|
2861
|
+
|
|
2862
|
+
/**
|
|
2863
|
+
* @private
|
|
2864
|
+
*/
|
|
2865
|
+
onNewPosition = geolocation => {
|
|
2866
|
+
|
|
2867
|
+
const { coords } = geolocation;
|
|
2868
|
+
if (!coords) {
|
|
2869
|
+
return;
|
|
2870
|
+
}
|
|
2871
|
+
|
|
2872
|
+
let bearing;
|
|
2873
|
+
if (coords.heading) {
|
|
2874
|
+
bearing = deg2rad(coords.heading);
|
|
2875
|
+
}
|
|
2876
|
+
|
|
2877
|
+
const timestamp = TimeUtils.unixTimestampToPreciseTime(geolocation.timestamp) / 1e3;
|
|
2878
|
+
|
|
2879
|
+
const position = new UserPosition(
|
|
2880
|
+
coords.latitude,
|
|
2881
|
+
coords.longitude,
|
|
2882
|
+
Constants.DEFAULT_ALTITUDE,
|
|
2883
|
+
null,
|
|
2884
|
+
timestamp,
|
|
2885
|
+
coords.accuracy,
|
|
2886
|
+
bearing,
|
|
2887
|
+
this.pname);
|
|
2888
|
+
|
|
2889
|
+
this.notify(this.createEvent(
|
|
2890
|
+
EventType.AbsolutePosition, position
|
|
2891
|
+
));
|
|
2892
|
+
|
|
2893
|
+
};
|
|
2894
|
+
|
|
2895
|
+
onPositionError = error => {
|
|
2896
|
+
|
|
2897
|
+
Logger.warn(`[Providers] watchPosition error: [${error.code}] ${error.message}`);
|
|
2898
|
+
|
|
2899
|
+
let customError;
|
|
2900
|
+
switch (error.code) {
|
|
2901
|
+
case 1:
|
|
2902
|
+
customError = new GeolocationPermissionDeniedError(error.message);
|
|
2903
|
+
break;
|
|
2904
|
+
case 2:
|
|
2905
|
+
customError = new GeolocationPositionUnavailableError(error.message);
|
|
2906
|
+
break;
|
|
2907
|
+
default:
|
|
2908
|
+
customError = new Error(error.message);
|
|
2909
|
+
}
|
|
2910
|
+
|
|
2911
|
+
this.notifyError(customError);
|
|
2912
|
+
};
|
|
2913
|
+
}
|
|
2914
|
+
|
|
2915
|
+
var GnssWifi$1 = new GnssWifi();
|
|
2916
|
+
|
|
2917
|
+
class GeoRelativePositionFromArCore extends Provider {
|
|
2918
|
+
|
|
2919
|
+
/**
|
|
2920
|
+
* @override
|
|
2921
|
+
*/
|
|
2922
|
+
static get pname() {
|
|
2923
|
+
return 'GeoRelativePositionFromArCore';
|
|
2924
|
+
}
|
|
2925
|
+
|
|
2926
|
+
/**
|
|
2927
|
+
* @override
|
|
2928
|
+
*/
|
|
2929
|
+
get _availability() {
|
|
2930
|
+
return ArCore$1.availability;
|
|
2931
|
+
}
|
|
2932
|
+
|
|
2933
|
+
/**
|
|
2934
|
+
* @override
|
|
2935
|
+
*/
|
|
2936
|
+
start() {
|
|
2937
|
+
|
|
2938
|
+
this.arCoreProviderId = ArCore$1.addEventListener(
|
|
2939
|
+
this.onArCoreEvents,
|
|
2940
|
+
error => this.notifyError(error)
|
|
2941
|
+
);
|
|
2942
|
+
|
|
2943
|
+
this.absoluteAttitudeProviderId = AbsoluteAttitude$1.addEventListener(
|
|
2944
|
+
events => (this.absoluteAttitudeEvent = events[0]),
|
|
2945
|
+
error => this.notifyError(error)
|
|
2946
|
+
);
|
|
2947
|
+
}
|
|
2948
|
+
|
|
2949
|
+
/**
|
|
2950
|
+
* @override
|
|
2951
|
+
*/
|
|
2952
|
+
stop() {
|
|
2953
|
+
ArCore$1.removeEventListener(this.arCoreProviderId);
|
|
2954
|
+
AbsoluteAttitude$1.removeEventListener(this.absoluteAttitudeProviderId);
|
|
2955
|
+
}
|
|
2956
|
+
|
|
2957
|
+
onArCoreEvents = events => {
|
|
2958
|
+
const relativeAttitudeEvent = events.find(event => event.dataType === EventType.RelativeAttitude);
|
|
2959
|
+
const relativePositionEvent = events.find(event => event.dataType === EventType.RelativePosition);
|
|
2960
|
+
|
|
2961
|
+
if (relativeAttitudeEvent && relativePositionEvent && this.absoluteAttitudeEvent) {
|
|
2962
|
+
this.compute(relativePositionEvent, relativeAttitudeEvent, this.absoluteAttitudeEvent);
|
|
2963
|
+
}
|
|
2964
|
+
}
|
|
2965
|
+
|
|
2966
|
+
compute(relativePositionEvent, relativeAttitudeEvent, absoluteAttitudeEvent) {
|
|
2967
|
+
const relativePosition = relativePositionEvent.data;
|
|
2968
|
+
const relativeAttitude = relativeAttitudeEvent.data;
|
|
2969
|
+
const absoluteAttitude = absoluteAttitudeEvent.data;
|
|
2970
|
+
|
|
2971
|
+
const rotation = absoluteAttitude.heading - relativeAttitude.heading;
|
|
2972
|
+
|
|
2973
|
+
/**
|
|
2974
|
+
* Here, we transform "relativePosition" which is defined in ArCore frame to a relative "position" defined in ENU frame
|
|
2975
|
+
*/
|
|
2976
|
+
const east = Math.cos(rotation) * relativePosition.x - Math.sin(rotation) * relativePosition.z;
|
|
2977
|
+
const north = -Math.sin(rotation) * relativePosition.x - Math.cos(rotation) * relativePosition.z;
|
|
2978
|
+
const up = relativePosition.y;
|
|
2979
|
+
|
|
2980
|
+
/**
|
|
2981
|
+
* Relative position is defined in ENU frame
|
|
2982
|
+
*/
|
|
2983
|
+
const position = new GeoRelativePosition$1(
|
|
2984
|
+
east,
|
|
2985
|
+
north,
|
|
2986
|
+
up,
|
|
2987
|
+
relativePosition.time,
|
|
2988
|
+
0,
|
|
2989
|
+
absoluteAttitude.heading
|
|
2990
|
+
);
|
|
2991
|
+
|
|
2992
|
+
this.notify(this.createEvent(
|
|
2993
|
+
EventType.GeoRelativePosition,
|
|
2994
|
+
position,
|
|
2995
|
+
[relativePositionEvent, relativeAttitudeEvent, absoluteAttitudeEvent]
|
|
2996
|
+
));
|
|
2997
|
+
}
|
|
2998
|
+
}
|
|
2999
|
+
|
|
3000
|
+
var GeoRelativePositionFromArCore$1 = new GeoRelativePositionFromArCore();
|
|
3001
|
+
|
|
3002
|
+
class Pdr extends Provider {
|
|
3003
|
+
|
|
3004
|
+
// https://ieeexplore.ieee.org/document/7346766
|
|
3005
|
+
misalignment = [1, 0, 0, 0];
|
|
3006
|
+
misalignmentError = deg2rad(3);
|
|
3007
|
+
|
|
3008
|
+
/**
|
|
3009
|
+
* @override
|
|
3010
|
+
*/
|
|
3011
|
+
static get pname() {
|
|
3012
|
+
return 'PDR';
|
|
3013
|
+
}
|
|
3014
|
+
|
|
3015
|
+
/**
|
|
3016
|
+
* @override
|
|
3017
|
+
*/
|
|
3018
|
+
static get eventsType() {
|
|
3019
|
+
return [EventType.RelativePosition];
|
|
3020
|
+
}
|
|
3021
|
+
|
|
3022
|
+
/**
|
|
3023
|
+
* @override
|
|
3024
|
+
*/
|
|
3025
|
+
get _availability() {
|
|
3026
|
+
return Promise.all([
|
|
3027
|
+
StepDetector$1.availability,
|
|
3028
|
+
AbsoluteAttitude$1.availability
|
|
3029
|
+
]);
|
|
3030
|
+
}
|
|
3031
|
+
|
|
3032
|
+
|
|
3033
|
+
/**
|
|
3034
|
+
* @override
|
|
3035
|
+
*/
|
|
3036
|
+
start() {
|
|
3037
|
+
|
|
3038
|
+
this.stepDetectionProviderId = StepDetector$1.addEventListener(
|
|
3039
|
+
events => this.onStepEvent(events[0]),
|
|
3040
|
+
error => this.notifyError(error)
|
|
3041
|
+
);
|
|
3042
|
+
|
|
3043
|
+
this.absoluteAttitudeProviderId = AbsoluteAttitude$1.addEventListener(
|
|
3044
|
+
events => (this.attitudeEvent = events[0]),
|
|
3045
|
+
error => this.notifyError(error)
|
|
3046
|
+
);
|
|
3047
|
+
|
|
3048
|
+
}
|
|
3049
|
+
|
|
3050
|
+
/**
|
|
3051
|
+
* @override
|
|
3052
|
+
*/
|
|
3053
|
+
stop() {
|
|
3054
|
+
StepDetector$1.removeEventListener(this.stepDetectionProviderId);
|
|
3055
|
+
AbsoluteAttitude$1.removeEventListener(this.absoluteAttitudeProviderId);
|
|
3056
|
+
}
|
|
3057
|
+
|
|
3058
|
+
/**
|
|
3059
|
+
* @private
|
|
3060
|
+
*/
|
|
3061
|
+
onStepEvent = stepEvent => {
|
|
3062
|
+
|
|
3063
|
+
if (!this.attitudeEvent) {
|
|
3064
|
+
return;
|
|
3065
|
+
}
|
|
3066
|
+
|
|
3067
|
+
const stepSize = stepEvent.data.size;
|
|
3068
|
+
|
|
3069
|
+
/**
|
|
3070
|
+
* There is three frames because (device heading != walking direction):
|
|
3071
|
+
*
|
|
3072
|
+
* Device frame (x-Device Right, y-Device Top, z-Device Front)
|
|
3073
|
+
* Navigation frame (x-Nav East, y-Nav North, z-Nav Up)
|
|
3074
|
+
* Earth local frame (x-East y-North z-Up)
|
|
3075
|
+
*
|
|
3076
|
+
*
|
|
3077
|
+
* For the PDR, in order to find the step direction,
|
|
3078
|
+
* we are looking for the rotation between the device frame and the navigation frame.
|
|
3079
|
+
* Attitude is the rotation from the device frame to the earth local frame.
|
|
3080
|
+
* Misalignment is the rotation from the navigation frame to the earth local frame
|
|
3081
|
+
*/
|
|
3082
|
+
const deviceAttitude = this.attitudeEvent.data;
|
|
3083
|
+
|
|
3084
|
+
/**
|
|
3085
|
+
* For optimisation, as we define misalignment to identity, we do not process the quat mulitply formula.
|
|
3086
|
+
*/
|
|
3087
|
+
// const deviceInNavFrame = Quaternion.multiply(deviceAttitude.quaternion, this.misalignment);
|
|
3088
|
+
// const deviceDirection = new Attitude(deviceInNavFrame).heading;
|
|
3089
|
+
const deviceDirection = deviceAttitude.heading;
|
|
3090
|
+
|
|
3091
|
+
/**
|
|
3092
|
+
* A bad accuracy from PDR is due to three things:
|
|
3093
|
+
* - Attitude accuracy
|
|
3094
|
+
* - Misalignement (device heading != walking direction)
|
|
3095
|
+
* - Step detection false positives / false negatives
|
|
3096
|
+
* Following formula only use attitude accuracy with cone formula
|
|
3097
|
+
*/
|
|
3098
|
+
const deviceDirectionAccuracy = deviceAttitude.accuracy + this.misalignmentError;
|
|
3099
|
+
const accuracy = (stepSize / 2) * Math.sin(deviceDirectionAccuracy / 2);
|
|
3100
|
+
const timestamp = deviceAttitude.time;
|
|
3101
|
+
|
|
3102
|
+
/**
|
|
3103
|
+
* Relative position is defined in ENU frame
|
|
3104
|
+
*/
|
|
3105
|
+
const position = new GeoRelativePosition$1(
|
|
3106
|
+
stepSize * Math.sin(deviceDirection),
|
|
3107
|
+
stepSize * Math.cos(deviceDirection),
|
|
3108
|
+
0,
|
|
3109
|
+
timestamp,
|
|
3110
|
+
accuracy,
|
|
3111
|
+
deviceDirection
|
|
3112
|
+
);
|
|
3113
|
+
|
|
3114
|
+
this.notify(this.createEvent(
|
|
3115
|
+
EventType.GeoRelativePosition,
|
|
3116
|
+
position,
|
|
3117
|
+
[stepEvent, this.attitudeEvent]
|
|
3118
|
+
));
|
|
3119
|
+
}
|
|
3120
|
+
|
|
3121
|
+
}
|
|
3122
|
+
|
|
3123
|
+
var Pdr$1 = new Pdr();
|
|
3124
|
+
|
|
3125
|
+
class GeoRelativePosition extends Provider {
|
|
3126
|
+
|
|
3127
|
+
/**
|
|
3128
|
+
* @override
|
|
3129
|
+
*/
|
|
3130
|
+
static get pname() {
|
|
3131
|
+
return 'GeoRelativePosition';
|
|
3132
|
+
}
|
|
3133
|
+
|
|
3134
|
+
/**
|
|
3135
|
+
* @override
|
|
3136
|
+
*/
|
|
3137
|
+
static get eventsType() {
|
|
3138
|
+
return [EventType.GeoRelativePosition];
|
|
3139
|
+
}
|
|
3140
|
+
|
|
3141
|
+
/**
|
|
3142
|
+
* @override
|
|
3143
|
+
*/
|
|
3144
|
+
get _availability() {
|
|
3145
|
+
return PromiseUtils.any([
|
|
3146
|
+
Pdr$1.availability,
|
|
3147
|
+
GeoRelativePositionFromArCore$1.availability
|
|
3148
|
+
]);
|
|
3149
|
+
}
|
|
3150
|
+
|
|
3151
|
+
|
|
3152
|
+
/**
|
|
3153
|
+
* @override
|
|
3154
|
+
*/
|
|
3155
|
+
start() {
|
|
3156
|
+
|
|
3157
|
+
GeoRelativePositionFromArCore$1.availability
|
|
3158
|
+
.then(() => (this.geoRelativeProvider = GeoRelativePositionFromArCore$1))
|
|
3159
|
+
.catch(() => (this.geoRelativeProvider = Pdr$1))
|
|
3160
|
+
.finally(() => {
|
|
3161
|
+
this.providerId = this.geoRelativeProvider.addEventListener(
|
|
3162
|
+
events => {
|
|
3163
|
+
const event = events.find(_event => _event.dataType === EventType.GeoRelativePosition);
|
|
3164
|
+
if (event) {
|
|
3165
|
+
this.notify(event.clone());
|
|
3166
|
+
}
|
|
3167
|
+
},
|
|
3168
|
+
error => this.notifyError(error)
|
|
3169
|
+
);
|
|
3170
|
+
});
|
|
3171
|
+
}
|
|
3172
|
+
|
|
3173
|
+
/**
|
|
3174
|
+
* @override
|
|
3175
|
+
*/
|
|
3176
|
+
stop() {
|
|
3177
|
+
if (this.geoRelativeProvider) {
|
|
3178
|
+
this.geoRelativeProvider.removeEventListener(this.providerId);
|
|
3179
|
+
this.geoRelativeProvider = null;
|
|
3180
|
+
}
|
|
3181
|
+
}
|
|
3182
|
+
}
|
|
3183
|
+
|
|
3184
|
+
var GeoRelativePositionProvider = new GeoRelativePosition();
|
|
3185
|
+
|
|
3186
|
+
class AbsolutePosition extends Provider {
|
|
3187
|
+
|
|
3188
|
+
// Use the new absolute position if its accuracy is at least x times better than the last one.
|
|
3189
|
+
static ACCURACY_RELOC_RATIO = 1.5;
|
|
3190
|
+
|
|
3191
|
+
/** @type {boolean} */
|
|
3192
|
+
static USE_MM_FOR_FEED = true;
|
|
3193
|
+
|
|
3194
|
+
|
|
3195
|
+
/** @type {number?} */
|
|
3196
|
+
_gnssWifiProviderId;
|
|
3197
|
+
|
|
3198
|
+
/** @type {number?} */
|
|
3199
|
+
_relativePositionProviderId;
|
|
3200
|
+
|
|
3201
|
+
/** @type {number?} */
|
|
3202
|
+
_mapMatchingHandlerId
|
|
3203
|
+
|
|
3204
|
+
/**
|
|
3205
|
+
* @override
|
|
3206
|
+
*/
|
|
3207
|
+
static get pname() {
|
|
3208
|
+
return 'AbsolutePosition';
|
|
3209
|
+
}
|
|
3210
|
+
|
|
3211
|
+
/**
|
|
3212
|
+
* @override
|
|
3213
|
+
*/
|
|
3214
|
+
static get eventsType() {
|
|
3215
|
+
return [EventType.AbsolutePosition];
|
|
3216
|
+
}
|
|
3217
|
+
|
|
3218
|
+
/**
|
|
3219
|
+
* @override
|
|
3220
|
+
*/
|
|
3221
|
+
get _availability() {
|
|
3222
|
+
return PromiseUtils.any([
|
|
3223
|
+
GeoRelativePositionProvider.availability,
|
|
3224
|
+
GnssWifi$1.availability
|
|
3225
|
+
]);
|
|
3226
|
+
}
|
|
3227
|
+
|
|
3228
|
+
|
|
3229
|
+
/**
|
|
3230
|
+
* @override
|
|
3231
|
+
*/
|
|
3232
|
+
start() {
|
|
3233
|
+
GeoRelativePositionProvider.availability
|
|
3234
|
+
.then(() => {
|
|
3235
|
+
this._relativePositionProviderId = GeoRelativePositionProvider.addEventListener(
|
|
3236
|
+
events => this._onRelativePosition(events[0])
|
|
3237
|
+
);
|
|
3238
|
+
})
|
|
3239
|
+
.catch(() => {
|
|
3240
|
+
// do nothing
|
|
3241
|
+
});
|
|
3242
|
+
|
|
3243
|
+
this._gnssWifiProviderId = GnssWifi$1.addEventListener(
|
|
3244
|
+
events => {
|
|
3245
|
+
// bearing from GnssWifi is not reliable for our usecase
|
|
3246
|
+
events[0].data.bearing = null;
|
|
3247
|
+
this._onAbsolutePosition(events[0], false);
|
|
3248
|
+
}
|
|
3249
|
+
);
|
|
3250
|
+
|
|
3251
|
+
this._mapMatchingHandlerId = MapMatchingHandler$1.addEventListener();
|
|
3252
|
+
}
|
|
3253
|
+
|
|
3254
|
+
|
|
3255
|
+
/**
|
|
3256
|
+
* @override
|
|
3257
|
+
*/
|
|
3258
|
+
stop() {
|
|
3259
|
+
if (this._relativePositionProviderId) {
|
|
3260
|
+
GeoRelativePositionProvider.removeEventListener(this._relativePositionProviderId);
|
|
3261
|
+
}
|
|
3262
|
+
GnssWifi$1.removeEventListener(this._gnssWifiProviderId);
|
|
3263
|
+
|
|
3264
|
+
MapMatchingHandler$1.removeEventListener(this._mapMatchingHandlerId);
|
|
3265
|
+
}
|
|
3266
|
+
|
|
3267
|
+
/**
|
|
3268
|
+
* @param {ProviderEvent<UserPosition>} positionEvent
|
|
3269
|
+
* @param {boolean} canContainLevel
|
|
3270
|
+
*/
|
|
3271
|
+
_onAbsolutePosition(positionEvent, canContainLevel = true) {
|
|
3272
|
+
|
|
3273
|
+
const newPosition = positionEvent.data.clone();
|
|
3274
|
+
const lastPosition = this.lastEvent ? this.lastEvent.data : null;
|
|
3275
|
+
|
|
3276
|
+
if (lastPosition) {
|
|
3277
|
+
|
|
3278
|
+
// Is the new position accuracy is better enough than the last position accuracy
|
|
3279
|
+
const isBetterEnough = newPosition.accuracy * AbsolutePosition.ACCURACY_RELOC_RATIO < lastPosition.accuracy;
|
|
3280
|
+
|
|
3281
|
+
// Is the new position is far from the new one (regarding accuracy)
|
|
3282
|
+
// This is important if the person put the current page in the background during a while
|
|
3283
|
+
const isFarEnough = lastPosition.distanceTo(newPosition) > lastPosition.accuracy + newPosition.accuracy;
|
|
3284
|
+
|
|
3285
|
+
if (!isBetterEnough && !isFarEnough) {
|
|
3286
|
+
return;
|
|
3287
|
+
}
|
|
3288
|
+
|
|
3289
|
+
if (!canContainLevel) {
|
|
3290
|
+
newPosition.level = lastPosition.level;
|
|
3291
|
+
}
|
|
3292
|
+
|
|
3293
|
+
// If the new position does not have a bearing, retrieve the bearing from the last position
|
|
3294
|
+
if (newPosition.bearing === null) {
|
|
3295
|
+
newPosition.bearing = lastPosition.bearing;
|
|
3296
|
+
}
|
|
3297
|
+
}
|
|
3298
|
+
|
|
3299
|
+
|
|
3300
|
+
const newPositionEvent = this.createEvent(
|
|
3301
|
+
EventType.AbsolutePosition,
|
|
3302
|
+
newPosition,
|
|
3303
|
+
[positionEvent]
|
|
3304
|
+
);
|
|
3305
|
+
|
|
3306
|
+
// If the MM is disable or the network is not set yet, use the new position as it is.
|
|
3307
|
+
// If the position bearing is null, do not use MM, it is too dangerous.
|
|
3308
|
+
if (!MapMatchingHandler$1.canUseMapMatching()) {
|
|
3309
|
+
this.notify(newPositionEvent);
|
|
3310
|
+
return;
|
|
3311
|
+
}
|
|
3312
|
+
|
|
3313
|
+
MapMatchingHandler$1.notifyPositionFromAbsolute(newPositionEvent);
|
|
3314
|
+
}
|
|
3315
|
+
|
|
3316
|
+
/**
|
|
3317
|
+
* @param {ProviderEvent<GeoRelativePosition>} relativeEvent
|
|
3318
|
+
*/
|
|
3319
|
+
_onRelativePosition(relativeEvent) {
|
|
3320
|
+
|
|
3321
|
+
if (!this.lastEvent) {
|
|
3322
|
+
return;
|
|
3323
|
+
}
|
|
3324
|
+
|
|
3325
|
+
const lastPosition = this.lastEvent.data;
|
|
3326
|
+
|
|
3327
|
+
const offsetPos = relativeEvent.data;
|
|
3328
|
+
|
|
3329
|
+
const dist = Math.sqrt(offsetPos.x ** 2 + offsetPos.y ** 2);
|
|
3330
|
+
const bearing = Math.atan2(offsetPos.x, offsetPos.y);
|
|
3331
|
+
const alt = lastPosition.alt !== null ? offsetPos.z : null;
|
|
3332
|
+
|
|
3333
|
+
const newPosition = lastPosition.destinationPoint(dist, bearing, alt);
|
|
3334
|
+
newPosition.bearing = offsetPos.bearing;
|
|
3335
|
+
newPosition.time = offsetPos.time;
|
|
3336
|
+
newPosition.accuracy += offsetPos.accuracy;
|
|
3337
|
+
|
|
3338
|
+
|
|
3339
|
+
const newPositionEvent = this.createEvent(
|
|
3340
|
+
EventType.AbsolutePosition,
|
|
3341
|
+
newPosition,
|
|
3342
|
+
[relativeEvent]
|
|
3343
|
+
);
|
|
3344
|
+
|
|
3345
|
+
|
|
3346
|
+
if (!MapMatchingHandler$1.canUseMapMatching()) {
|
|
3347
|
+
this.notify(newPositionEvent);
|
|
3348
|
+
return;
|
|
3349
|
+
}
|
|
3350
|
+
|
|
3351
|
+
MapMatchingHandler$1.notifyPositionFromRelative(newPositionEvent);
|
|
3352
|
+
}
|
|
3353
|
+
|
|
3354
|
+
|
|
3355
|
+
/**
|
|
3356
|
+
* @override
|
|
3357
|
+
* @param {UserPosition|ProviderEvent} data
|
|
3358
|
+
*/
|
|
3359
|
+
feed(data) {
|
|
3360
|
+
|
|
3361
|
+
/** @type {ProviderEvent<UserPosition>} */
|
|
3362
|
+
let newPositionEvent;
|
|
3363
|
+
|
|
3364
|
+
if (data instanceof UserPosition) {
|
|
3365
|
+
|
|
3366
|
+
if (data.time === null) {
|
|
3367
|
+
throw Error('the time of the position is not defined');
|
|
3368
|
+
}
|
|
3369
|
+
if (data.accuracy === null) {
|
|
3370
|
+
throw Error('the accuracy of the position is not defined');
|
|
3371
|
+
}
|
|
3372
|
+
|
|
3373
|
+
newPositionEvent = new ProviderEvent(EventType.AbsolutePosition, data);
|
|
3374
|
+
|
|
3375
|
+
} else if (data instanceof ProviderEvent) {
|
|
3376
|
+
|
|
3377
|
+
if (data.dataType !== EventType.AbsolutePosition
|
|
3378
|
+
|| !(data.data instanceof UserPosition)) {
|
|
3379
|
+
throw Error('the provider event is not an UserPosition');
|
|
3380
|
+
}
|
|
3381
|
+
|
|
3382
|
+
newPositionEvent = data;
|
|
3383
|
+
|
|
3384
|
+
} else {
|
|
3385
|
+
throw new Error('data is nor an UserPosition or a ProviderEvent');
|
|
3386
|
+
}
|
|
3387
|
+
|
|
3388
|
+
// If the MM is disable or the network is not set yet, use the new position as it is.
|
|
3389
|
+
if (!AbsolutePosition.USE_MM_FOR_FEED || !MapMatchingHandler$1.canUseMapMatching()) {
|
|
3390
|
+
this.notify(newPositionEvent);
|
|
3391
|
+
return;
|
|
3392
|
+
}
|
|
3393
|
+
|
|
3394
|
+
MapMatchingHandler$1.notifyPositionFromFeed(newPositionEvent);
|
|
3395
|
+
}
|
|
3396
|
+
}
|
|
3397
|
+
|
|
3398
|
+
var AbsolutePosition$1 = new AbsolutePosition();
|
|
3399
|
+
|
|
3400
|
+
/**
|
|
3401
|
+
* Absolute attitude provider gives the device attitude in East-North-Up (ENU) frame using
|
|
3402
|
+
* browser deviceorientation or deviceorientationabsolute
|
|
3403
|
+
* The provider does not work until an AbsolutePosition is given. This is necessary to
|
|
3404
|
+
* calculate declination.
|
|
3405
|
+
*
|
|
3406
|
+
* -----------------------------------
|
|
3407
|
+
* Overview of compatibilities:
|
|
3408
|
+
* -----------------------------------
|
|
3409
|
+
*
|
|
3410
|
+
* Chrome Android (v72.0.3626): YES (via deviceorientationabsolute)
|
|
3411
|
+
* Safari iOS (v12.0): YES (via deviceorientation and event.webkitCompassHeading)
|
|
3412
|
+
* Opera Android (v50.2.2426): NO {@link https://developer.mozilla.org/en-US/docs/Web/API/Window/ondeviceorientation}
|
|
3413
|
+
* Firefox Android (v65.0.1): NO {@link https://www.fxsitecompat.com/en-CA/docs/2018/various-device-sensor-apis-are-now-deprecated/}
|
|
3414
|
+
*
|
|
3415
|
+
* -----------------------------------
|
|
3416
|
+
*/
|
|
3417
|
+
class AbsoluteAttitudeFromBrowser extends Provider {
|
|
3418
|
+
|
|
3419
|
+
// from http://tyrex.inria.fr/mobile/benchmarks-attitude/
|
|
3420
|
+
static DEFAULT_ACCURACY = deg2rad(15);
|
|
3421
|
+
|
|
3422
|
+
absolutePositionProviderId = null;
|
|
3423
|
+
|
|
3424
|
+
/**
|
|
3425
|
+
* @override
|
|
3426
|
+
*/
|
|
3427
|
+
static get pname() {
|
|
3428
|
+
return 'AbsoluteAttitudeFromBrowser';
|
|
3429
|
+
}
|
|
3430
|
+
|
|
3431
|
+
/**
|
|
3432
|
+
* @override
|
|
3433
|
+
*/
|
|
3434
|
+
static get eventsType() {
|
|
3435
|
+
return [EventType.AbsoluteAttitude];
|
|
3436
|
+
}
|
|
3437
|
+
|
|
3438
|
+
/**
|
|
3439
|
+
* @override
|
|
3440
|
+
*/
|
|
3441
|
+
get _availability() {
|
|
3442
|
+
return BrowserUtils.isMobile
|
|
3443
|
+
? Promise.resolve()
|
|
3444
|
+
: Promise.reject(new AskImuOnDesktopError());
|
|
3445
|
+
}
|
|
3446
|
+
|
|
3447
|
+
/**
|
|
3448
|
+
* @override
|
|
3449
|
+
*/
|
|
3450
|
+
start() {
|
|
3451
|
+
|
|
3452
|
+
const subscribe = () => {
|
|
3453
|
+
switch (BrowserUtils.name) {
|
|
3454
|
+
case Browser.CHROME:
|
|
3455
|
+
window.addEventListener('deviceorientationabsolute',
|
|
3456
|
+
this.onDeviceOrientationChromeEvent, true);
|
|
3457
|
+
break;
|
|
3458
|
+
|
|
3459
|
+
case Browser.SAFARI:
|
|
3460
|
+
case Browser.IOS_WEBVIEW:
|
|
3461
|
+
window.addEventListener('deviceorientation',
|
|
3462
|
+
this.onDeviceOrientationSafariEvent, true);
|
|
3463
|
+
break;
|
|
3464
|
+
}
|
|
3465
|
+
};
|
|
3466
|
+
|
|
3467
|
+
if (typeof (DeviceOrientationEvent) !== 'undefined' && typeof (DeviceOrientationEvent.requestPermission) === 'function') {
|
|
3468
|
+
DeviceOrientationEvent
|
|
3469
|
+
.requestPermission()
|
|
3470
|
+
.then(response => {
|
|
3471
|
+
if (response !== 'granted') {
|
|
3472
|
+
throw new Error('Permission not granted');
|
|
3473
|
+
}
|
|
3474
|
+
subscribe();
|
|
3475
|
+
})
|
|
3476
|
+
.catch(error => this.notifyError(error));
|
|
3477
|
+
} else {
|
|
3478
|
+
subscribe();
|
|
3479
|
+
}
|
|
3480
|
+
|
|
3481
|
+
|
|
3482
|
+
const lastAbsolutePosition = AbsolutePosition$1.lastEvent;
|
|
3483
|
+
if (lastAbsolutePosition) {
|
|
3484
|
+
this.onAbsolutePositionEvent(lastAbsolutePosition);
|
|
3485
|
+
} else {
|
|
3486
|
+
this.absolutePositionProviderId = AbsolutePosition$1.addEventListener(
|
|
3487
|
+
events => this.onAbsolutePositionEvent(events[0]),
|
|
3488
|
+
error => this.notifyError(error),
|
|
3489
|
+
false
|
|
3490
|
+
);
|
|
3491
|
+
}
|
|
3492
|
+
|
|
3493
|
+
}
|
|
3494
|
+
|
|
3495
|
+
/**
|
|
3496
|
+
* @override
|
|
3497
|
+
*/
|
|
3498
|
+
stop() {
|
|
3499
|
+
switch (BrowserUtils.name) {
|
|
3500
|
+
case Browser.CHROME:
|
|
3501
|
+
window.removeEventListener('deviceorientationabsolute',
|
|
3502
|
+
this.onDeviceOrientationChromeEvent, true);
|
|
3503
|
+
break;
|
|
3504
|
+
|
|
3505
|
+
case Browser.SAFARI:
|
|
3506
|
+
case Browser.IOS_WEBVIEW:
|
|
3507
|
+
window.removeEventListener('deviceorientation',
|
|
3508
|
+
this.onDeviceOrientationSafariEvent, true);
|
|
3509
|
+
break;
|
|
3510
|
+
}
|
|
3511
|
+
|
|
3512
|
+
if (this.absolutePositionProviderId !== null) {
|
|
3513
|
+
AbsolutePosition$1.removeEventListener(this.absolutePositionProviderId);
|
|
3514
|
+
}
|
|
3515
|
+
}
|
|
3516
|
+
|
|
3517
|
+
|
|
3518
|
+
onDeviceOrientationChromeEvent = e => {
|
|
3519
|
+
|
|
3520
|
+
this.magQuaternionTimestamp = e.timeStamp / 1e3;
|
|
3521
|
+
|
|
3522
|
+
if (typeof e.alpha !== 'number' || typeof e.beta !== 'number' || typeof e.gamma !== 'number') {
|
|
3523
|
+
this.notifyError(new MissingSensorError().from('deviceorientationabsolute'));
|
|
3524
|
+
return;
|
|
3525
|
+
}
|
|
3526
|
+
|
|
3527
|
+
this.magQuaternion = Rotations.eulerToQuaternionZXYDegrees(
|
|
3528
|
+
[e.alpha, e.beta, e.gamma]);
|
|
3529
|
+
|
|
3530
|
+
this.compute();
|
|
3531
|
+
};
|
|
3532
|
+
|
|
3533
|
+
|
|
3534
|
+
iosPreviousQuat = null;
|
|
3535
|
+
iosIsSkyMode;
|
|
3536
|
+
|
|
3537
|
+
onDeviceOrientationSafariEvent = e => {
|
|
3538
|
+
|
|
3539
|
+
this.magQuaternionTimestamp = e.timeStamp / 1e3;
|
|
3540
|
+
|
|
3541
|
+
if (typeof e.beta !== 'number' || typeof e.gamma !== 'number') {
|
|
3542
|
+
this.notifyError(new MissingSensorError().from('deviceorientation'));
|
|
3543
|
+
return;
|
|
3544
|
+
}
|
|
3545
|
+
|
|
3546
|
+
if (typeof e.webkitCompassHeading !== 'number') {
|
|
3547
|
+
super.notifyError(new MissingMagnetometerError().from('deviceorientation'));
|
|
3548
|
+
return;
|
|
3549
|
+
}
|
|
3550
|
+
|
|
3551
|
+
/**
|
|
3552
|
+
* Trying the best to retrieve a good quaternion from devicemotion event.
|
|
3553
|
+
*/
|
|
3554
|
+
|
|
3555
|
+
let alpha;
|
|
3556
|
+
const [qw, qx, qy, qz] = Rotations.eulerToQuaternionZXYDegrees([e.webkitCompassHeading, e.beta, e.gamma]);
|
|
3557
|
+
const groundAngle = rad2deg(Math.acos(qw ** 2 - qx ** 2 - qy ** 2 + qz ** 2));
|
|
3558
|
+
|
|
3559
|
+
let isSkyMode;
|
|
3560
|
+
if (groundAngle > 136) {
|
|
3561
|
+
isSkyMode = true;
|
|
3562
|
+
} else if (groundAngle < 134) {
|
|
3563
|
+
isSkyMode = false;
|
|
3564
|
+
} else if (this.iosPreviousQuat && this.iosIsSkyMode !== null) {
|
|
3565
|
+
/**
|
|
3566
|
+
* This condition is true only if :
|
|
3567
|
+
* - we are in the [134°; 136°] ground angle range
|
|
3568
|
+
* - we know the previous quaternion
|
|
3569
|
+
* - one of the both previous condition has been reached during this session
|
|
3570
|
+
*
|
|
3571
|
+
* In this case, we define a threshold to detect if there is a "jump" in the quaternion calculation,
|
|
3572
|
+
* then, the mode is switched.
|
|
3573
|
+
*/
|
|
3574
|
+
isSkyMode = Quaternion.distance([qw, qx, qy, qz], this.iosPreviousQuat) < 0.5
|
|
3575
|
+
? this.iosIsSkyMode
|
|
3576
|
+
: !this.iosIsSkyMode;
|
|
3577
|
+
}
|
|
3578
|
+
this.iosPreviousQuat = [qw, qx, qy, qz];
|
|
3579
|
+
this.iosIsSkyMode = isSkyMode;
|
|
3580
|
+
|
|
3581
|
+
if (typeof (isSkyMode) === 'undefined') {
|
|
3582
|
+
// Algorithm uncertainity
|
|
3583
|
+
return;
|
|
3584
|
+
}
|
|
3585
|
+
|
|
3586
|
+
if (isSkyMode) {
|
|
3587
|
+
alpha = 180 - e.webkitCompassHeading;
|
|
3588
|
+
} else {
|
|
3589
|
+
alpha = AbsoluteAttitudeFromBrowser.webkitCompassToHeading(
|
|
3590
|
+
e.webkitCompassHeading, e.beta, e.gamma);
|
|
3591
|
+
}
|
|
3592
|
+
|
|
3593
|
+
this.magQuaternion = Rotations.eulerToQuaternionZXYDegrees([alpha, e.beta, e.gamma]);
|
|
3594
|
+
|
|
3595
|
+
this.compute();
|
|
3596
|
+
};
|
|
3597
|
+
|
|
3598
|
+
|
|
3599
|
+
compute() {
|
|
3600
|
+
|
|
3601
|
+
if (!this.declinationQuaternion || !this.magQuaternion) {
|
|
3602
|
+
return;
|
|
3603
|
+
}
|
|
3604
|
+
|
|
3605
|
+
const trueQuaternion = Quaternion.multiply(this.declinationQuaternion, this.magQuaternion);
|
|
3606
|
+
const attitude = new Attitude(trueQuaternion, this.magQuaternionTimestamp,
|
|
3607
|
+
this.constructor.DEFAULT_ACCURACY, this.pname);
|
|
3608
|
+
this.notify(this.createEvent(
|
|
3609
|
+
EventType.AbsoluteAttitude,
|
|
3610
|
+
attitude,
|
|
3611
|
+
[this.absolutePositionEvent]
|
|
3612
|
+
));
|
|
3613
|
+
}
|
|
3614
|
+
|
|
3615
|
+
/**
|
|
3616
|
+
* Initialized declination quaternion using current position.
|
|
3617
|
+
* This method should be theoretically called every time the user moves.
|
|
3618
|
+
* But in reality declination does not change as much.
|
|
3619
|
+
* @param {Coordinates} positionEvent user position event
|
|
3620
|
+
*/
|
|
3621
|
+
onAbsolutePositionEvent = positionEvent => {
|
|
3622
|
+
|
|
3623
|
+
if (!positionEvent) {
|
|
3624
|
+
return;
|
|
3625
|
+
}
|
|
3626
|
+
|
|
3627
|
+
this.absolutePositionEvent = positionEvent;
|
|
3628
|
+
|
|
3629
|
+
const position = positionEvent.data;
|
|
3630
|
+
const wmmResult = geomagnetism.model().point([position.lat, position.lng]);
|
|
3631
|
+
// Declination is given in NED frame and our code use ENU, that is why we have: "-decl"
|
|
3632
|
+
this.declinationQuaternion = Quaternion.fromAxisAngle([0, 0, 1], - deg2rad(wmmResult.decl));
|
|
3633
|
+
|
|
3634
|
+
AbsolutePosition$1.removeEventListener(this.absolutePositionProviderId);
|
|
3635
|
+
this.absolutePositionProviderId = null;
|
|
3636
|
+
this.compute();
|
|
3637
|
+
}
|
|
3638
|
+
|
|
3639
|
+
static webkitCompassToHeading(_webkitCompassHeading, _beta, _gamma) {
|
|
3640
|
+
const webkitCompassHeading = deg2rad(_webkitCompassHeading);
|
|
3641
|
+
const beta = deg2rad(_beta);
|
|
3642
|
+
const gamma = deg2rad(_gamma);
|
|
3643
|
+
|
|
3644
|
+
const c1 = Math.cos(webkitCompassHeading / 2);
|
|
3645
|
+
const c2 = Math.cos(beta / 2);
|
|
3646
|
+
const c3 = Math.cos(gamma / 2);
|
|
3647
|
+
const s1 = Math.sin(webkitCompassHeading / 2);
|
|
3648
|
+
const s2 = Math.sin(beta / 2);
|
|
3649
|
+
const s3 = Math.sin(gamma / 2);
|
|
3650
|
+
|
|
3651
|
+
const qw = c1 * c2 * c3 - s1 * s2 * s3;
|
|
3652
|
+
const qz = s1 * c2 * c3 + c1 * s2 * s3;
|
|
3653
|
+
|
|
3654
|
+
return rad2deg(-2 * Math.atan(qz / qw));
|
|
3655
|
+
}
|
|
3656
|
+
}
|
|
3657
|
+
|
|
3658
|
+
var AbsoluteAttitudeFromBrowser$1 = new AbsoluteAttitudeFromBrowser();
|
|
3659
|
+
|
|
3660
|
+
/**
|
|
3661
|
+
* Inclination provider gives the inclination of the device using Imu Sensor
|
|
3662
|
+
* For example, when the top of the device is pointing the sky, inclination = Math.PI/2
|
|
3663
|
+
* when the device is layed on a table, inclination = 0
|
|
3664
|
+
* This provider use window.orientation to return a result in function of screen orientation
|
|
3665
|
+
*/
|
|
3666
|
+
class InclinationFromAcc extends Provider {
|
|
3667
|
+
|
|
3668
|
+
|
|
3669
|
+
/**
|
|
3670
|
+
* @override
|
|
3671
|
+
*/
|
|
3672
|
+
static get pname() {
|
|
3673
|
+
return 'InclinationFromAcc';
|
|
3674
|
+
}
|
|
3675
|
+
|
|
3676
|
+
/**
|
|
3677
|
+
* @override
|
|
3678
|
+
*/
|
|
3679
|
+
static get eventsType() {
|
|
3680
|
+
return [EventType.Inclination];
|
|
3681
|
+
}
|
|
3682
|
+
|
|
3683
|
+
/**
|
|
3684
|
+
* @override
|
|
3685
|
+
*/
|
|
3686
|
+
get _availability() {
|
|
3687
|
+
return Accelerometer$1.availability;
|
|
3688
|
+
}
|
|
3689
|
+
|
|
3690
|
+
/**
|
|
3691
|
+
* @override
|
|
3692
|
+
*/
|
|
3693
|
+
start() {
|
|
3694
|
+
this.providerId = Accelerometer$1.addEventListener(
|
|
3695
|
+
events => this.onAccelerometerEvent(events[0]),
|
|
3696
|
+
error => this.notifyError(error)
|
|
3697
|
+
);
|
|
3698
|
+
}
|
|
3699
|
+
|
|
3700
|
+
/**
|
|
3701
|
+
* @override
|
|
3702
|
+
*/
|
|
3703
|
+
stop() {
|
|
3704
|
+
Accelerometer$1.removeEventListener(this.providerId);
|
|
3705
|
+
}
|
|
3706
|
+
|
|
3707
|
+
/**
|
|
3708
|
+
* @private
|
|
3709
|
+
*/
|
|
3710
|
+
onAccelerometerEvent = accelerometerEvent => {
|
|
3711
|
+
const acc = accelerometerEvent.data.values;
|
|
3712
|
+
|
|
3713
|
+
const screenOrientation = window.orientation || 0;
|
|
3714
|
+
|
|
3715
|
+
const sizeAcc = Math.sqrt(acc[0] * acc[0] + acc[1] * acc[1] + acc[2] * acc[2]);
|
|
3716
|
+
const accNormalized = [acc[0] / sizeAcc, acc[1] / sizeAcc, acc[2] / sizeAcc];
|
|
3717
|
+
|
|
3718
|
+
const q = [accNormalized[2] + 1, accNormalized[1], -accNormalized[0], 0];
|
|
3719
|
+
const qSize = Math.sqrt(q[0] * q[0] + q[1] * q[1] + q[2] * q[2]);
|
|
3720
|
+
const qNormalized = [q[0] / qSize, q[1] / qSize, q[2] / qSize, 0];
|
|
3721
|
+
|
|
3722
|
+
let inclination;
|
|
3723
|
+
if (screenOrientation === 0) {
|
|
3724
|
+
inclination = Math.asin(2 * qNormalized[1] * qNormalized[0]);
|
|
3725
|
+
} else if (screenOrientation === 90) {
|
|
3726
|
+
inclination = -Math.asin(2 * qNormalized[2] * qNormalized[0]);
|
|
3727
|
+
} else if (screenOrientation === -90) {
|
|
3728
|
+
inclination = Math.asin(2 * qNormalized[2] * qNormalized[0]);
|
|
3729
|
+
} else if (screenOrientation === 180) {
|
|
3730
|
+
inclination = -Math.asin(2 * qNormalized[1] * qNormalized[0]);
|
|
3731
|
+
}
|
|
3732
|
+
|
|
3733
|
+
this.notify(this.createEvent(
|
|
3734
|
+
EventType.Inclination,
|
|
3735
|
+
inclination,
|
|
3736
|
+
[accelerometerEvent]
|
|
3737
|
+
));
|
|
3738
|
+
};
|
|
3739
|
+
}
|
|
3740
|
+
|
|
3741
|
+
var InclinationFromAcc$1 = new InclinationFromAcc();
|
|
3742
|
+
|
|
3743
|
+
/**
|
|
3744
|
+
* Inclination provider gives the inclination of the device using Relative Attitude
|
|
3745
|
+
* For example, when the top of the device is pointing the sky, inclination = Math.PI/2
|
|
3746
|
+
* when the device is layed on a table, inclination = 0
|
|
3747
|
+
* This provider use window.orientation to return a result in function of screen orientation
|
|
3748
|
+
*/
|
|
3749
|
+
class InclinationFromRelativeAttitude extends Provider {
|
|
3750
|
+
|
|
3751
|
+
/**
|
|
3752
|
+
* @override
|
|
3753
|
+
*/
|
|
3754
|
+
static get pname() {
|
|
3755
|
+
return 'InclinationFromRelativeAttitude';
|
|
3756
|
+
}
|
|
3757
|
+
|
|
3758
|
+
/**
|
|
3759
|
+
* @override
|
|
3760
|
+
*/
|
|
3761
|
+
static get eventsType() {
|
|
3762
|
+
return [EventType.Inclination];
|
|
3763
|
+
}
|
|
3764
|
+
|
|
3765
|
+
/**
|
|
3766
|
+
* @override
|
|
3767
|
+
*/
|
|
3768
|
+
get _availability() {
|
|
3769
|
+
return RelativeAttitudeFromInertial$1.availability;
|
|
3770
|
+
}
|
|
3771
|
+
|
|
3772
|
+
/**
|
|
3773
|
+
* @override
|
|
3774
|
+
*/
|
|
3775
|
+
start() {
|
|
3776
|
+
this.providerId = RelativeAttitudeFromInertial$1.addEventListener(
|
|
3777
|
+
events => {
|
|
3778
|
+
const attitudeEvent = events[0];
|
|
3779
|
+
const inclination = this.constructor.enuQuatToInclination(
|
|
3780
|
+
attitudeEvent.data.quaternion
|
|
3781
|
+
);
|
|
3782
|
+
this.notify(this.createEvent(
|
|
3783
|
+
EventType.Inclination,
|
|
3784
|
+
inclination,
|
|
3785
|
+
[attitudeEvent]
|
|
3786
|
+
));
|
|
3787
|
+
},
|
|
3788
|
+
error => this.notifyError(error)
|
|
3789
|
+
);
|
|
3790
|
+
}
|
|
3791
|
+
|
|
3792
|
+
/**
|
|
3793
|
+
* @override
|
|
3794
|
+
*/
|
|
3795
|
+
stop() {
|
|
3796
|
+
RelativeAttitudeFromInertial$1.removeEventListener(this.providerId);
|
|
3797
|
+
}
|
|
3798
|
+
|
|
3799
|
+
static enuQuatToInclination(q) {
|
|
3800
|
+
const screenOrientation = window.orientation || 0;
|
|
3801
|
+
|
|
3802
|
+
if (screenOrientation === 0) {
|
|
3803
|
+
return Math.asin(2 * (q[2] * q[3] + q[1] * q[0]));
|
|
3804
|
+
} else if (screenOrientation === 90) {
|
|
3805
|
+
return Math.asin(2 * (q[1] * q[3] - q[2] * q[0]));
|
|
3806
|
+
} else if (screenOrientation === -90) {
|
|
3807
|
+
return Math.asin(2 * (q[2] * q[0] - q[1] * q[3]));
|
|
3808
|
+
}
|
|
3809
|
+
// else if (screenOrientation === 180)
|
|
3810
|
+
return -Math.asin(2 * (q[2] * q[3] + q[1] * q[0]));
|
|
3811
|
+
}
|
|
3812
|
+
}
|
|
3813
|
+
|
|
3814
|
+
var InclinationFromRelativeAttitude$1 = new InclinationFromRelativeAttitude();
|
|
3815
|
+
|
|
3816
|
+
/**
|
|
3817
|
+
* Inclination provider gives the inclination of the device using Imu Sensor
|
|
3818
|
+
* For example, when the top of the device is pointing the sky, inclination = Math.PI/2
|
|
3819
|
+
* when the device is layed on a table, inclination = 0
|
|
3820
|
+
* This provider use window.orientation to return a result in function of screen orientation
|
|
3821
|
+
*/
|
|
3822
|
+
class Inclination extends Provider {
|
|
3823
|
+
|
|
3824
|
+
/**
|
|
3825
|
+
* @override
|
|
3826
|
+
*/
|
|
3827
|
+
static get pname() {
|
|
3828
|
+
return 'InclinationProvider';
|
|
3829
|
+
}
|
|
3830
|
+
|
|
3831
|
+
/**
|
|
3832
|
+
* @override
|
|
3833
|
+
*/
|
|
3834
|
+
static get eventsType() {
|
|
3835
|
+
return [EventType.Inclination];
|
|
3836
|
+
}
|
|
3837
|
+
|
|
3838
|
+
/**
|
|
3839
|
+
* @override
|
|
3840
|
+
*/
|
|
3841
|
+
get _availability() {
|
|
3842
|
+
return PromiseUtils.any([
|
|
3843
|
+
InclinationFromAcc$1.availability,
|
|
3844
|
+
InclinationFromRelativeAttitude$1.availability
|
|
3845
|
+
]);
|
|
3846
|
+
}
|
|
3847
|
+
|
|
3848
|
+
/**
|
|
3849
|
+
* @override
|
|
3850
|
+
*/
|
|
3851
|
+
start() {
|
|
3852
|
+
|
|
3853
|
+
const startInclinationFromAcc = () => {
|
|
3854
|
+
this.provider = InclinationFromAcc$1;
|
|
3855
|
+
this.providerId = this.provider.addEventListener(
|
|
3856
|
+
events => this.notify(events[0].clone()),
|
|
3857
|
+
error => this.notifyError(error)
|
|
3858
|
+
);
|
|
3859
|
+
};
|
|
3860
|
+
|
|
3861
|
+
if (InclinationFromRelativeAttitude$1.availability) {
|
|
3862
|
+
this.provider = InclinationFromRelativeAttitude$1;
|
|
3863
|
+
this.providerId = this.provider.addEventListener(
|
|
3864
|
+
events => this.notify(events[0].clone()),
|
|
3865
|
+
() => {
|
|
3866
|
+
InclinationFromRelativeAttitude$1.removeEventListener(this.providerId);
|
|
3867
|
+
startInclinationFromAcc();
|
|
3868
|
+
}
|
|
3869
|
+
);
|
|
3870
|
+
} else {
|
|
3871
|
+
startInclinationFromAcc();
|
|
3872
|
+
}
|
|
3873
|
+
}
|
|
3874
|
+
|
|
3875
|
+
/**
|
|
3876
|
+
* @override
|
|
3877
|
+
*/
|
|
3878
|
+
stop() {
|
|
3879
|
+
if (this.provider) {
|
|
3880
|
+
this.provider.removeEventListener(this.providerId);
|
|
3881
|
+
}
|
|
3882
|
+
}
|
|
3883
|
+
}
|
|
3884
|
+
|
|
3885
|
+
var Inclination$1 = new Inclination();
|
|
3886
|
+
|
|
3887
|
+
class IpResolveServerError extends Error {
|
|
3888
|
+
|
|
3889
|
+
static DEFAULT_MESSAGE = 'IP Resolver failed';
|
|
3890
|
+
|
|
3891
|
+
constructor(message) {
|
|
3892
|
+
super(message || IpResolveServerError.DEFAULT_MESSAGE);
|
|
3893
|
+
}
|
|
3894
|
+
}
|
|
3895
|
+
|
|
3896
|
+
/**
|
|
3897
|
+
* GnssWifiProvider is a provider based on navigator.geolocation.
|
|
3898
|
+
* This API does not allow us to know if the position returned is provided
|
|
3899
|
+
* by Wifi Fingerprinting algorithms or by GNSS. That is why the name is
|
|
3900
|
+
* "GnssWifi".
|
|
3901
|
+
*/
|
|
3902
|
+
class Ip extends Provider {
|
|
3903
|
+
|
|
3904
|
+
/**
|
|
3905
|
+
* @override
|
|
3906
|
+
*/
|
|
3907
|
+
static get pname() {
|
|
3908
|
+
return 'IP';
|
|
3909
|
+
}
|
|
3910
|
+
|
|
3911
|
+
/**
|
|
3912
|
+
* @override
|
|
3913
|
+
*/
|
|
3914
|
+
static get eventsType() {
|
|
3915
|
+
return [EventType.AbsolutePosition];
|
|
3916
|
+
}
|
|
3917
|
+
|
|
3918
|
+
/**
|
|
3919
|
+
* @override
|
|
3920
|
+
*/
|
|
3921
|
+
start() {
|
|
3922
|
+
|
|
3923
|
+
fetch('https://ipinfo.io/geo?token=24a7ca2f3b489d')
|
|
3924
|
+
.then((response) => {
|
|
3925
|
+
if (!response) {
|
|
3926
|
+
this.notifyError(new IpResolveServerError());
|
|
3927
|
+
return;
|
|
3928
|
+
}
|
|
3929
|
+
|
|
3930
|
+
const timestamp = TimeUtils.preciseTime() / 1e3;
|
|
3931
|
+
|
|
3932
|
+
const latLngStr = response.loc.split(',');
|
|
3933
|
+
const position = new UserPosition(
|
|
3934
|
+
parseFloat(latLngStr[0]),
|
|
3935
|
+
parseFloat(latLngStr[1]),
|
|
3936
|
+
null,
|
|
3937
|
+
null,
|
|
3938
|
+
timestamp
|
|
3939
|
+
);
|
|
3940
|
+
|
|
3941
|
+
this.notify(this.createEvent(EventType.AbsolutePosition, position));
|
|
3942
|
+
});
|
|
3943
|
+
}
|
|
3944
|
+
|
|
3945
|
+
/**
|
|
3946
|
+
* @override
|
|
3947
|
+
*/
|
|
3948
|
+
stop() {
|
|
3949
|
+
|
|
3950
|
+
}
|
|
3951
|
+
|
|
3952
|
+
}
|
|
3953
|
+
|
|
3954
|
+
var Ip$1 = new Ip();
|
|
3955
|
+
|
|
3956
|
+
class Barcode extends Provider {
|
|
3957
|
+
|
|
3958
|
+
/**
|
|
3959
|
+
* @override
|
|
3960
|
+
*/
|
|
3961
|
+
static get pname() {
|
|
3962
|
+
return 'Barcode';
|
|
3963
|
+
}
|
|
3964
|
+
|
|
3965
|
+
/**
|
|
3966
|
+
* @override
|
|
3967
|
+
*/
|
|
3968
|
+
static get eventsType() {
|
|
3969
|
+
return [EventType.Barcode];
|
|
3970
|
+
}
|
|
3971
|
+
|
|
3972
|
+
/**
|
|
3973
|
+
* @override
|
|
3974
|
+
*/
|
|
3975
|
+
get _availability() {
|
|
3976
|
+
return ArCore$1.availability;
|
|
3977
|
+
}
|
|
3978
|
+
|
|
3979
|
+
/**
|
|
3980
|
+
* @override
|
|
3981
|
+
*/
|
|
3982
|
+
start() {
|
|
3983
|
+
ArCore$1.enableBarcodeScanner();
|
|
3984
|
+
|
|
3985
|
+
this.providerId = ArCore$1.addEventListener(
|
|
3986
|
+
events => {
|
|
3987
|
+
const barcodeEvent = events.find(event => event.dataType === EventType.Barcode);
|
|
3988
|
+
if (barcodeEvent) {
|
|
3989
|
+
this.notify(barcodeEvent);
|
|
3990
|
+
}
|
|
3991
|
+
},
|
|
3992
|
+
error => this.notifyError(error)
|
|
3993
|
+
);
|
|
3994
|
+
}
|
|
3995
|
+
|
|
3996
|
+
/**
|
|
3997
|
+
* @override
|
|
3998
|
+
*/
|
|
3999
|
+
stop() {
|
|
4000
|
+
ArCore$1.disableBarcodeScanner();
|
|
4001
|
+
ArCore$1.removeEventListener(this.providerId);
|
|
4002
|
+
}
|
|
4003
|
+
}
|
|
4004
|
+
|
|
4005
|
+
var Barcode$1 = new Barcode();
|
|
4006
|
+
|
|
4007
|
+
class CameraNative extends Provider {
|
|
4008
|
+
|
|
4009
|
+
/**
|
|
4010
|
+
* @override
|
|
4011
|
+
*/
|
|
4012
|
+
static get pname() {
|
|
4013
|
+
return 'CameraNative';
|
|
4014
|
+
}
|
|
4015
|
+
|
|
4016
|
+
/**
|
|
4017
|
+
* @override
|
|
4018
|
+
*/
|
|
4019
|
+
static get eventsType() {
|
|
4020
|
+
return [EventType.CameraNative];
|
|
4021
|
+
}
|
|
4022
|
+
|
|
4023
|
+
/**
|
|
4024
|
+
* @override
|
|
4025
|
+
*/
|
|
4026
|
+
get _availability() {
|
|
4027
|
+
return new Promise((resolve, reject) => {
|
|
4028
|
+
ArCore$1.availability
|
|
4029
|
+
.then(output => resolve(output))
|
|
4030
|
+
.catch(() => reject('Providers will not use the camera.'));
|
|
4031
|
+
});
|
|
4032
|
+
}
|
|
4033
|
+
|
|
4034
|
+
/**
|
|
4035
|
+
* @override
|
|
4036
|
+
*/
|
|
4037
|
+
start() {
|
|
4038
|
+
|
|
4039
|
+
if (ArCore$1.state !== ProviderState.STOPPPED) {
|
|
4040
|
+
this.notify(this.createEvent(EventType.CameraNative, 'started'));
|
|
4041
|
+
}
|
|
4042
|
+
this.providerId = ArCore$1.addMonitoringListener(
|
|
4043
|
+
() => this.notify(this.createEvent(EventType.CameraNative, 'started')),
|
|
4044
|
+
() => this.notify(this.createEvent(EventType.CameraNative, 'stopped'))
|
|
4045
|
+
);
|
|
4046
|
+
}
|
|
4047
|
+
|
|
4048
|
+
/**
|
|
4049
|
+
* @override
|
|
4050
|
+
*/
|
|
4051
|
+
stop() {
|
|
4052
|
+
ArCore$1.removeMonitoringListener(this.providerId);
|
|
4053
|
+
}
|
|
4054
|
+
}
|
|
4055
|
+
|
|
4056
|
+
var CameraNative$1 = new CameraNative();
|
|
4057
|
+
|
|
4058
|
+
class CameraProjectionMatrix extends Provider {
|
|
4059
|
+
|
|
4060
|
+
/**
|
|
4061
|
+
* @override
|
|
4062
|
+
*/
|
|
4063
|
+
static get pname() {
|
|
4064
|
+
return 'CameraProjectionMatrix';
|
|
4065
|
+
}
|
|
4066
|
+
|
|
4067
|
+
/**
|
|
4068
|
+
* @override
|
|
4069
|
+
*/
|
|
4070
|
+
static get eventsType() {
|
|
4071
|
+
return [EventType.CameraProjectionMatrix];
|
|
4072
|
+
}
|
|
4073
|
+
|
|
4074
|
+
/**
|
|
4075
|
+
* @override
|
|
4076
|
+
*/
|
|
4077
|
+
get _availability() {
|
|
4078
|
+
return ArCore$1.availability;
|
|
4079
|
+
}
|
|
4080
|
+
|
|
4081
|
+
/**
|
|
4082
|
+
* @override
|
|
4083
|
+
*/
|
|
4084
|
+
start() {
|
|
4085
|
+
this.providerId = ArCore$1.addEventListener(
|
|
4086
|
+
events => {
|
|
4087
|
+
const projMatrixEvent = events.find(event => event.dataType === EventType.CameraProjectionMatrix);
|
|
4088
|
+
if (projMatrixEvent) {
|
|
4089
|
+
this.notify(projMatrixEvent);
|
|
4090
|
+
}
|
|
4091
|
+
},
|
|
4092
|
+
error => this.notifyError(error)
|
|
4093
|
+
);
|
|
4094
|
+
}
|
|
4095
|
+
|
|
4096
|
+
/**
|
|
4097
|
+
* @override
|
|
4098
|
+
*/
|
|
4099
|
+
stop() {
|
|
4100
|
+
ArCore$1.removeEventListener(this.providerId);
|
|
4101
|
+
}
|
|
4102
|
+
}
|
|
4103
|
+
|
|
4104
|
+
var CameraProjectionMatrix$1 = new CameraProjectionMatrix();
|
|
4105
|
+
|
|
4106
|
+
/* eslint-disable max-len */
|
|
4107
|
+
|
|
4108
|
+
var Providers = /*#__PURE__*/Object.freeze({
|
|
4109
|
+
__proto__: null,
|
|
4110
|
+
Imu: Imu$1,
|
|
4111
|
+
Accelerometer: Accelerometer$1,
|
|
4112
|
+
Gyroscope: Gyroscope$1,
|
|
4113
|
+
HighRotationsDetector: HighRotationsDetector$1,
|
|
4114
|
+
RelativeAttitudeFromBrowser: RelativeAttitudeFromBrowser$1,
|
|
4115
|
+
RelativeAttitudeFromEkf: RelativeAttitudeFromEkf$1,
|
|
4116
|
+
RelativeAttitudeFromInertial: RelativeAttitudeFromInertial$1,
|
|
4117
|
+
RelativeAttitude: RelativeAttitude$1,
|
|
4118
|
+
AbsoluteAttitudeFromBrowser: AbsoluteAttitudeFromBrowser$1,
|
|
4119
|
+
AbsoluteAttitude: AbsoluteAttitude$1,
|
|
4120
|
+
TurnDetector: TurnDectector,
|
|
4121
|
+
InclinationFromAcc: InclinationFromAcc$1,
|
|
4122
|
+
InclinationFromRelativeAttitude: InclinationFromRelativeAttitude$1,
|
|
4123
|
+
Inclination: Inclination$1,
|
|
4124
|
+
StepDetector: StepDetector$1,
|
|
4125
|
+
StraightLineDetector: StraightLineDetector$1,
|
|
4126
|
+
ArCore: ArCore$1,
|
|
4127
|
+
Pdr: Pdr$1,
|
|
4128
|
+
GeoRelativePositionFromArCore: GeoRelativePositionFromArCore$1,
|
|
4129
|
+
GeoRelativePosition: GeoRelativePositionProvider,
|
|
4130
|
+
GnssWifi: GnssWifi$1,
|
|
4131
|
+
Ip: Ip$1,
|
|
4132
|
+
AbsolutePosition: AbsolutePosition$1,
|
|
4133
|
+
Barcode: Barcode$1,
|
|
4134
|
+
CameraNative: CameraNative$1,
|
|
4135
|
+
CameraProjectionMatrix: CameraProjectionMatrix$1
|
|
4136
|
+
});
|
|
4137
|
+
|
|
4138
|
+
class ProvidersInterface {
|
|
4139
|
+
|
|
4140
|
+
static idListeners = new Map();
|
|
4141
|
+
|
|
4142
|
+
/**
|
|
4143
|
+
* @param {EventType} eventType
|
|
4144
|
+
* @param {Function} onEvent
|
|
4145
|
+
* @param {Function} onError
|
|
4146
|
+
* @param {Boolean} watchOnly if true, addEventListener does not start any provider, it just watches for events
|
|
4147
|
+
* @returns {Number}
|
|
4148
|
+
*/
|
|
4149
|
+
static addEventListener(eventType, onEvent, onError, watchOnly = false) {
|
|
4150
|
+
const provider = this._getProviderFromEventType(eventType);
|
|
4151
|
+
const id = provider.addEventListener(events => onEvent(events[0].data), onError, !watchOnly);
|
|
4152
|
+
this.idListeners.set(id, provider);
|
|
4153
|
+
return id;
|
|
4154
|
+
}
|
|
4155
|
+
|
|
4156
|
+
/**
|
|
4157
|
+
* @see addEventListener
|
|
4158
|
+
* @param {Number} id
|
|
4159
|
+
*/
|
|
4160
|
+
static removeEventListener(id) {
|
|
4161
|
+
const provider = this.idListeners.get(id);
|
|
4162
|
+
if (provider) {
|
|
4163
|
+
return provider.removeEventListener(id);
|
|
4164
|
+
}
|
|
4165
|
+
return null;
|
|
4166
|
+
}
|
|
4167
|
+
|
|
4168
|
+
|
|
4169
|
+
/**
|
|
4170
|
+
* @param {EventType} eventType
|
|
4171
|
+
* @param {Object} data
|
|
4172
|
+
*/
|
|
4173
|
+
static feed(eventType, data) {
|
|
4174
|
+
this._checkEventTypeDataConsistency(eventType, data);
|
|
4175
|
+
if (eventType === EventType.Network) {
|
|
4176
|
+
MapMatchingHandler$1.network = data;
|
|
4177
|
+
return;
|
|
4178
|
+
}
|
|
4179
|
+
if (eventType === EventType.Itinerary) {
|
|
4180
|
+
MapMatchingHandler$1.itinerary = data;
|
|
4181
|
+
return;
|
|
4182
|
+
}
|
|
4183
|
+
this._getProviderFromEventType(eventType).feed(data, eventType);
|
|
4184
|
+
}
|
|
4185
|
+
|
|
4186
|
+
/**
|
|
4187
|
+
* @param {EventType} eventType
|
|
4188
|
+
*/
|
|
4189
|
+
static getLastKnown(eventType) {
|
|
4190
|
+
const { lastEvent } = this._getProviderFromEventType(eventType);
|
|
4191
|
+
return lastEvent ? lastEvent.data : null;
|
|
4192
|
+
}
|
|
4193
|
+
|
|
4194
|
+
/**
|
|
4195
|
+
* @param {EventType} eventType
|
|
4196
|
+
* @returns {Provider}
|
|
4197
|
+
*/
|
|
4198
|
+
static _getProviderFromEventType(eventType) {
|
|
4199
|
+
|
|
4200
|
+
switch (eventType) {
|
|
4201
|
+
case EventType.AbsoluteAttitude:
|
|
4202
|
+
case EventType.AbsoluteHeading:
|
|
4203
|
+
return AbsoluteAttitude$1;
|
|
4204
|
+
|
|
4205
|
+
case EventType.AbsolutePosition:
|
|
4206
|
+
return AbsolutePosition$1;
|
|
4207
|
+
|
|
4208
|
+
case EventType.RelativeAttitude:
|
|
4209
|
+
return RelativeAttitude$1;
|
|
4210
|
+
|
|
4211
|
+
case EventType.Inclination:
|
|
4212
|
+
return Inclination$1;
|
|
4213
|
+
|
|
4214
|
+
case EventType.CameraNative:
|
|
4215
|
+
return CameraNative$1;
|
|
4216
|
+
|
|
4217
|
+
case EventType.Barcode:
|
|
4218
|
+
return Barcode$1;
|
|
4219
|
+
|
|
4220
|
+
case EventType.CameraProjectionMatrix:
|
|
4221
|
+
return CameraProjectionMatrix$1;
|
|
4222
|
+
|
|
4223
|
+
default:
|
|
4224
|
+
throw new Error(`Unable to deal with this event type: ${eventType}`);
|
|
4225
|
+
}
|
|
4226
|
+
}
|
|
4227
|
+
|
|
4228
|
+
static _checkEventTypeDataConsistency(eventType, data) {
|
|
4229
|
+
const errorFn = expectedType =>
|
|
4230
|
+
new Error(`Event type (${eventType}) is not an instance of ${expectedType}`);
|
|
4231
|
+
|
|
4232
|
+
switch (eventType) {
|
|
4233
|
+
case EventType.AbsoluteAttitude:
|
|
4234
|
+
if (!(data instanceof Attitude)) {
|
|
4235
|
+
throw errorFn('Attitude');
|
|
4236
|
+
}
|
|
4237
|
+
break;
|
|
4238
|
+
case EventType.AbsoluteHeading:
|
|
4239
|
+
if (!(data instanceof AbsoluteHeading)) {
|
|
4240
|
+
throw errorFn('AbsoluteHeading');
|
|
4241
|
+
}
|
|
4242
|
+
break;
|
|
4243
|
+
case EventType.AbsolutePosition:
|
|
4244
|
+
if (!(data instanceof UserPosition)) {
|
|
4245
|
+
throw errorFn('UserPosition');
|
|
4246
|
+
}
|
|
4247
|
+
break;
|
|
4248
|
+
case EventType.Network:
|
|
4249
|
+
if (!(data instanceof Network || data === null)) {
|
|
4250
|
+
throw errorFn('Network');
|
|
4251
|
+
}
|
|
4252
|
+
break;
|
|
4253
|
+
case EventType.Itinerary:
|
|
4254
|
+
if (!(data instanceof Itinerary || data === null)) {
|
|
4255
|
+
throw errorFn('Itinerary');
|
|
4256
|
+
}
|
|
4257
|
+
break;
|
|
4258
|
+
default:
|
|
4259
|
+
throw new Error(`Unable to deal with this event type: ${eventType}`);
|
|
4260
|
+
}
|
|
4261
|
+
}
|
|
4262
|
+
|
|
4263
|
+
/**
|
|
4264
|
+
* @param {Boolean} enabled
|
|
4265
|
+
*/
|
|
4266
|
+
static set logger(enabled) {
|
|
4267
|
+
ProvidersLoggerOld$1.enabled = enabled;
|
|
4268
|
+
}
|
|
4269
|
+
|
|
4270
|
+
static get mapMatchingMaxDistance() {
|
|
4271
|
+
return MapMatchingHandler$1.maxDistance;
|
|
4272
|
+
}
|
|
4273
|
+
|
|
4274
|
+
static set mapMatchingMaxDistance(maxDistance) {
|
|
4275
|
+
MapMatchingHandler$1.maxDistance = maxDistance;
|
|
4276
|
+
}
|
|
4277
|
+
|
|
4278
|
+
static get mapMatchingMinDistance() {
|
|
4279
|
+
return MapMatchingHandler$1.minDistance;
|
|
4280
|
+
}
|
|
4281
|
+
|
|
4282
|
+
static set mapMatchingMinDistance(minDistance) {
|
|
4283
|
+
MapMatchingHandler$1.minDistance = minDistance;
|
|
4284
|
+
}
|
|
4285
|
+
|
|
4286
|
+
static get mapMatchingMaxAngleBearing() {
|
|
4287
|
+
return MapMatchingHandler$1.maxAngleBearing;
|
|
4288
|
+
}
|
|
4289
|
+
|
|
4290
|
+
static set mapMatchingMaxAngleBearing(maxAngleBearing) {
|
|
4291
|
+
MapMatchingHandler$1.maxAngleBearing = maxAngleBearing;
|
|
4292
|
+
}
|
|
4293
|
+
}
|
|
4294
|
+
|
|
4295
|
+
/* eslint-disable max-statements */
|
|
4296
|
+
|
|
4297
|
+
class PositionSmoother {
|
|
4298
|
+
|
|
4299
|
+
|
|
4300
|
+
// Generated positions by second
|
|
4301
|
+
static DEFAULT_FREQUENCY = 60;
|
|
4302
|
+
|
|
4303
|
+
// flyby (in second)
|
|
4304
|
+
static DEFAULT_FLYBY_TIME = 1;
|
|
4305
|
+
|
|
4306
|
+
|
|
4307
|
+
/**
|
|
4308
|
+
* @param {Function} callback
|
|
4309
|
+
* @param {Number} frequency in Hz
|
|
4310
|
+
*/
|
|
4311
|
+
constructor(callback, frequency = this.constructor.DEFAULT_FREQUENCY) {
|
|
4312
|
+
this.callback = callback;
|
|
4313
|
+
this.positionsQueue = [];
|
|
4314
|
+
this.frequency = frequency;
|
|
4315
|
+
}
|
|
4316
|
+
|
|
4317
|
+
/**
|
|
4318
|
+
* @param {UserPosition} newPosition
|
|
4319
|
+
* @param {Number} flybyTime in seconds
|
|
4320
|
+
*/
|
|
4321
|
+
feed(newPosition, flybyTime = this.constructor.DEFAULT_FLYBY_TIME) {
|
|
4322
|
+
|
|
4323
|
+
if (!(newPosition instanceof UserPosition)) {
|
|
4324
|
+
throw new TypeError('newPosition is not instance of UserPosition');
|
|
4325
|
+
}
|
|
4326
|
+
|
|
4327
|
+
if (newPosition.time === null) {
|
|
4328
|
+
throw new Error('newPosition does not have time property');
|
|
4329
|
+
}
|
|
4330
|
+
|
|
4331
|
+
let previousPosition;
|
|
4332
|
+
if (this.positionsQueue.length !== 0) {
|
|
4333
|
+
previousPosition = this.positionsQueue[0];
|
|
4334
|
+
this.positionsQueue = [];
|
|
4335
|
+
} else {
|
|
4336
|
+
previousPosition = this.previousPosition;
|
|
4337
|
+
}
|
|
4338
|
+
|
|
4339
|
+
if (previousPosition) {
|
|
4340
|
+
|
|
4341
|
+
const refTimestamp = newPosition.time;
|
|
4342
|
+
const distance = previousPosition.distanceTo(newPosition);
|
|
4343
|
+
const azimuth = previousPosition.bearingTo(newPosition);
|
|
4344
|
+
|
|
4345
|
+
let i = 1;
|
|
4346
|
+
const nSamples = this.frequency * flybyTime + 1;
|
|
4347
|
+
|
|
4348
|
+
let newPositionLevel = null;
|
|
4349
|
+
if (newPosition.level !== null) {
|
|
4350
|
+
newPositionLevel = newPosition.level.clone();
|
|
4351
|
+
}
|
|
4352
|
+
|
|
4353
|
+
while (i < nSamples + 1) {
|
|
4354
|
+
const smoothedPosition = previousPosition.destinationPoint(distance * i / nSamples, azimuth);
|
|
4355
|
+
smoothedPosition.time = refTimestamp + (i - 1) / this.frequency;
|
|
4356
|
+
smoothedPosition.level = newPositionLevel;
|
|
4357
|
+
smoothedPosition.bearing = newPosition.bearing;
|
|
4358
|
+
smoothedPosition.accuracy = Math.max(
|
|
4359
|
+
smoothedPosition.accuracy + (newPosition.accuracy - smoothedPosition.accuracy) * i / nSamples,
|
|
4360
|
+
0
|
|
4361
|
+
);
|
|
4362
|
+
this.positionsQueue.push(smoothedPosition);
|
|
4363
|
+
i++;
|
|
4364
|
+
}
|
|
4365
|
+
|
|
4366
|
+
if (this.timeoutNotify) {
|
|
4367
|
+
clearTimeout(this.timeoutNotify);
|
|
4368
|
+
}
|
|
4369
|
+
} else {
|
|
4370
|
+
this.positionsQueue.push(newPosition.clone());
|
|
4371
|
+
}
|
|
4372
|
+
|
|
4373
|
+
this.previousPosition = newPosition;
|
|
4374
|
+
this._notifyNext();
|
|
4375
|
+
}
|
|
4376
|
+
|
|
4377
|
+
_notifyNext = () => {
|
|
4378
|
+
this.callback(this.positionsQueue.shift());
|
|
4379
|
+
if (this.positionsQueue.length !== 0) {
|
|
4380
|
+
this.timeoutNotify = setTimeout(this._notifyNext, 1e3 / this.frequency);
|
|
4381
|
+
} else {
|
|
4382
|
+
delete this.timeoutNotify;
|
|
4383
|
+
}
|
|
4384
|
+
}
|
|
4385
|
+
|
|
4386
|
+
clear() {
|
|
4387
|
+
clearTimeout(this.timeoutNotify);
|
|
4388
|
+
delete this.timeoutNotify;
|
|
4389
|
+
this.positionsQueue = [];
|
|
4390
|
+
}
|
|
4391
|
+
}
|
|
4392
|
+
|
|
4393
|
+
/* eslint-disable max-statements */
|
|
4394
|
+
|
|
4395
|
+
class AttitudeSmoother {
|
|
4396
|
+
|
|
4397
|
+
/** @type {number} in radians/s */
|
|
4398
|
+
static ROTATION_SPEED_JUMP_THRESHOLD = deg2rad(180);
|
|
4399
|
+
|
|
4400
|
+
/** @type {number} in radians/s */
|
|
4401
|
+
static ROTATION_SPEED_CONVERGENCE = deg2rad(10);
|
|
4402
|
+
|
|
4403
|
+
/** @type {number} in radians/s */
|
|
4404
|
+
static HIGH_JUMP_THRESHOLD = deg2rad(20);
|
|
4405
|
+
|
|
4406
|
+
/** @type {number} in radians/s */
|
|
4407
|
+
static ROTATION_SPEED_HIGH_JUMP_CONVERGENCE = deg2rad(100);
|
|
4408
|
+
|
|
4409
|
+
/** @type {number} in radians */
|
|
4410
|
+
static PITCH_UNCERTAINITY_HEADING_THRESHOLD = deg2rad(5);
|
|
4411
|
+
|
|
4412
|
+
/** @type {Function} */
|
|
4413
|
+
_callback;
|
|
4414
|
+
|
|
4415
|
+
/** @type {object?} */
|
|
4416
|
+
_smoothing = null;
|
|
4417
|
+
|
|
4418
|
+
/**
|
|
4419
|
+
* @param {Function} callback
|
|
4420
|
+
*/
|
|
4421
|
+
constructor(callback) {
|
|
4422
|
+
this._callback = callback;
|
|
4423
|
+
}
|
|
4424
|
+
|
|
4425
|
+
/**
|
|
4426
|
+
* @param {Attitude} attitude
|
|
4427
|
+
*/
|
|
4428
|
+
feed(newAttitude) {
|
|
4429
|
+
|
|
4430
|
+
if (!(newAttitude instanceof Attitude)) {
|
|
4431
|
+
throw new TypeError('newAttitude is not instance of Attitude');
|
|
4432
|
+
}
|
|
4433
|
+
|
|
4434
|
+
if (newAttitude.time === null) {
|
|
4435
|
+
throw new Error('newAttitude does not have time property');
|
|
4436
|
+
}
|
|
4437
|
+
|
|
4438
|
+
const { _previousAttitude: previousAttitude } = this;
|
|
4439
|
+
this._previousAttitude = newAttitude;
|
|
4440
|
+
|
|
4441
|
+
if (!previousAttitude) {
|
|
4442
|
+
this._callback(newAttitude);
|
|
4443
|
+
return;
|
|
4444
|
+
}
|
|
4445
|
+
|
|
4446
|
+
/*
|
|
4447
|
+
* Comparison between two successive Attitude from the "feed" function
|
|
4448
|
+
*/
|
|
4449
|
+
if (AttitudeSmoother.isJump(previousAttitude, newAttitude)) {
|
|
4450
|
+
|
|
4451
|
+
const fromAttitude = this._smoothing === null
|
|
4452
|
+
? previousAttitude
|
|
4453
|
+
: this._smoothing.interpAttitude(previousAttitude);
|
|
4454
|
+
|
|
4455
|
+
const fromHeading = fromAttitude.heading;
|
|
4456
|
+
const toHeading = newAttitude.heading;
|
|
4457
|
+
const diffAngleHeading = diffAngle(toHeading, fromHeading);
|
|
4458
|
+
|
|
4459
|
+
const isHighJump = Math.abs(diffAngleHeading) < AttitudeSmoother.HIGH_JUMP_THRESHOLD;
|
|
4460
|
+
const rotationSpeedConvergence = isHighJump
|
|
4461
|
+
? AttitudeSmoother.ROTATION_SPEED_CONVERGENCE
|
|
4462
|
+
: AttitudeSmoother.ROTATION_SPEED_HIGH_JUMP_CONVERGENCE;
|
|
4463
|
+
|
|
4464
|
+
const fromTime = fromAttitude.time;
|
|
4465
|
+
const timeToConsume = Math.abs(diffAngleHeading) / rotationSpeedConvergence;
|
|
4466
|
+
const multiplier = diffAngleHeading < 0 ? -1 : 1;
|
|
4467
|
+
|
|
4468
|
+
this._smoothing = {
|
|
4469
|
+
toTime: fromTime + timeToConsume,
|
|
4470
|
+
|
|
4471
|
+
/**
|
|
4472
|
+
* @param {Attitude} attitude
|
|
4473
|
+
*/
|
|
4474
|
+
interpAttitude: attitude => {
|
|
4475
|
+
const angle = rotationSpeedConvergence * (attitude.time - fromTime);
|
|
4476
|
+
const interpHeading = fromHeading + angle * multiplier;
|
|
4477
|
+
const offsetQuat = Quaternion.fromAxisAngle([0, 0, 1], toHeading - interpHeading);
|
|
4478
|
+
const interpQuat = Quaternion.multiply(offsetQuat, attitude.quaternion);
|
|
4479
|
+
return new Attitude(interpQuat, attitude.time, attitude.accuracy);
|
|
4480
|
+
}
|
|
4481
|
+
};
|
|
4482
|
+
}
|
|
4483
|
+
|
|
4484
|
+
if (this._smoothing !== null) {
|
|
4485
|
+
if (newAttitude.time >= this._smoothing.toTime) {
|
|
4486
|
+
// This means that is the last epoch for smoothing
|
|
4487
|
+
this._smoothing = null;
|
|
4488
|
+
} else {
|
|
4489
|
+
const interpAttitude = this._smoothing.interpAttitude(newAttitude);
|
|
4490
|
+
this._callback(interpAttitude);
|
|
4491
|
+
return;
|
|
4492
|
+
}
|
|
4493
|
+
}
|
|
4494
|
+
|
|
4495
|
+
this._callback(newAttitude);
|
|
4496
|
+
}
|
|
4497
|
+
|
|
4498
|
+
/**
|
|
4499
|
+
* @param {Attitude} previousAttitude
|
|
4500
|
+
* @param {Attitude} newAttitude
|
|
4501
|
+
* @returns {boolean}
|
|
4502
|
+
*/
|
|
4503
|
+
static isJump(previousAttitude, newAttitude) {
|
|
4504
|
+
const fromHeading = previousAttitude.heading;
|
|
4505
|
+
const toHeading = newAttitude.heading;
|
|
4506
|
+
const diffAngleHeading = diffAngle(toHeading, fromHeading);
|
|
4507
|
+
const diffTime = newAttitude.time - previousAttitude.time;
|
|
4508
|
+
|
|
4509
|
+
/**
|
|
4510
|
+
* Heading is calculated from two different formulas in function of the pitch angle.
|
|
4511
|
+
* Do not consider a jump if attitude is close to the change of the methods
|
|
4512
|
+
* @see MathsRotations#getHeadingFromQuaternion()
|
|
4513
|
+
*/
|
|
4514
|
+
const [qw, qx, qy, qz] = newAttitude.quaternion;
|
|
4515
|
+
const distToPitchThreshold = Math.abs(Math.asin(2 * (qw * qx + qy * qz)) - Math.PI / 4);
|
|
4516
|
+
if (distToPitchThreshold < AttitudeSmoother.PITCH_UNCERTAINITY_HEADING_THRESHOLD) {
|
|
4517
|
+
return false;
|
|
4518
|
+
}
|
|
4519
|
+
|
|
4520
|
+
return Math.abs(diffAngleHeading) > AttitudeSmoother.ROTATION_SPEED_JUMP_THRESHOLD * diffTime;
|
|
4521
|
+
}
|
|
4522
|
+
|
|
4523
|
+
}
|
|
4524
|
+
|
|
4525
|
+
export { AttitudeSmoother, EventType, PositionSmoother, ProviderEvent, Providers, ProvidersInterface, ProvidersLoggerOld$1 as ProvidersLoggerOld, ProvidersOptions };
|
|
4526
|
+
//# sourceMappingURL=wemap-providers.es.js.map
|