@wemap/providers 4.0.1 → 4.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/debug/dist/index.html +1 -0
  2. package/debug/dist/turn-detection.html +19 -0
  3. package/debug/index.js +2 -0
  4. package/debug/src/AbsoluteAttitudeComponent.jsx +0 -10
  5. package/debug/src/StepDetectionComponent.jsx +3 -3
  6. package/debug/src/TurnDetectionComponent.jsx +47 -0
  7. package/debug/src/Utils.js +24 -0
  8. package/package.json +7 -9
  9. package/src/Providers.js +5 -4
  10. package/src/ProvidersInterface.js +5 -6
  11. package/src/events/EventType.js +5 -1
  12. package/src/events/ProviderEvent.js +7 -0
  13. package/src/mapmatching/MapMatchingHandler.js +370 -53
  14. package/src/providers/Provider.js +42 -8
  15. package/src/providers/Provider.spec.js +3 -4
  16. package/src/providers/attitude/TurnDectector.js +71 -0
  17. package/src/providers/attitude/absolute/AbsoluteAttitude.js +123 -65
  18. package/src/providers/attitude/relative/RelativeAttitude.js +1 -3
  19. package/src/providers/attitude/relative/RelativeAttitudeFromBrowser.js +1 -2
  20. package/src/providers/attitude/relative/RelativeAttitudeFromEkf.js +1 -2
  21. package/src/providers/attitude/relative/RelativeAttitudeFromInertial.js +19 -2
  22. package/src/providers/imu/HighRotationsDetector.js +62 -0
  23. package/src/providers/inclination/Inclination.js +2 -2
  24. package/src/providers/others/CameraNative.js +2 -2
  25. package/src/providers/others/CameraProjectionMatrix.js +2 -2
  26. package/src/providers/position/absolute/AbsolutePosition.js +112 -82
  27. package/src/providers/position/relative/GeoRelativePosition.js +2 -2
  28. package/src/providers/position/relative/Pdr.js +4 -4
  29. package/src/providers/steps/{StepDetection.js → StepDetector.js} +4 -4
  30. package/src/providers/steps/StraightLineDetector.js +84 -0
  31. package/src/smoothers/AttitudeSmoother.js +94 -59
  32. package/src/providers/MetaProvider.js +0 -44
  33. package/src/providers/attitude/absolute/AbsoluteAttitudeFromRelAtt.js +0 -133
  34. package/src/providers/attitude/absolute/AbsoluteAttitudeFused.js +0 -166
  35. package/src/providers/position/absolute/AbsolutePositionFromRel.js +0 -106
@@ -1,30 +1,76 @@
1
+ /* eslint-disable max-statements */
1
2
  import {
2
- MapMatching, Network, UserPosition
3
+ AbsoluteHeading, Edge, Itinerary, MapMatching, Network, Projection, UserPosition
3
4
  } from '@wemap/geo';
4
- import { deg2rad } from '@wemap/maths';
5
+ import { deg2rad, diffAngle } from '@wemap/maths';
6
+ import { TimeUtils } from '@wemap/utils';
5
7
 
6
- import AbsoluteAttitudeFused from '../providers/attitude/absolute/AbsoluteAttitudeFused.js';
8
+ import EventType from '../events/EventType.js';
9
+ import ProviderEvent from '../events/ProviderEvent.js';
10
+
11
+ import AbsoluteAttitude from '../providers/attitude/absolute/AbsoluteAttitude.js';
12
+ import AbsoluteAttitudeFromBrowser from '../providers/attitude/absolute/AbsoluteAttitudeFromBrowser.js';
13
+ import TurnDectector from '../providers/attitude/TurnDectector.js';
14
+ import Constants from '../providers/Constants.js';
7
15
  import AbsolutePosition from '../providers/position/absolute/AbsolutePosition.js';
16
+ import Provider from '../providers/Provider.js';
17
+ import ProviderState from '../providers/ProviderState.js';
18
+ import StepDetector from '../providers/steps/StepDetector.js';
19
+ import StraightLineDetector from '../providers/steps/StraightLineDetector.js';
8
20
  import ProvidersOptions from '../ProvidersOptions.js';
9
21
 
10
- class MapMatchingHandler {
22
+ class MapMatchingHandler extends Provider {
23
+
24
+ /** @type {number} in radians */
25
+ static MM_MAX_ANGLE = deg2rad(30);
11
26
 
12
- static MM_MAX_ANGLE = deg2rad(20);
27
+ /** @type {number} in meters */
13
28
  static MM_MAX_DIST = 30;
29
+
30
+ /** @type {number} in meters */
14
31
  static MM_MIN_DIST = 0;
15
32
 
33
+ /** @type {boolean} */
34
+ static ORIENTATION_MATCHING = true;
35
+
36
+ /** @type {boolean} */
37
+ static USE_ITINERARY_START_AS_POSITION = true;
38
+
39
+ /** @type {number} in meters */
40
+ static MM_HUGE_JUMP_DISTANCE = 3;
41
+
42
+ /** @type {number} */
43
+ static MIN_STEPS_BETWEEN_ORIENTATION_MATCHING = 3;
44
+
45
+ /** @type {number} */
46
+ static MIN_STEPS_FOR_ORIENTATION_MATCHING = 5;
47
+
16
48
  /** @type {MapMatching} */
17
49
  _mapMatching;
18
50
 
19
51
  /** @type {number} */
20
52
  _mapMatchingMinDistance;
21
53
 
54
+ /** @type {boolean} */
55
+ _internalProvidersStarted = false;
22
56
 
23
- constructor() {
57
+ /** @type {number?} */
58
+ _straightLineDetectorProviderId;
24
59
 
25
- if (this.constructor._instance) {
26
- throw new Error(`Cannot instantiate ${this.name} twice`);
27
- }
60
+ /** @type {number?} */
61
+ _turnDetectorProviderId;
62
+
63
+ /** @type {number?} */
64
+ _stepDetectorProviderId;
65
+
66
+ /** @type {Projection[]} */
67
+ _projectionsWithAbsAndWithoutRelAttitudeInARow = [];
68
+
69
+ /** @type {number} */
70
+ _countStepsFromLastMatching = 0;
71
+
72
+ constructor() {
73
+ super();
28
74
 
29
75
  this._mapMatching = new MapMatching();
30
76
  this._mapMatching.maxDistance = MapMatchingHandler.MM_MAX_DIST;
@@ -32,82 +78,353 @@ class MapMatchingHandler {
32
78
  this._mapMatchingMinDistance = MapMatchingHandler.MM_MIN_DIST;
33
79
  }
34
80
 
81
+ /**
82
+ * @override
83
+ */
84
+ start() {
85
+ if (this.network) {
86
+ this._startInternalProviders();
87
+ }
88
+ }
89
+
90
+ /**
91
+ * @override
92
+ */
93
+ stop() {
94
+ this._stopInternalProviders();
95
+ }
96
+
97
+ _manageStartStop = () => {
98
+ if (this.network && !this._internalProvidersStarted) {
99
+ this._startInternalProviders();
100
+ } else if (!this.network && this._internalProvidersStarted) {
101
+ this._stopInternalProviders();
102
+ }
103
+ }
104
+
105
+ _startInternalProviders() {
106
+
107
+ if (this.enabled && this._internalProvidersStarted) {
108
+ return;
109
+ }
110
+
111
+ this._straightLineDetectorProviderId = StraightLineDetector.addEventListener();
112
+ this._turnDetectorProviderId = TurnDectector.addEventListener();
113
+ this._stepDetectorProviderId = StepDetector.addEventListener(() => (this._countStepsFromLastMatching++));
114
+
115
+ this._internalProvidersStarted = true;
116
+ }
117
+
118
+ _stopInternalProviders() {
119
+
120
+ if (this.enabled && !this._internalProvidersStarted) {
121
+ return;
122
+ }
123
+
124
+ StraightLineDetector.removeEventListener(this._straightLineDetectorProviderId);
125
+ TurnDectector.removeEventListener(this._turnDetectorProviderId);
126
+ StepDetector.removeEventListener(this._stepDetectorProviderId);
127
+
128
+ this._internalProvidersStarted = false;
129
+ }
130
+
35
131
  get enabled() {
36
132
  return ProvidersOptions.useMapMatching;
37
133
  }
38
134
 
135
+ /** @type {Network} */
136
+ get network() {
137
+ return this._mapMatching.network;
138
+ }
139
+
140
+ /** @type {Network} */
141
+ set network(network) {
142
+
143
+ this._mapMatching.network = network;
144
+ this.notify(this.createEvent(EventType.Network, network));
145
+
146
+ this._manageStartStop();
147
+
148
+ if (this.canUseMapMatching()) {
149
+ this._notifyPositionFromNetworkInput(network);
150
+ }
151
+ }
152
+
153
+ /**
154
+ * @returns {boolean}
155
+ */
156
+ canUseMapMatching() {
157
+ return this.enabled && this.network;
158
+ }
159
+
39
160
  /**
40
161
  * @param {Network} network
41
162
  */
42
- set network(network) {
43
- this._mapMatching.network = network;
163
+ _notifyPositionFromNetworkInput(network) {
164
+
165
+ const lastEvent = AbsolutePosition.lastEvent;
166
+ const lastPosition = lastEvent ? lastEvent.data : null;
167
+
168
+ const notify = (pos) => {
169
+ const newEvent = lastEvent
170
+ ? lastEvent.clone()
171
+ : new ProviderEvent(EventType.AbsolutePosition);
172
+ newEvent.data = pos;
173
+ AbsolutePosition.notify(newEvent);
174
+ };
44
175
 
45
- if (!this.enabled) {
176
+ let newPosition;
177
+ if (MapMatchingHandler.USE_ITINERARY_START_AS_POSITION
178
+ && network instanceof Itinerary && network.start) {
179
+
180
+ // In case of an itinerary, use itinerary start as new position,
181
+ // but add the distance between lastPosition and itinerary start for the accuracy
182
+ newPosition = UserPosition.fromCoordinates(network.start);
183
+
184
+ if (lastPosition) {
185
+ newPosition.alt = lastPosition.alt;
186
+ newPosition.time = lastPosition.time;
187
+ newPosition.accuracy = lastPosition.accuracy + newPosition.distanceTo(lastPosition);
188
+ newPosition.bearing = lastPosition.bearing;
189
+ } else {
190
+ newPosition.alt = Constants.DEFAULT_ALTITUDE;
191
+ newPosition.time = TimeUtils.preciseTime();
192
+ newPosition.accuracy = 0;
193
+ newPosition.bearing = network.edges[0].bearing;
194
+ }
195
+
196
+ // if the distance between itinerary.start and itinerary.nodes[0] is less than MM_MAX_DIST,
197
+ // projection.projection is itinerary.nodes[0]
198
+ const projection = this.getProjection(newPosition, true);
199
+ if (projection) {
200
+ notify(projection.projection);
201
+ // Do not notify for attitude projection because bearing has not been used.
202
+ } else {
203
+ // This means the first position is far from the network and the user has
204
+ // to reach itinerary.nodes[0] before to continue
205
+ notify(newPosition);
206
+ }
46
207
  return;
47
208
  }
48
209
 
49
- const lastEvent = AbsolutePosition.lastEvent;
50
- if (network !== null && lastEvent) {
51
- const projectedPosition = this.calcProjection(lastEvent.data, true);
52
- if (projectedPosition) {
53
- const newEvent = lastEvent.clone();
54
- newEvent.data = projectedPosition;
55
- AbsolutePosition.notify(newEvent);
210
+ if (!lastPosition) {
211
+ return;
212
+ }
213
+
214
+ newPosition = lastPosition.clone();
215
+ // TODO in function of the lastEvent, if it is from relative or absolute
216
+ }
217
+
218
+ /**
219
+ * @param {ProviderEvent<UserPosition>} newPositionEvent
220
+ */
221
+ notifyPositionFromFeed(newPositionEvent) {
222
+
223
+ const projection = this.getProjection(newPositionEvent.data, true, false);
224
+ if (!projection) {
225
+ AbsolutePosition.notify(newPositionEvent);
226
+ return;
227
+ }
228
+
229
+ AbsolutePosition.notify(this.createEvent(
230
+ EventType.AbsolutePosition,
231
+ projection.projection,
232
+ [newPositionEvent]
233
+ ));
234
+ }
235
+
236
+
237
+ /**
238
+ * @param {ProviderEvent<UserPosition>} positionEvent
239
+ */
240
+ notifyPositionFromAbsolute(positionEvent) {
241
+
242
+ const newPosition = positionEvent.data;
243
+
244
+ let projectionWithBearing = null;
245
+ if (newPosition.bearing !== null) {
246
+ projectionWithBearing = this.getProjection(newPosition, true, true);
247
+ }
248
+
249
+ if (!projectionWithBearing) {
250
+ // Verify if the newPosition is far enough from the network to be used by the system.
251
+ const projectionWithoutBearing = this.getProjection(newPosition, true, false);
252
+ if (!projectionWithoutBearing) {
253
+ // In this case, the newPosition is far enough and can be used safely.
254
+ AbsolutePosition.notify(positionEvent);
56
255
  }
256
+ return;
257
+ }
258
+
259
+ // newPosition must not be used after this line
260
+
261
+ const thisWillBeAHugeJump = projectionWithBearing.distanceFromNearestElement > MapMatchingHandler.MM_HUGE_JUMP_DISTANCE;
262
+
263
+ // In case of a huge jump, be sure the user is in a straight line
264
+ if (thisWillBeAHugeJump && !StraightLineDetector.isStraight()) {
265
+ return;
266
+ }
267
+
268
+ // Detector to avoid big jumps in the wrong direction
269
+ if (thisWillBeAHugeJump && this._detectWrongBigJump(projectionWithBearing)) {
270
+ return;
57
271
  }
272
+
273
+ AbsolutePosition.notify(this.createEvent(
274
+ EventType.AbsolutePosition,
275
+ projectionWithBearing.projection,
276
+ [positionEvent]
277
+ ));
278
+
279
+ this.tryOrientationMatching(projectionWithBearing);
280
+
58
281
  }
59
282
 
283
+
60
284
  /**
61
- * @param {UserPosition} position
62
- * @param {boolean} shouldTryMmOnNodes
63
- * @returns {UserPosition}
285
+ * @param {ProviderEvent<UserPosition>} positionEvent
64
286
  */
65
- calcProjection(position, shouldTryMmOnNodes = false) {
287
+ notifyPositionFromRelative(positionEvent) {
288
+
289
+ if (TurnDectector.isTurning()) {
290
+ this._projectionsWithAbsAndWithoutRelAttitudeInARow = [];
291
+ AbsolutePosition.notify(positionEvent);
292
+ return;
293
+ }
294
+
295
+ const newPosition = positionEvent.data;
296
+ const projection = this.getProjection(newPosition, true, true);
297
+
298
+ if (projection) {
299
+ this._projectionsWithAbsAndWithoutRelAttitudeInARow = [];
66
300
 
67
- let projection = null;
68
- let bearingUsedForProjection = false;
301
+ const thisWillBeAHugeJump = projection.distanceFromNearestElement > MapMatchingHandler.MM_HUGE_JUMP_DISTANCE;
69
302
 
70
- if (!this.enabled || !this._mapMatching.network) {
71
- return null;
303
+ // In case of a huge jump, be sure the user is in a straight line
304
+ if (thisWillBeAHugeJump && !StraightLineDetector.isStraight()) {
305
+ AbsolutePosition.notify(positionEvent);
306
+ return;
307
+ }
308
+
309
+ // Detector to avoid big jumps in the wrong direction
310
+ if (thisWillBeAHugeJump && this._detectWrongBigJump(projection)) {
311
+ AbsolutePosition.notify(positionEvent);
312
+ return;
313
+ }
314
+
315
+ AbsolutePosition.notify(this.createEvent(
316
+ EventType.AbsolutePosition,
317
+ projection.projection,
318
+ [positionEvent]
319
+ ));
320
+ this.tryOrientationMatching(projection);
321
+
322
+ } else {
323
+
324
+ // Sometimes, the newPosition.bearing diverges due to the Absolute Attitude offset
325
+ // and the Orientation Matching. So, we created this detector to check if projection
326
+ // with bearing from "AbsoluteAttitudeFromBrowser" is better than the current bearing.
327
+ // /!\ This works only if the user is waking in the same direction than the smartphone orientation /!\
328
+ if (StraightLineDetector.isStraight()) {
329
+
330
+ const testedPosition = newPosition.clone();
331
+ testedPosition.bearing = AbsoluteAttitudeFromBrowser.lastEvent.data.heading;
332
+ const projectionWithAbs = this.getProjection(testedPosition, true, true);
333
+
334
+ if (projectionWithAbs) {
335
+
336
+ this._projectionsWithAbsAndWithoutRelAttitudeInARow.push(projectionWithAbs);
337
+ if (this._projectionsWithAbsAndWithoutRelAttitudeInARow.length < 3) {
338
+ return;
339
+ }
340
+
341
+ AbsolutePosition.notify(this.createEvent(
342
+ EventType.AbsolutePosition,
343
+ projectionWithAbs.projection,
344
+ [positionEvent]
345
+ ));
346
+
347
+ this.tryOrientationMatching(projectionWithAbs);
348
+ return;
349
+ }
350
+ }
351
+
352
+ this._projectionsWithAbsAndWithoutRelAttitudeInARow = [];
353
+
354
+ // If no projection found with both projection methods, simply use the newPosition.
355
+ AbsolutePosition.notify(positionEvent);
72
356
  }
73
357
 
74
- // Firstly, if new position bearing is known, try to use map-matching with bearing
75
- // That is not always the case, for e.g. the first position returned by the GnssWifi provider.
76
- if (position.bearing !== null) {
77
- projection = this._mapMatching.getProjection(position, true, true);
78
- bearingUsedForProjection = true;
358
+ }
359
+
360
+ /**
361
+ * @param {Projection} projection
362
+ */
363
+ _detectWrongBigJump(projection) {
364
+
365
+ if (this.network instanceof Itinerary && AbsolutePosition.lastEvent) {
366
+ const itinerary = this.network;
367
+ const infoPrevious = itinerary.getInfo(AbsolutePosition.lastEvent.data);
368
+ const infoProjection = itinerary.getInfo(projection.projection);
369
+ if (infoPrevious
370
+ && infoProjection
371
+ && infoPrevious.traveledDistance > infoProjection.traveledDistance
372
+ && (infoPrevious.traveledDistance - infoProjection.traveledDistance) > projection.origin.accuracy
373
+ && projection.distanceFromNearestElement > projection.origin.accuracy
374
+ && projection.origin.distanceTo(AbsolutePosition.lastEvent.data) < projection.origin.accuracy + AbsolutePosition.lastEvent.data.accuracy) {
375
+
376
+ return true;
377
+ }
79
378
  }
80
379
 
81
- // Secondly, if map-matching with bearing did not work, try MM on nodes (without bearing).
82
- // Note: this is not allowed for positions from AbsolutePositionFromRel to allow
83
- // user to leave the network.
84
- if (!projection && shouldTryMmOnNodes) {
85
- projection = this._mapMatching.getProjection(position, true, false);
86
- bearingUsedForProjection = false;
380
+ return false;
381
+ }
382
+
383
+
384
+ /**
385
+ * @param {Projection} projection
386
+ */
387
+ tryOrientationMatching(projection) {
388
+
389
+ if (!MapMatchingHandler.ORIENTATION_MATCHING) {
390
+ return;
87
391
  }
88
392
 
89
- // No projection found
90
- if (!projection) {
91
- return null;
393
+ if (this.state !== ProviderState.STARTED
394
+ || this._countStepsFromLastMatching < MapMatchingHandler.MIN_STEPS_BETWEEN_ORIENTATION_MATCHING
395
+ || StraightLineDetector.numStepsDetectedFromLastTurn < MapMatchingHandler.MIN_STEPS_FOR_ORIENTATION_MATCHING) {
396
+ return;
92
397
  }
93
398
 
94
- if (bearingUsedForProjection) {
95
- AbsoluteAttitudeFused.mapMatching(projection);
399
+ const { nearestElement, origin } = projection;
400
+ if (!(nearestElement instanceof Edge)) {
401
+ return;
96
402
  }
97
403
 
98
- // Do not use projection if it too close from itinerary,
99
- // this allows left/right movements (ie with ArCore)
100
- if (projection.distanceFromNearestElement < this._mapMatchingMinDistance) {
101
- return null;
404
+ let matchingDirection;
405
+ const matchingDirectionAngle1 = diffAngle(nearestElement.bearing, origin.bearing);
406
+ const matchingDirectionAngle2 = diffAngle(nearestElement.bearing + Math.PI, origin.bearing);
407
+
408
+ if (Math.abs(matchingDirectionAngle1) < Math.abs(matchingDirectionAngle2)) {
409
+ matchingDirection = nearestElement.bearing;
410
+ } else {
411
+ matchingDirection = (nearestElement.bearing + Math.PI) % (2 * Math.PI);
102
412
  }
103
413
 
104
- // Do not use projection.projection directly, because position has some specific properties like time, bearing and altitude
105
- const projectedPosition = position.clone();
106
- projectedPosition.lat = projection.projection.lat;
107
- projectedPosition.lng = projection.projection.lng;
108
- projectedPosition.level = projection.projection.level;
414
+ const matchedHeading = new AbsoluteHeading(
415
+ matchingDirection,
416
+ origin.time,
417
+ 0
418
+ // Math.min(Math.abs(matchingDirectionAngle1), Math.abs(matchingDirectionAngle2))
419
+ );
420
+
421
+ AbsoluteAttitude.feed(new ProviderEvent(EventType.AbsoluteHeading, matchedHeading));
422
+
423
+ this._countStepsFromLastMatching = 0;
424
+ }
109
425
 
110
- return projectedPosition;
426
+ getProjection(position, useDistance, useBearing) {
427
+ return this._mapMatching.getProjection(position, useDistance, useBearing);
111
428
  }
112
429
 
113
430
  get maxDistance() {
@@ -1,5 +1,3 @@
1
- import noop from 'lodash.noop';
2
-
3
1
  import EventType from '../events/EventType.js';
4
2
  import ProviderEvent from '../events/ProviderEvent.js';
5
3
  import ProvidersLoggerOld from '../events/ProvidersLoggerOld.js';
@@ -14,11 +12,22 @@ import ProviderState from './ProviderState.js';
14
12
  */
15
13
  class Provider {
16
14
 
15
+ /** @type {number} */
17
16
  static _callbackUniqueId = 0;
17
+
18
+ /** @type {number} */
18
19
  static _uniqueId = 1;
19
20
 
21
+
22
+ /** @type {number} */
23
+ id;
24
+
25
+ /** @type {number} */
20
26
  state = ProviderState.STOPPPED;
21
27
 
28
+ /** @type {ProviderEvent[]} */
29
+ _lastEvents = null;
30
+
22
31
  _eventsCallbacks = [];
23
32
  _monitoringCallbacks = [];
24
33
 
@@ -38,12 +47,13 @@ class Provider {
38
47
 
39
48
  /**
40
49
  * Get the provider name
41
- * @returns {String} the provider name
50
+ * @type {String} the provider name
42
51
  */
43
52
  static get pname() {
44
53
  return 'Unknown';
45
54
  }
46
55
 
56
+ /** @type {String} */
47
57
  get pname() {
48
58
  return this.constructor.pname;
49
59
  }
@@ -59,7 +69,7 @@ class Provider {
59
69
  }
60
70
 
61
71
  /**
62
- * @returns {Promise} returns an availability promise
72
+ * @type {Promise} returns an availability promise
63
73
  */
64
74
  get availability() {
65
75
  if (ProvidersOptions.ignoreProviders.includes(this.pname)) {
@@ -69,13 +79,14 @@ class Provider {
69
79
  return this._availability;
70
80
  }
71
81
 
82
+ /** @type {Promise} */
72
83
  get _availability() {
73
84
  return Promise.resolve();
74
85
  }
75
86
 
76
87
  /**
77
88
  * Create an event from current provider
78
- * @param {ProviderEvent#DataType} dataType type of ProviderEvent
89
+ * @param {String} dataType type of ProviderEvent (from EventType)
79
90
  * @param {Object} data data exported to ProviderEvent
80
91
  * @returns {ProviderEvent}
81
92
  * @protected
@@ -119,7 +130,7 @@ class Provider {
119
130
  * @param {Boolean} startIfNecessary
120
131
  * @returns {Number}
121
132
  */
122
- addEventListener(onEvents = noop, onError = noop, startIfNecessary = true) {
133
+ addEventListener(onEvents = () => {}, onError = () => {}, startIfNecessary = true) {
123
134
  const id = ++Provider._callbackUniqueId;
124
135
 
125
136
  /**
@@ -168,7 +179,7 @@ class Provider {
168
179
  this.notifyError(e);
169
180
  })
170
181
  // notifyError can throw an error if onStop is not defined
171
- .catch(() => noop);
182
+ .catch(() => {});
172
183
 
173
184
  return id;
174
185
  }
@@ -221,7 +232,7 @@ class Provider {
221
232
  * @param {Function} onStopped
222
233
  * @returns {Number}
223
234
  */
224
- addMonitoringListener(onStarted = noop, onStopped = noop) {
235
+ addMonitoringListener(onStarted = () => {}, onStopped = () => {}) {
225
236
  const id = ++this.constructor._callbackUniqueId;
226
237
  this._monitoringCallbacks.push({
227
238
  id,
@@ -269,6 +280,9 @@ class Provider {
269
280
 
270
281
  // Notify callbacks
271
282
  this._eventsCallbacks.forEach(({ onEvents }) => onEvents(events));
283
+
284
+ // Keep a trace of the last events.
285
+ this._lastEvents = events;
272
286
  }
273
287
 
274
288
  /**
@@ -285,6 +299,26 @@ class Provider {
285
299
  onError(error);
286
300
  });
287
301
  }
302
+
303
+ /** @type {ProviderEvent} */
304
+ get lastEvent() {
305
+ return this._lastEvents ? this._lastEvents[0] : null;
306
+ }
307
+
308
+ /** @type {ProviderEvent[]} */
309
+ get lastEvents() {
310
+ return this._lastEvents;
311
+ }
312
+
313
+ /**
314
+ * Input data from external interface
315
+ * @param {ProviderEvent} event
316
+ * @param {EventType} eventType
317
+ */
318
+ // eslint-disable-next-line no-unused-vars
319
+ feed(event, eventType) {
320
+ throw new Error('Not implemented');
321
+ }
288
322
  }
289
323
 
290
324
  export default Provider;
@@ -1,7 +1,6 @@
1
1
  /* eslint-disable max-nested-callbacks */
2
2
  import chai from 'chai';
3
3
  import chaiAsPromised from 'chai-as-promised';
4
- import noop from 'lodash.noop';
5
4
 
6
5
  import Logger from '@wemap/logger';
7
6
 
@@ -55,7 +54,7 @@ describe('Provider', () => {
55
54
  * checkAvailabilityOnStart = true
56
55
  */
57
56
  let errored = false;
58
- FakeProvider2.addEventListener(noop, () => (errored = true));
57
+ FakeProvider2.addEventListener(() => {}, () => (errored = true));
59
58
 
60
59
  await sleep(2);
61
60
 
@@ -69,7 +68,7 @@ describe('Provider', () => {
69
68
  ProvidersOptions.checkAvailabilityOnStart = false;
70
69
 
71
70
  errored = false;
72
- const id = FakeProvider2.addEventListener(noop, () => (errored = true));
71
+ const id = FakeProvider2.addEventListener(() => {}, () => (errored = true));
73
72
 
74
73
  await sleep(2);
75
74
 
@@ -112,7 +111,7 @@ describe('Provider', () => {
112
111
 
113
112
  it('start/stop', async () => {
114
113
  let errored = false;
115
- FakeProvider1.addEventListener(noop, (errored = true));
114
+ FakeProvider1.addEventListener(() => {}, (errored = true));
116
115
  await sleep(2);
117
116
  expect(errored).is.true;
118
117