@wemap/providers 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. package/babel.config.js +11 -0
  2. package/config.json +4 -0
  3. package/debug/absolute-attitude.html +16 -0
  4. package/debug/absolute-position.html +16 -0
  5. package/debug/attitude.html +16 -0
  6. package/debug/components/AbsoluteAttitudeComponent.jsx +142 -0
  7. package/debug/components/AbsolutePositionComponent.jsx +79 -0
  8. package/debug/components/AttitudeComponent.jsx +40 -0
  9. package/debug/components/Common.css +27 -0
  10. package/debug/components/GnssWifiComponent.jsx +53 -0
  11. package/debug/components/ImuComponent.jsx +53 -0
  12. package/debug/components/InclinationComponent.jsx +68 -0
  13. package/debug/components/MapComponent.jsx +366 -0
  14. package/debug/components/NavigationConfig.js +112 -0
  15. package/debug/components/PoseComponent.jsx +168 -0
  16. package/debug/components/RelativeAttitudeComponent.jsx +85 -0
  17. package/debug/components/StartStopComponent.jsx +45 -0
  18. package/debug/components/StepDetectionComponent.jsx +39 -0
  19. package/debug/components/Utils.js +216 -0
  20. package/debug/components/index.js +30 -0
  21. package/debug/components/old/PositioningComponent.jsx +29 -0
  22. package/debug/components/old/PositioningInclinationComponent.jsx +82 -0
  23. package/debug/components/old/PositioningPoseComponent.jsx +117 -0
  24. package/debug/gnss-wifi.html +16 -0
  25. package/debug/imu.html +16 -0
  26. package/debug/inclination.html +16 -0
  27. package/debug/pose.html +16 -0
  28. package/debug/positioning-legacy.html +16 -0
  29. package/debug/relative-attitude.html +16 -0
  30. package/debug/step-detection.html +16 -0
  31. package/index.js +7 -0
  32. package/package.json +67 -0
  33. package/src/Providers.js +80 -0
  34. package/src/ProvidersInterface.js +125 -0
  35. package/src/ProvidersOptions.js +29 -0
  36. package/src/errors/AskImuOnDesktopError.js +9 -0
  37. package/src/errors/ContainsIgnoredProviderError.js +9 -0
  38. package/src/errors/GeolocationApiMissingError.js +9 -0
  39. package/src/errors/GeolocationPermissionDeniedError.js +9 -0
  40. package/src/errors/GeolocationPositionUnavailableError.js +9 -0
  41. package/src/errors/IpResolveServerError.js +9 -0
  42. package/src/errors/MissingAccelerometerError.js +11 -0
  43. package/src/errors/MissingArCoreError.js +9 -0
  44. package/src/errors/MissingGyroscopeError.js +11 -0
  45. package/src/errors/MissingMagnetometerError.js +9 -0
  46. package/src/errors/MissingNativeInterfaceError.js +9 -0
  47. package/src/errors/MissingSensorError.js +14 -0
  48. package/src/errors/NoProviderFoundError.js +9 -0
  49. package/src/events/Availability.js +44 -0
  50. package/src/events/EventType.js +33 -0
  51. package/src/events/ProviderEvent.js +32 -0
  52. package/src/events/ProvidersLogger.js +83 -0
  53. package/src/providers/Constants.js +5 -0
  54. package/src/providers/FakeProvider.spec.js +57 -0
  55. package/src/providers/MetaProvider.js +42 -0
  56. package/src/providers/Provider.js +314 -0
  57. package/src/providers/Provider.spec.js +136 -0
  58. package/src/providers/ProviderState.js +5 -0
  59. package/src/providers/attitude/AttitudeProvider.js +63 -0
  60. package/src/providers/attitude/EkfAttitude.js +224 -0
  61. package/src/providers/attitude/EkfAttitude.spec.js +114 -0
  62. package/src/providers/attitude/absolute/AbsoluteAttitudeFromBrowserProvider.js +224 -0
  63. package/src/providers/attitude/absolute/AbsoluteAttitudeFromRelAttProvider.js +134 -0
  64. package/src/providers/attitude/absolute/AbsoluteAttitudeProvider.js +143 -0
  65. package/src/providers/attitude/relative/RelativeAttitudeFromBrowserProvider.js +89 -0
  66. package/src/providers/attitude/relative/RelativeAttitudeFromEkfProvider.js +114 -0
  67. package/src/providers/attitude/relative/RelativeAttitudeProvider.js +103 -0
  68. package/src/providers/imu/AccelerometerProvider.js +61 -0
  69. package/src/providers/imu/GyroscopeProvider.js +61 -0
  70. package/src/providers/imu/ImuProvider.js +122 -0
  71. package/src/providers/inclination/InclinationFromAccProvider.js +87 -0
  72. package/src/providers/inclination/InclinationFromAttitudeProvider.js +77 -0
  73. package/src/providers/inclination/InclinationProvider.js +69 -0
  74. package/src/providers/legacy/AbsolutePdrProvider.js +258 -0
  75. package/src/providers/legacy/ArCoreAbsoluteProvider.js +230 -0
  76. package/src/providers/legacy/GnssWifiPdrProvider.js +217 -0
  77. package/src/providers/legacy/MapMatchingProvider.js +65 -0
  78. package/src/providers/legacy/PdrProvider.old.js +300 -0
  79. package/src/providers/legacy/PoseProvider.js +68 -0
  80. package/src/providers/legacy/helpers/HeadingUnlocker.js +47 -0
  81. package/src/providers/legacy/helpers/HeadingUnlocker.spec.js +53 -0
  82. package/src/providers/legacy/helpers/Smoother.js +92 -0
  83. package/src/providers/legacy/helpers/Smoother.spec.js +426 -0
  84. package/src/providers/legacy/helpers/ThugDetector.js +37 -0
  85. package/src/providers/others/CameraNativeProvider.js +44 -0
  86. package/src/providers/position/absolute/AbsolutePositionFromRelProvider.js +109 -0
  87. package/src/providers/position/absolute/AbsolutePositionProvider.js +172 -0
  88. package/src/providers/position/absolute/GnssWifiProvider.js +122 -0
  89. package/src/providers/position/absolute/IpProvider.js +68 -0
  90. package/src/providers/position/relative/ArCoreProvider.js +197 -0
  91. package/src/providers/position/relative/GeoRelativePositionFromArCoreProvider.js +85 -0
  92. package/src/providers/position/relative/GeoRelativePositionProvider.js +66 -0
  93. package/src/providers/position/relative/PdrProvider.js +132 -0
  94. package/src/providers/steps/StepDetectionLadetto.js +67 -0
  95. package/src/providers/steps/StepDetectionMinMaxPeaks.js +80 -0
  96. package/src/providers/steps/StepDetectionMinMaxPeaks2.js +108 -0
  97. package/src/providers/steps/StepDetectionProvider.js +100 -0
  98. package/src/smoothers/PositionSmoother.js +86 -0
  99. package/src/smoothers/PositionSmoother.spec.js +55 -0
  100. package/webpack/webpack.common.js +20 -0
  101. package/webpack/webpack.dev.js +24 -0
  102. package/webpack/webpack.prod.js +15 -0
@@ -0,0 +1,132 @@
1
+ import { GeoRelativePosition } from '@wemap/geo';
2
+ import { deg2rad } from '@wemap/maths';
3
+
4
+ import Provider from '../../Provider';
5
+ import Availability from '../../../events/Availability';
6
+ import EventType from '../../../events/EventType';
7
+ import {
8
+ StepDetection, AbsoluteAttitude
9
+ } from '../../../Providers';
10
+
11
+ class PdrProvider extends Provider {
12
+
13
+ // https://ieeexplore.ieee.org/document/7346766
14
+ misalignment = [1, 0, 0, 0];
15
+ misalignmentError = deg2rad(3);
16
+
17
+ /**
18
+ * @override
19
+ */
20
+ static get displayName() {
21
+ return 'PDR';
22
+ }
23
+
24
+ /**
25
+ * @override
26
+ */
27
+ static get eventsType() {
28
+ return [EventType.RelativePosition];
29
+ }
30
+
31
+ /**
32
+ * @override
33
+ */
34
+ get _availability() {
35
+ return Availability.merge(
36
+ StepDetection.availability,
37
+ AbsoluteAttitude.availability
38
+ );
39
+ }
40
+
41
+
42
+ /**
43
+ * @override
44
+ */
45
+ start() {
46
+
47
+ this.stepDetectionProviderId = StepDetection.addEventListener(
48
+ events => this.onStepEvent(events[0]),
49
+ this.notifyError,
50
+ this.name);
51
+
52
+ this.absoluteAttitudeProviderId = AbsoluteAttitude.addEventListener(
53
+ events => (this.attitudeEvent = events[0]),
54
+ this.notifyError,
55
+ this.name);
56
+
57
+ }
58
+
59
+ /**
60
+ * @override
61
+ */
62
+ stop() {
63
+ StepDetection.removeEventListener(this.stepDetectionProviderId);
64
+ AbsoluteAttitude.removeEventListener(this.absoluteAttitudeProviderId);
65
+ }
66
+
67
+ /**
68
+ * @private
69
+ */
70
+ onStepEvent = stepEvent => {
71
+
72
+ if (!this.attitudeEvent) {
73
+ return;
74
+ }
75
+
76
+ const timestamp = stepEvent.timestamp;
77
+ const stepSize = stepEvent.data.size;
78
+
79
+ /**
80
+ * There is three frames because (device heading != walking direction):
81
+ *
82
+ * Device frame (x-Device Right, y-Device Top, z-Device Front)
83
+ * Navigation frame (x-Nav East, y-Nav North, z-Nav Up)
84
+ * Earth local frame (x-East y-North z-Up)
85
+ *
86
+ *
87
+ * For the PDR, in order to find the step direction,
88
+ * we are looking for the rotation between the device frame and the navigation frame.
89
+ * Attitude is the rotation from the device frame to the earth local frame.
90
+ * Misalignment is the rotation from the navigation frame to the earth local frame
91
+ */
92
+ const deviceAttitude = this.attitudeEvent.data;
93
+
94
+ /**
95
+ * For optimisation, as we define misalignment to identity, we do not process the quat mulitply formula.
96
+ */
97
+ // const deviceInNavFrame = Quaternion.multiply(deviceAttitude.quaternion, this.misalignment);
98
+ // const deviceDirection = new Attitude(deviceInNavFrame).heading;
99
+ const deviceDirection = deviceAttitude.heading;
100
+
101
+ /**
102
+ * A bad accuracy from PDR is due to three things:
103
+ * - Attitude accuracy
104
+ * - Misalignement (device heading != walking direction)
105
+ * - Step detection false positives / false negatives
106
+ * Following formula only use attitude accuracy with cone formula
107
+ */
108
+ const deviceDirectionAccuracy = deviceAttitude.accuracy + this.misalignmentError;
109
+ const accuracy = (stepSize / 2) * Math.sin(deviceDirectionAccuracy / 2);
110
+
111
+ /**
112
+ * Relative position is defined in ENU frame
113
+ */
114
+ const position = new GeoRelativePosition(
115
+ stepSize * Math.sin(deviceDirection),
116
+ stepSize * Math.cos(deviceDirection),
117
+ 0,
118
+ timestamp,
119
+ accuracy
120
+ );
121
+
122
+ this.notify(this.createEvent(
123
+ EventType.GeoRelativePosition,
124
+ position,
125
+ timestamp,
126
+ [stepEvent, this.attitudeEvent]
127
+ ));
128
+ }
129
+
130
+ }
131
+
132
+ export default PdrProvider;
@@ -0,0 +1,67 @@
1
+
2
+ // in seconds
3
+ const MIN_TIME_BETWEEN_STEPS = 0.4;
4
+
5
+ // in Hz
6
+ const MAX_FRENQUENCY = 4;
7
+ const MIN_FRENQUENCY = 1;
8
+
9
+ // in m.s-2
10
+ const VERTICAL_ACC_POSITIVE_PEAK_THRESHOLD = 1;
11
+
12
+
13
+ class StepDetectionLadetto {
14
+
15
+ lastVerticalAcc = 0;
16
+ maxAcceleration = 0;
17
+ lastStepTimestamp = -MIN_TIME_BETWEEN_STEPS;
18
+
19
+ compute(timestamp, linearAcc) {
20
+
21
+ let stepDetected = false;
22
+
23
+ const verticalAcc = linearAcc[2];
24
+ const timeInterval = timestamp - this.lastStepTimestamp;
25
+
26
+ const upfront = verticalAcc > this.lastVerticalAcc;
27
+
28
+ if (upfront) {
29
+ if (verticalAcc > this.maxAcceleration) {
30
+ this.maxAcceleration = verticalAcc;
31
+ }
32
+ } else if (this.maxAcceleration > VERTICAL_ACC_POSITIVE_PEAK_THRESHOLD
33
+ && timeInterval > MIN_TIME_BETWEEN_STEPS) {
34
+ this.maxAcceleration = 0;
35
+
36
+ const diffTime = timestamp - this.lastStepTimestamp;
37
+ this.frequency = Math.min(Math.max((1 / diffTime), MIN_FRENQUENCY), MAX_FRENQUENCY);
38
+
39
+ stepDetected = true;
40
+ this.lastStepTimestamp = timestamp;
41
+ } else {
42
+ this.maxAcceleration = 0;
43
+ }
44
+
45
+ this.lastVerticalAcc = verticalAcc;
46
+
47
+ return stepDetected;
48
+ }
49
+
50
+ get lastStepSize() {
51
+
52
+ if (!this.frequency) {
53
+ return 0;
54
+ }
55
+
56
+ const kParamA = 0.45;
57
+ const kParamB = 0.2;
58
+ return kParamA + kParamB * this.frequency;
59
+ }
60
+
61
+ get speed() {
62
+ return this.lastStepTimestamp ? this.lastStepSize * this.frequency : 0;
63
+ }
64
+
65
+ }
66
+
67
+ export default StepDetectionLadetto;
@@ -0,0 +1,80 @@
1
+
2
+ // in seconds
3
+ const WINDOW_TIME = 0.3;
4
+
5
+ // in seconds
6
+ const MIN_TIME_BETWEEN_STEPS = 0.4;
7
+
8
+ // in Hz
9
+ const MAX_FRENQUENCY = 4;
10
+ const MIN_FRENQUENCY = 1;
11
+
12
+ // in m.s-2
13
+ const VERTICAL_ACC_POSITIVE_PEAK_THRESHOLD = 1.5;
14
+ const VERTICAL_ACC_NEGATIVE_PEAK_THRESHOLD = -1;
15
+
16
+
17
+ class StepDetectionMinMaxPeaks {
18
+
19
+ constructor() {
20
+ this.slidingWindow = [];
21
+
22
+ this.lastStepTimestamp = -MIN_TIME_BETWEEN_STEPS;
23
+ }
24
+
25
+
26
+ compute(timestamp, linearAcc) {
27
+
28
+ if (this.lastStepTimestamp && this.lastStepTimestamp + MIN_TIME_BETWEEN_STEPS > timestamp) {
29
+ return false;
30
+ }
31
+
32
+ let maxValue = Number.MIN_SAFE_INTEGER;
33
+ let minValue = Number.MAX_SAFE_INTEGER;
34
+
35
+ const windowTime = WINDOW_TIME;
36
+ this.slidingWindow.forEach(function(item, index, object) {
37
+ if (item.timestamp < timestamp - windowTime) {
38
+ object.splice(index, 1);
39
+ } else {
40
+ maxValue = Math.max(item.verticalAcc, maxValue);
41
+ minValue = Math.min(item.verticalAcc, minValue);
42
+ }
43
+ });
44
+ this.slidingWindow.push({
45
+ timestamp: timestamp,
46
+ verticalAcc: linearAcc[2]
47
+ });
48
+
49
+
50
+ if (maxValue > VERTICAL_ACC_POSITIVE_PEAK_THRESHOLD
51
+ && minValue < VERTICAL_ACC_NEGATIVE_PEAK_THRESHOLD) {
52
+
53
+ const timeInterval = this.lastStepTimestamp ? timestamp - this.lastStepTimestamp : 1;
54
+ this.frequency = Math.min(Math.max((1 / timeInterval), MIN_FRENQUENCY), MAX_FRENQUENCY);
55
+
56
+ this.lastStepTimestamp = timestamp;
57
+ return true;
58
+ }
59
+
60
+ return false;
61
+ }
62
+
63
+ get lastStepSize() {
64
+
65
+ if (!this.frequency) {
66
+ return 0;
67
+ }
68
+
69
+ const kParamA = 0.45;
70
+ const kParamB = 0.2;
71
+ return kParamA + kParamB * this.frequency;
72
+ }
73
+
74
+ get speed() {
75
+ return this.lastStepTimestamp ? this.lastStepSize * this.frequency : 0;
76
+ }
77
+
78
+ }
79
+
80
+ export default StepDetectionMinMaxPeaks;
@@ -0,0 +1,108 @@
1
+
2
+ // in seconds
3
+ const WINDOW_TIME = 0.3;
4
+
5
+ // in seconds
6
+ const MIN_TIME_BETWEEN_STEPS = 0.4;
7
+
8
+ // in Hz
9
+ const MAX_FRENQUENCY = 4;
10
+ const MIN_FRENQUENCY = 1;
11
+
12
+ // in m.s-2
13
+ const VERTICAL_ACC_POSITIVE_PEAK_THRESHOLD = 0.75;
14
+ const VERTICAL_ACC_NEGATIVE_PEAK_THRESHOLD = -0.3;
15
+
16
+
17
+ class StepDetectionMinMaxPeaks2 {
18
+
19
+ constructor() {
20
+ this.slidingWindow = [];
21
+
22
+ this.lastStepTimestamp = -MIN_TIME_BETWEEN_STEPS;
23
+ this.previousVerticalAcc = 0;
24
+ this.influence = 0.2;
25
+ }
26
+
27
+
28
+ compute(timestamp, linearAcc, angularRate) {
29
+
30
+ const verticalAcc = this.influence * (linearAcc[2] * 2) + (1 - this.influence) * this.previousVerticalAcc;
31
+ this.previousVerticalAcc = verticalAcc;
32
+
33
+
34
+ if (Math.sqrt(angularRate[0] ** 2 + angularRate[1] ** 2 + angularRate[2] ** 2) > 0.75) {
35
+ return false;
36
+ }
37
+
38
+ if (this.lastStepTimestamp && this.lastStepTimestamp + MIN_TIME_BETWEEN_STEPS > timestamp) {
39
+ return false;
40
+ }
41
+
42
+ let maxValue = Number.MIN_SAFE_INTEGER;
43
+ let minValue = Number.MAX_SAFE_INTEGER;
44
+
45
+ this.slidingWindow.forEach(function(item, index, object) {
46
+ if (item.timestamp < timestamp - WINDOW_TIME) {
47
+ object.splice(index, 1);
48
+ } else {
49
+ maxValue = Math.max(item.verticalAcc, maxValue);
50
+ minValue = Math.min(item.verticalAcc, minValue);
51
+ }
52
+ });
53
+ this.slidingWindow.push({
54
+ timestamp: timestamp,
55
+ verticalAcc: verticalAcc
56
+ });
57
+
58
+
59
+ if (maxValue > VERTICAL_ACC_POSITIVE_PEAK_THRESHOLD
60
+ && minValue < VERTICAL_ACC_NEGATIVE_PEAK_THRESHOLD) {
61
+
62
+ const timeInterval = this.lastStepTimestamp ? timestamp - this.lastStepTimestamp : 1;
63
+ this.frequency = Math.min(Math.max((1 / timeInterval), MIN_FRENQUENCY), MAX_FRENQUENCY);
64
+
65
+ this.lastStepTimestamp = timestamp;
66
+ return true;
67
+ }
68
+
69
+ return false;
70
+ }
71
+
72
+ get lastStepSize() {
73
+
74
+ if (!this.frequency) {
75
+ return 0;
76
+ }
77
+
78
+ const kParamA = 0.45;
79
+ const kParamB = 0.2;
80
+ return kParamA + kParamB * this.frequency;
81
+ }
82
+
83
+ get speed() {
84
+ return this.lastStepSize && this.frequency ? this.lastStepSize * this.frequency : 0;
85
+ }
86
+
87
+ mean(data) {
88
+ let sum = 0.0, mean = 0.0;
89
+
90
+ for (let i = 0; i < data.length; ++i) {
91
+ sum += data[i].verticalAcc;
92
+ }
93
+ mean = sum / data.length;
94
+ return mean;
95
+ }
96
+
97
+ stddev(data) {
98
+ const theMean = this.mean(data);
99
+ let standardDeviation = 0;
100
+ for (let i = 0; i < data.length; ++i) {
101
+ standardDeviation += (data[i].verticalAcc - theMean) ** 2;
102
+ }
103
+
104
+ return Math.sqrt(standardDeviation / data.length);
105
+ }
106
+ }
107
+
108
+ export default StepDetectionMinMaxPeaks2;
@@ -0,0 +1,100 @@
1
+ import { Constants as GeoConstants } from '@wemap/geo';
2
+ import { Quaternion } from '@wemap/maths';
3
+
4
+ import Availability from '../../events/Availability';
5
+ import EventType from '../../events/EventType';
6
+ import Provider from '../Provider';
7
+ import StepDetectionMinMaxPeaks2 from './StepDetectionMinMaxPeaks2';
8
+ import {
9
+ Accelerometer, Attitude, Gyroscope
10
+ } from '../../Providers';
11
+
12
+ class StepDetectionProvider extends Provider {
13
+
14
+ constructor() {
15
+ super();
16
+ this.stepDetector = new StepDetectionMinMaxPeaks2();
17
+ }
18
+
19
+ /**
20
+ * @override
21
+ */
22
+ get _availability() {
23
+ return Availability.merge(
24
+ Accelerometer.availability,
25
+ Gyroscope.availability,
26
+ Attitude.availability
27
+ );
28
+ }
29
+
30
+ /**
31
+ * @override
32
+ */
33
+ start() {
34
+
35
+ this.numOfSteps = 0;
36
+
37
+ this.accelerometerProviderId = Accelerometer.addEventListener(
38
+ events => this.onAccelerometerEvent(events[0]),
39
+ this.notifyError,
40
+ this.name);
41
+
42
+ this.gyroscopeProviderId = Gyroscope.addEventListener(
43
+ events => (this.angularRateEvent = events[0]),
44
+ this.notifyError,
45
+ this.name);
46
+
47
+ this.attitudeProviderId = Attitude.addEventListener(
48
+ events => (this.attitudeEvent = events[0]),
49
+ this.notifyError,
50
+ this.name);
51
+ }
52
+
53
+ /**
54
+ * @override
55
+ */
56
+ stop() {
57
+ Accelerometer.removeEventListener(this.accelerometerProviderId);
58
+ Gyroscope.removeEventListener(this.gyroscopeProviderId);
59
+ Attitude.removeEventListener(this.attitudeProviderId);
60
+ }
61
+
62
+ onAccelerometerEvent(accelerationEvent) {
63
+
64
+ if (!this.attitudeEvent || !this.angularRateEvent) {
65
+ return;
66
+ }
67
+
68
+ const { timestamp } = accelerationEvent;
69
+ const acceleration = accelerationEvent.data;
70
+
71
+ /**
72
+ * Step Detection and Step Size Detection
73
+ */
74
+ const linearAcc = this.constructor.computeLinearAcceleration(
75
+ this.attitudeEvent.data.quaternion, acceleration);
76
+ const stepDetected = this.stepDetector.compute(timestamp, linearAcc, this.angularRateEvent.data);
77
+
78
+ if (stepDetected) {
79
+ const size = this.stepDetector.lastStepSize;
80
+ this.numOfSteps++;
81
+ this.notify(this.createEvent(
82
+ EventType.STEP, {
83
+ size,
84
+ number: this.numOfSteps
85
+ },
86
+ timestamp,
87
+ [accelerationEvent, this.angularRateEvent, this.attitudeEvent]
88
+ ));
89
+ }
90
+ }
91
+
92
+ // Linear acceleration in ENU
93
+ static computeLinearAcceleration(quaternion, acc) {
94
+ const linearAcc = Quaternion.rotate(Quaternion.inverse(quaternion), acc);
95
+ linearAcc[2] -= GeoConstants.EARTH_GRAVITY;
96
+ return linearAcc;
97
+ }
98
+ }
99
+
100
+ export default StepDetectionProvider;
@@ -0,0 +1,86 @@
1
+ /* eslint-disable max-statements */
2
+ import { UserPosition } from '@wemap/geo';
3
+
4
+ class PositionSmoother {
5
+
6
+
7
+ // Generated positions by second
8
+ static DEFAULT_FREQUENCY = 60;
9
+
10
+ // flyby (in second)
11
+ static DEFAULT_FLYBY_TIME = 1;
12
+
13
+
14
+ /**
15
+ * @param {Function} callback
16
+ * @param {Number} frequency in Hz
17
+ */
18
+ constructor(callback, frequency = this.constructor.DEFAULT_FREQUENCY) {
19
+ this.callback = callback;
20
+ this.positionsQueue = [];
21
+ this.timeoutPositions = null;
22
+ this.frequency = frequency;
23
+ }
24
+
25
+ /**
26
+ * @param {UserPosition} newPosition
27
+ * @param {Number} flybyTime in seconds
28
+ */
29
+ feed(newPosition, flybyTime = this.constructor.DEFAULT_FLYBY_TIME) {
30
+
31
+ if (!(newPosition instanceof UserPosition)) {
32
+ throw new TypeError('newPosition is not instance of UserPosition');
33
+ }
34
+
35
+ if (newPosition.time === null) {
36
+ throw new Error('newPosition does not have time property');
37
+ }
38
+
39
+ let previousPosition;
40
+ if (this.positionsQueue.length !== 0) {
41
+ previousPosition = this.positionsQueue[0];
42
+ this.positionsQueue = [];
43
+ } else {
44
+ previousPosition = this.previousPosition;
45
+ }
46
+
47
+ if (previousPosition) {
48
+
49
+ const refTimestamp = newPosition.time;
50
+ const distance = previousPosition.distanceTo(newPosition);
51
+ const azimuth = previousPosition.bearingTo(newPosition);
52
+
53
+ let i = 1;
54
+ const nSamples = this.frequency * flybyTime + 1;
55
+
56
+ while (i < nSamples + 1) {
57
+ const smoothedPosition = previousPosition.destinationPoint(distance * i / nSamples, azimuth);
58
+ smoothedPosition.time = refTimestamp + (i - 1) / this.frequency;
59
+ smoothedPosition.bearing = newPosition.bearing;
60
+ smoothedPosition.accuracy += (newPosition.accuracy - smoothedPosition.accuracy) * i / nSamples;
61
+ this.positionsQueue.push(smoothedPosition);
62
+ i++;
63
+ }
64
+
65
+ if (this.timeoutNotify) {
66
+ clearTimeout(this.timeoutNotify);
67
+ }
68
+ } else {
69
+ this.positionsQueue.push(newPosition);
70
+ }
71
+
72
+ this.previousPosition = newPosition;
73
+ this._notifyNext();
74
+ }
75
+
76
+ _notifyNext = () => {
77
+ this.callback(this.positionsQueue.shift());
78
+ if (this.positionsQueue.length !== 0) {
79
+ this.timeoutNotify = setTimeout(this._notifyNext, 1e3 / this.frequency);
80
+ } else {
81
+ delete this.timeoutNotify;
82
+ }
83
+ }
84
+ }
85
+
86
+ export default PositionSmoother;
@@ -0,0 +1,55 @@
1
+ import chai from 'chai';
2
+ import chaiAlmost from 'chai-almost';
3
+
4
+ import {
5
+ Constants as GeoConstants, Coordinates, UserPosition
6
+ } from '@wemap/geo';
7
+
8
+ import Smoother from './PositionSmoother';
9
+
10
+
11
+ const expect = chai.expect;
12
+ chai.use(chaiAlmost(GeoConstants.EPS_MM));
13
+
14
+ const positions = [
15
+ [0, 0, 0, 0],
16
+ [0.2, 4.5E-6, 1.7E-6, 0],
17
+ [1.3, 3.6E-5, 3.6E-5, 1],
18
+ [1.7, 4.0E-5, 3.6E-5, 0],
19
+ [3.5, 4.0E-5, 3.0E-5, 0],
20
+ [4, 3.4E-5, 9.0E-6, 0]
21
+ ];
22
+
23
+ const createPosition = i =>
24
+ new UserPosition(positions[i][1], positions[i][2], null, null, positions[i][0]);
25
+
26
+ const ratio = 1e-1;
27
+
28
+ const reducePositionTime = () => positions.forEach(pos => (pos[0] *= ratio));
29
+
30
+
31
+ describe('validSmoother', () => {
32
+
33
+ it('Last position reached', done => {
34
+
35
+ reducePositionTime();
36
+
37
+ const lastPosition = createPosition(positions.length - 1);
38
+ const smoother = new Smoother(position => {
39
+ if (Coordinates.equalsTo(position, lastPosition)) {
40
+ done();
41
+ }
42
+ }, 60 / ratio);
43
+
44
+ positions.forEach((pos, index) => {
45
+ setTimeout(() => smoother.feed(createPosition(index), ratio), pos[0] * 1e3);
46
+ });
47
+ });
48
+
49
+ it('Errors', () => {
50
+ const smoother = new Smoother();
51
+ expect(() => smoother.feed(new Coordinates(0, 0))).throw(TypeError);
52
+ expect(() => smoother.feed(new UserPosition(0, 0))).throw(Error);
53
+ });
54
+
55
+ });
@@ -0,0 +1,20 @@
1
+ module.exports = {
2
+ module: {
3
+ rules: [
4
+ {
5
+ test: /\.jsx?$/,
6
+ include: [
7
+ /src/,
8
+ /wemap-(.*)/
9
+ ],
10
+ exclude: /(\/mapbox-gl\/|.test.js|.spec.js|.mock.js)/,
11
+ use: { loader: 'babel-loader' }
12
+ },
13
+ {
14
+ test: /\.css$/,
15
+ use: ['style-loader', 'css-loader']
16
+ }
17
+ ]
18
+ },
19
+ resolve: { extensions: ['.js', '.jsx'] }
20
+ };
@@ -0,0 +1,24 @@
1
+ const merge = require('webpack-merge');
2
+ const path = require('path');
3
+
4
+ const common = require('./webpack.common.js');
5
+
6
+ const debugFolder = path.join(__dirname, '../debug');
7
+
8
+ module.exports = merge(common, {
9
+ mode: 'development',
10
+ entry: { 'providers-components': './debug/components/index.js' },
11
+ output: {
12
+ publicPath: '/js/',
13
+ filename: '[name].js',
14
+ path: path.join(debugFolder, 'js'),
15
+ libraryTarget: 'global'
16
+ },
17
+ devServer: {
18
+ contentBase: debugFolder,
19
+ inline: true,
20
+ hot: true,
21
+ host: '0.0.0.0',
22
+ port: 9000
23
+ }
24
+ });
@@ -0,0 +1,15 @@
1
+ const merge = require('webpack-merge');
2
+ const path = require('path');
3
+
4
+ const common = require('./webpack.common.js');
5
+ const config = require('../config.json');
6
+
7
+ module.exports = merge(common, {
8
+ mode: 'production',
9
+ entry: './index.js',
10
+ output: {
11
+ filename: config.distFileName,
12
+ path: path.join(__dirname, '../', config.distFolder),
13
+ libraryTarget: 'commonjs2'
14
+ }
15
+ });