@wemap/providers 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/babel.config.js +11 -0
- package/config.json +4 -0
- package/debug/absolute-attitude.html +16 -0
- package/debug/absolute-position.html +16 -0
- package/debug/attitude.html +16 -0
- package/debug/components/AbsoluteAttitudeComponent.jsx +142 -0
- package/debug/components/AbsolutePositionComponent.jsx +79 -0
- package/debug/components/AttitudeComponent.jsx +40 -0
- package/debug/components/Common.css +27 -0
- package/debug/components/GnssWifiComponent.jsx +53 -0
- package/debug/components/ImuComponent.jsx +53 -0
- package/debug/components/InclinationComponent.jsx +68 -0
- package/debug/components/MapComponent.jsx +366 -0
- package/debug/components/NavigationConfig.js +112 -0
- package/debug/components/PoseComponent.jsx +168 -0
- package/debug/components/RelativeAttitudeComponent.jsx +85 -0
- package/debug/components/StartStopComponent.jsx +45 -0
- package/debug/components/StepDetectionComponent.jsx +39 -0
- package/debug/components/Utils.js +216 -0
- package/debug/components/index.js +30 -0
- package/debug/components/old/PositioningComponent.jsx +29 -0
- package/debug/components/old/PositioningInclinationComponent.jsx +82 -0
- package/debug/components/old/PositioningPoseComponent.jsx +117 -0
- package/debug/gnss-wifi.html +16 -0
- package/debug/imu.html +16 -0
- package/debug/inclination.html +16 -0
- package/debug/pose.html +16 -0
- package/debug/positioning-legacy.html +16 -0
- package/debug/relative-attitude.html +16 -0
- package/debug/step-detection.html +16 -0
- package/index.js +7 -0
- package/package.json +67 -0
- package/src/Providers.js +80 -0
- package/src/ProvidersInterface.js +125 -0
- package/src/ProvidersOptions.js +29 -0
- package/src/errors/AskImuOnDesktopError.js +9 -0
- package/src/errors/ContainsIgnoredProviderError.js +9 -0
- package/src/errors/GeolocationApiMissingError.js +9 -0
- package/src/errors/GeolocationPermissionDeniedError.js +9 -0
- package/src/errors/GeolocationPositionUnavailableError.js +9 -0
- package/src/errors/IpResolveServerError.js +9 -0
- package/src/errors/MissingAccelerometerError.js +11 -0
- package/src/errors/MissingArCoreError.js +9 -0
- package/src/errors/MissingGyroscopeError.js +11 -0
- package/src/errors/MissingMagnetometerError.js +9 -0
- package/src/errors/MissingNativeInterfaceError.js +9 -0
- package/src/errors/MissingSensorError.js +14 -0
- package/src/errors/NoProviderFoundError.js +9 -0
- package/src/events/Availability.js +44 -0
- package/src/events/EventType.js +33 -0
- package/src/events/ProviderEvent.js +32 -0
- package/src/events/ProvidersLogger.js +83 -0
- package/src/providers/Constants.js +5 -0
- package/src/providers/FakeProvider.spec.js +57 -0
- package/src/providers/MetaProvider.js +42 -0
- package/src/providers/Provider.js +314 -0
- package/src/providers/Provider.spec.js +136 -0
- package/src/providers/ProviderState.js +5 -0
- package/src/providers/attitude/AttitudeProvider.js +63 -0
- package/src/providers/attitude/EkfAttitude.js +224 -0
- package/src/providers/attitude/EkfAttitude.spec.js +114 -0
- package/src/providers/attitude/absolute/AbsoluteAttitudeFromBrowserProvider.js +224 -0
- package/src/providers/attitude/absolute/AbsoluteAttitudeFromRelAttProvider.js +134 -0
- package/src/providers/attitude/absolute/AbsoluteAttitudeProvider.js +143 -0
- package/src/providers/attitude/relative/RelativeAttitudeFromBrowserProvider.js +89 -0
- package/src/providers/attitude/relative/RelativeAttitudeFromEkfProvider.js +114 -0
- package/src/providers/attitude/relative/RelativeAttitudeProvider.js +103 -0
- package/src/providers/imu/AccelerometerProvider.js +61 -0
- package/src/providers/imu/GyroscopeProvider.js +61 -0
- package/src/providers/imu/ImuProvider.js +122 -0
- package/src/providers/inclination/InclinationFromAccProvider.js +87 -0
- package/src/providers/inclination/InclinationFromAttitudeProvider.js +77 -0
- package/src/providers/inclination/InclinationProvider.js +69 -0
- package/src/providers/legacy/AbsolutePdrProvider.js +258 -0
- package/src/providers/legacy/ArCoreAbsoluteProvider.js +230 -0
- package/src/providers/legacy/GnssWifiPdrProvider.js +217 -0
- package/src/providers/legacy/MapMatchingProvider.js +65 -0
- package/src/providers/legacy/PdrProvider.old.js +300 -0
- package/src/providers/legacy/PoseProvider.js +68 -0
- package/src/providers/legacy/helpers/HeadingUnlocker.js +47 -0
- package/src/providers/legacy/helpers/HeadingUnlocker.spec.js +53 -0
- package/src/providers/legacy/helpers/Smoother.js +92 -0
- package/src/providers/legacy/helpers/Smoother.spec.js +426 -0
- package/src/providers/legacy/helpers/ThugDetector.js +37 -0
- package/src/providers/others/CameraNativeProvider.js +44 -0
- package/src/providers/position/absolute/AbsolutePositionFromRelProvider.js +109 -0
- package/src/providers/position/absolute/AbsolutePositionProvider.js +172 -0
- package/src/providers/position/absolute/GnssWifiProvider.js +122 -0
- package/src/providers/position/absolute/IpProvider.js +68 -0
- package/src/providers/position/relative/ArCoreProvider.js +197 -0
- package/src/providers/position/relative/GeoRelativePositionFromArCoreProvider.js +85 -0
- package/src/providers/position/relative/GeoRelativePositionProvider.js +66 -0
- package/src/providers/position/relative/PdrProvider.js +132 -0
- package/src/providers/steps/StepDetectionLadetto.js +67 -0
- package/src/providers/steps/StepDetectionMinMaxPeaks.js +80 -0
- package/src/providers/steps/StepDetectionMinMaxPeaks2.js +108 -0
- package/src/providers/steps/StepDetectionProvider.js +100 -0
- package/src/smoothers/PositionSmoother.js +86 -0
- package/src/smoothers/PositionSmoother.spec.js +55 -0
- package/webpack/webpack.common.js +20 -0
- package/webpack/webpack.dev.js +24 -0
- package/webpack/webpack.prod.js +15 -0
|
@@ -0,0 +1,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
|
+
});
|