@wemap/positioning 2.0.0 → 2.1.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 (71) hide show
  1. package/.eslintrc.json +4 -2
  2. package/debug/absolute-attitude.html +16 -0
  3. package/debug/gnss-wifi-pdr.html +16 -0
  4. package/debug/gnss-wifi.html +16 -0
  5. package/debug/imu.html +16 -0
  6. package/debug/inclination.html +16 -0
  7. package/debug/pdr.html +16 -0
  8. package/debug/pose.html +16 -0
  9. package/debug/positioning.html +16 -0
  10. package/debug/relative-attitude.html +16 -0
  11. package/package.json +6 -4
  12. package/src/PositioningHandler.js +96 -34
  13. package/src/components/AbsoluteAttitudeComponent.jsx +104 -0
  14. package/src/components/GnssWifiComponent.jsx +46 -0
  15. package/src/components/GnssWifiPdrComponent.jsx +85 -0
  16. package/src/components/ImuComponent.jsx +100 -0
  17. package/src/components/InclinationComponent.jsx +53 -0
  18. package/src/components/PdrComponent.jsx +88 -0
  19. package/src/components/PoseComponent.jsx +74 -0
  20. package/src/components/PositioningComponent.jsx +26 -0
  21. package/src/components/PositioningInclinationComponent.jsx +76 -0
  22. package/src/components/PositioningPoseComponent.jsx +111 -0
  23. package/src/components/RelativeAttitudeComponent.jsx +82 -0
  24. package/src/components/StartStopComponent.jsx +50 -0
  25. package/src/components/Utils.js +74 -0
  26. package/src/components/index.js +30 -0
  27. package/src/errors/AskImuOnDesktopError.js +9 -0
  28. package/src/errors/GeolocationApiMissingError.js +9 -0
  29. package/src/errors/GeolocationPermissionDeniedError.js +9 -0
  30. package/src/errors/GeolocationPositionUnavailableError.js +9 -0
  31. package/src/errors/IpResolveServerError.js +9 -0
  32. package/src/errors/MissingAccelerometerError.js +11 -0
  33. package/src/errors/MissingGyroscopeError.js +11 -0
  34. package/src/errors/MissingMagnetometerError.js +9 -0
  35. package/src/errors/MissingSensorError.js +14 -0
  36. package/src/events/EventType.js +20 -0
  37. package/src/events/ProviderError.js +52 -0
  38. package/src/events/ProviderEvent.js +35 -0
  39. package/src/index.js +2 -2
  40. package/src/providers/Constants.js +5 -0
  41. package/src/providers/FakeAbsolutePositionProvider.js +56 -0
  42. package/src/providers/Provider.js +218 -0
  43. package/src/providers/ProviderOptions.js +28 -0
  44. package/src/providers/ProvidersLogger.js +77 -0
  45. package/src/providers/attitude/AbsoluteAttitudeProvider.js +207 -0
  46. package/src/providers/attitude/EkfAttitude.js +238 -0
  47. package/src/providers/attitude/EkfAttitude.spec.js +116 -0
  48. package/src/providers/attitude/RelativeAttitudeProvider.js +129 -0
  49. package/src/providers/others/ImuProvider.js +186 -0
  50. package/src/providers/others/InclinationProvider.js +107 -0
  51. package/src/providers/others/MapMatchingProvider.js +147 -0
  52. package/src/providers/pose/GnssWifiPdrProvider.js +233 -0
  53. package/src/providers/pose/PoseProvider.js +90 -0
  54. package/src/providers/pose/pdr/PdrProvider.js +352 -0
  55. package/src/providers/pose/pdr/helpers/HeadingUnlocker.js +41 -0
  56. package/src/providers/pose/pdr/helpers/HeadingUnlocker.spec.js +26 -0
  57. package/src/providers/pose/pdr/helpers/Smoother.js +90 -0
  58. package/src/providers/pose/pdr/helpers/Smoother.spec.js +424 -0
  59. package/src/providers/pose/pdr/helpers/ThugDetector.js +37 -0
  60. package/src/providers/pose/pdr/steps/StepDetection.js +7 -0
  61. package/src/providers/pose/pdr/steps/StepDetectionLadetto.js +67 -0
  62. package/src/providers/pose/pdr/steps/StepDetectionMinMaxPeaks.js +80 -0
  63. package/src/providers/pose/pdr/steps/StepDetectionMinMaxPeaks2.js +108 -0
  64. package/src/providers/position/GnssWifiProvider.js +129 -0
  65. package/src/providers/position/IpProvider.js +75 -0
  66. package/src.old/providers/GnssPdrLocationSource.js +1 -1
  67. package/webpack/webpack.dev.js +1 -1
  68. package/debug/index.html +0 -15
  69. package/debug/index.old.html +0 -37
  70. package/scripts/release-github.js +0 -216
  71. package/src/providers/FakeLocationSource.js +0 -36
@@ -0,0 +1,129 @@
1
+ import Provider from '../Provider';
2
+ import EventType from '../../events/EventType';
3
+ import EkfAttitude from './EkfAttitude';
4
+ import ImuProvider from '../others/ImuProvider';
5
+ import { Attitude } from '@wemap/geo';
6
+ import { deg2rad } from '@wemap/maths';
7
+ import ProviderError from '../../events/ProviderError';
8
+
9
+
10
+ /**
11
+ * Relative attitude provider gives the device attitude in East-North-Up (ENU) frame using
12
+ * browser deviceorientation
13
+ * The provider does not work until an offset is given.
14
+ */
15
+ class RelativeAttitudeProvider extends Provider {
16
+
17
+ lastTimestamp = 0;
18
+
19
+ /**
20
+ * @override
21
+ */
22
+ constructor(onEvent, onError, options) {
23
+ super(onEvent, onError, options);
24
+
25
+ this.ekfAttitude = new EkfAttitude();
26
+ this.relativeOffsetQuaternion = [1, 0, 0, 0];
27
+
28
+ this.imuProvider = new ImuProvider(this.onImuEvent,
29
+ this.onImuError, { require: [EventType.Acceleration, EventType.AngularRate] });
30
+
31
+ }
32
+
33
+ /**
34
+ * @override
35
+ */
36
+ static get displayName() {
37
+ return 'Relative Attitude from Ekf';
38
+ }
39
+
40
+ /**
41
+ * @override
42
+ */
43
+ static get eventsType() {
44
+ return [EventType.RelativeAttitude];
45
+ }
46
+
47
+ /**
48
+ * @override
49
+ */
50
+ static get requiredProviders() {
51
+ return [ImuProvider];
52
+ }
53
+
54
+ /**
55
+ * @override
56
+ */
57
+ startInternal() {
58
+ this.imuProvider.start();
59
+ }
60
+
61
+ /**
62
+ * @override
63
+ */
64
+ stopInternal() {
65
+ this.imuProvider.stop();
66
+ }
67
+
68
+ /**
69
+ * @private
70
+ */
71
+ onImuEvent = imuEvent => {
72
+
73
+ let timestamp, acceleration, angularRate;
74
+ imuEvent.forEach(event => {
75
+ if (event.dataType === EventType.Acceleration) {
76
+ acceleration = event.data;
77
+ timestamp = event.timestamp;
78
+ } else if (event.dataType === EventType.AngularRate) {
79
+ angularRate = event.data;
80
+ }
81
+ });
82
+
83
+ // Handle timestamps and dt
84
+ if (this.lastTimestamp === 0) {
85
+ this.lastTimestamp = timestamp;
86
+ return;
87
+ }
88
+ const diffTime = timestamp - this.lastTimestamp;
89
+ this.lastTimestamp = timestamp;
90
+
91
+ const quaternion = this.ekfAttitude.update(diffTime, acceleration, angularRate);
92
+
93
+ if (quaternion) {
94
+ this.notify(this.createEvent(EventType.RelativeAttitude, new Attitude(quaternion), timestamp));
95
+ }
96
+ };
97
+
98
+ /**
99
+ *
100
+ * Set yaw offset, this value will be used as the filter does not use magnetometer
101
+ * @param {Number} heading heading offset in radians and clockwise
102
+ */
103
+ setOffset(_heading) {
104
+
105
+ // Minus before "heading" is here because ENU attitude is counter-clockwise whereas WGS84 heading is clockwise.
106
+ let heading = -_heading;
107
+
108
+ // Offset from window orientation
109
+ heading += deg2rad(window.orientation || 0);
110
+
111
+ this.ekfAttitude.setOrientationYaw(heading);
112
+ }
113
+
114
+ onImuError = imuErrors => {
115
+ this.notifyError(...ProviderError.modifyArrayDataType(imuErrors, EventType.RelativeAttitude));
116
+ }
117
+
118
+ /**
119
+ * @override
120
+ */
121
+ static checkAvailabilityErrors() {
122
+ return ProviderError.modifyArrayDataType(
123
+ super.checkAvailabilityErrors(),
124
+ EventType.RelativeAttitude
125
+ );
126
+ }
127
+ }
128
+
129
+ export default RelativeAttitudeProvider;
@@ -0,0 +1,186 @@
1
+ import { deg2rad } from '@wemap/maths';
2
+ import {
3
+ Browser, BrowserUtils
4
+ } from '@wemap/utils';
5
+
6
+ import Provider from '../Provider';
7
+ import EventType from '../../events/EventType';
8
+ import MissingAccelerometerError from '../../errors/MissingAccelerometerError';
9
+ import MissingGyroscopeError from '../../errors/MissingGyroscopeError';
10
+ import AskImuOnDesktopError from '../../errors/AskImuOnDesktopError';
11
+
12
+ /**
13
+ * Imu (Inertial Measurement Unit) provider retrieve acceleration data
14
+ * and/or angular rate data from inertial sensors.
15
+ * opions.require has to be defined in order to work
16
+ *
17
+ * -----------------------------------
18
+ * Overview of compatibilities:
19
+ * -----------------------------------
20
+ *
21
+ * Chrome Android (v72.0.3626): YES (via devicemotion)
22
+ * Safari iOS (v12.0): YES (via devicemotion)
23
+ * Opera Android (v50.2.2426): NO {@link https://developer.mozilla.org/en-US/docs/Web/API/DeviceMotionEvent}
24
+ * Firefox Android (v65.0.1): YES (via devicemotion)
25
+ *
26
+ * -----------------------------------
27
+ */
28
+ class ImuProvider extends Provider {
29
+
30
+ requiredSensors = [];
31
+
32
+ /**
33
+ * Constructor of ImuProvider
34
+ * options.require has to be filled by a list of EventType
35
+ * @example options = {require: [EventType.Acceleration, EventType.AngularRate]};
36
+ * @example options = {require: [EventType.Acceleration]};
37
+ *
38
+ * @param {Function} onEvent @see Provider#constructor
39
+ * @param {Function} onError @see Provider#constructor
40
+ * @param {Object} options @see Provider#constructor
41
+ */
42
+ constructor(onEvent, onError, options) {
43
+ super(onEvent, onError, options);
44
+
45
+ if (!options.hasOwnProperty('require')) {
46
+ throw new Error('options.require is missing in ImuProvider constructor');
47
+ }
48
+
49
+ if (options.require.length === 0) {
50
+ throw new Error('option.require is empty');
51
+ }
52
+
53
+ options.require.forEach(elem => {
54
+ if (!ImuProvider.eventsType.includes(elem)) {
55
+ throw new Error(elem + ' is not recognised in options.require');
56
+ }
57
+ });
58
+
59
+ this.requiredSensors = options.require;
60
+ }
61
+
62
+
63
+ /**
64
+ * @override
65
+ */
66
+ static get displayName() {
67
+ return 'Inertial Measurement Unit';
68
+ }
69
+
70
+ /**
71
+ * @override
72
+ */
73
+ static get eventsType() {
74
+ return [EventType.AngularRate, EventType.Acceleration];
75
+ }
76
+
77
+ /**
78
+ * @override
79
+ */
80
+ static checkAvailabilityErrors() {
81
+
82
+ if (BrowserUtils.isMobile) {
83
+ return [];
84
+ }
85
+
86
+ return [
87
+ ImuProvider.createError(
88
+ EventType.AngularRate,
89
+ new AskImuOnDesktopError()
90
+ ),
91
+ ImuProvider.createError(
92
+ EventType.Acceleration,
93
+ new AskImuOnDesktopError()
94
+ )
95
+ ];
96
+ }
97
+
98
+ /**
99
+ * @override
100
+ */
101
+ startInternal() {
102
+ window.addEventListener('devicemotion', this.parseDeviceMotionEvent, true);
103
+ }
104
+
105
+ /**
106
+ * @override
107
+ */
108
+ stopInternal() {
109
+ window.removeEventListener('devicemotion', this.parseDeviceMotionEvent, true);
110
+ }
111
+
112
+ /**
113
+ * devicemotion callback
114
+ * @param {DeviceMotionEvent} e device motion event
115
+ * @returns {ProviderEvent[]} an array of provider events
116
+ * @private
117
+ */
118
+ parseDeviceMotionEvent = e => {
119
+
120
+ const events = [];
121
+
122
+ const timestamp = e.timeStamp / 1e3;
123
+
124
+ if (this.requiredSensors.includes(EventType.Acceleration)) {
125
+
126
+ let acc;
127
+ if (e.accelerationIncludingGravity) {
128
+ const {
129
+ x, y, z
130
+ } = e.accelerationIncludingGravity;
131
+
132
+ if (x && y && z) {
133
+ acc = [x, y, z];
134
+
135
+ if (BrowserUtils.name === Browser.SAFARI) {
136
+ acc[0] *= -1;
137
+ acc[1] *= -1;
138
+ acc[2] *= -1;
139
+ }
140
+ }
141
+ }
142
+
143
+ if (!acc) {
144
+ super.notifyError(this.createError(
145
+ EventType.AngularRate,
146
+ new MissingAccelerometerError().from('devicemotion'),
147
+ timestamp
148
+ ));
149
+ return;
150
+ }
151
+
152
+ events.push(this.createEvent(EventType.Acceleration, acc, timestamp));
153
+ }
154
+
155
+
156
+ if (this.requiredSensors.includes(EventType.AngularRate)) {
157
+
158
+ let gyr;
159
+ if (e.rotationRate) {
160
+ const {
161
+ alpha, beta, gamma
162
+ } = e.rotationRate;
163
+
164
+ if (alpha && beta && gamma) {
165
+ gyr = [deg2rad(alpha), deg2rad(beta), deg2rad(gamma)];
166
+ }
167
+ }
168
+
169
+ if (!gyr) {
170
+ super.notifyError(this.createError(EventType.AngularRate,
171
+ new MissingGyroscopeError().from('devicemotion'),
172
+ timestamp));
173
+ return;
174
+ }
175
+
176
+ events.push(this.createEvent(EventType.AngularRate, gyr, timestamp));
177
+ }
178
+
179
+ if (events.length !== 0) {
180
+ super.notify(...events);
181
+ }
182
+ }
183
+
184
+ }
185
+
186
+ export default ImuProvider;
@@ -0,0 +1,107 @@
1
+ import Provider from '../Provider';
2
+ import EventType from '../../events/EventType';
3
+ import ImuProvider from './ImuProvider';
4
+ import ProviderError from '../../events/ProviderError';
5
+
6
+ /**
7
+ * Inclination provider gives the inclination of the device using Imu Sensor
8
+ * For example, when the top of the device is pointing the sky, inclination = Math.PI/2
9
+ * when the device is layed on a table, inclination = 0
10
+ * This provider use window.orientation to return a result in function of screen orientation
11
+ */
12
+ class InclinationProvider extends Provider {
13
+
14
+ /**
15
+ * @override
16
+ */
17
+ constructor(onEvent, onError, options) {
18
+ super(onEvent, onError, options);
19
+
20
+ this.imuProvider = new ImuProvider(
21
+ this.onImuEvent,
22
+ this.onImuError,
23
+ { require: [EventType.Acceleration] }
24
+ );
25
+ }
26
+
27
+ /**
28
+ * @override
29
+ */
30
+ static get displayName() {
31
+ return 'Inertial Measurement Unit';
32
+ }
33
+
34
+ /**
35
+ * @override
36
+ */
37
+ static get eventsType() {
38
+ return [EventType.Inclination];
39
+ }
40
+
41
+ /**
42
+ * @override
43
+ */
44
+ static get requiredProviders() {
45
+ return [ImuProvider];
46
+ }
47
+
48
+ /**
49
+ * @override
50
+ */
51
+ startInternal() {
52
+ return this.imuProvider.start();
53
+ }
54
+
55
+ /**
56
+ * @override
57
+ */
58
+ stopInternal() {
59
+ return this.imuProvider.stop();
60
+ }
61
+
62
+ /**
63
+ * @private
64
+ */
65
+ onImuEvent = imuEvent => {
66
+ const accelerationEvent = imuEvent[0];
67
+ const acc = accelerationEvent.data;
68
+
69
+ const screenOrientation = window.orientation || 0;
70
+
71
+ const sizeAcc = Math.sqrt(acc[0] * acc[0] + acc[1] * acc[1] + acc[2] * acc[2]);
72
+ const accNormalized = [acc[0] / sizeAcc, acc[1] / sizeAcc, acc[2] / sizeAcc];
73
+
74
+ const q = [accNormalized[2] + 1, accNormalized[1], -accNormalized[0], 0];
75
+ const qSize = Math.sqrt(q[0] * q[0] + q[1] * q[1] + q[2] * q[2]);
76
+ const qNormalized = [q[0] / qSize, q[1] / qSize, q[2] / qSize, 0];
77
+
78
+ let inclination;
79
+ if (screenOrientation === 0) {
80
+ inclination = Math.asin(2 * qNormalized[1] * qNormalized[0]);
81
+ } else if (screenOrientation === 90) {
82
+ inclination = -Math.asin(2 * qNormalized[2] * qNormalized[0]);
83
+ } else if (screenOrientation === -90) {
84
+ inclination = Math.asin(2 * qNormalized[2] * qNormalized[0]);
85
+ } else if (screenOrientation === 180) {
86
+ inclination = -Math.asin(2 * qNormalized[1] * qNormalized[0]);
87
+ }
88
+
89
+ this.notify(this.createEvent(EventType.Inclination, inclination));
90
+ };
91
+
92
+ onImuError = imuErrors => {
93
+ this.notifyError(...ProviderError.modifyArrayDataType(imuErrors, EventType.Inclination));
94
+ }
95
+
96
+ /**
97
+ * @override
98
+ */
99
+ static checkAvailabilityErrors() {
100
+ return ProviderError.modifyArrayDataType(
101
+ super.checkAvailabilityErrors(),
102
+ EventType.Inclination
103
+ );
104
+ }
105
+ }
106
+
107
+ export default InclinationProvider;
@@ -0,0 +1,147 @@
1
+ import Provider from '../Provider';
2
+ import {
3
+ WGS84, Edge, Itinerary, Network, MapMatching
4
+ } from '@wemap/geo';
5
+
6
+ class MapMatchingProvider extends Provider {
7
+
8
+ /**
9
+ * Enable LocationSource mapmatching
10
+ * @param {*} network a network which will be used for map matching
11
+ * @param {*} maxDistance max distance between location and network to match. null disables maxDistance (default: null)
12
+ * @param {*} maxAngleBearing threshold to match a parallel segment for angle between location bearing and segment. null disables this threshold (default: null)
13
+ * @public
14
+ */
15
+ enableMapMatching(network, maxDistance, maxAngleBearing) {
16
+ this.mapMatching = new MapMatching();
17
+ this.mapMatching.maxDistance = maxDistance;
18
+ this.mapMatching.maxAngleBearing = maxAngleBearing;
19
+
20
+ if (network) {
21
+ this.setNetwork(network);
22
+ }
23
+ }
24
+
25
+ /**
26
+ * Update the LocationSource network for mapmatching
27
+ * @param {Network} network a network instance
28
+ * @public
29
+ */
30
+ setNetwork(network) {
31
+ if (!(network instanceof Network)) {
32
+ throw new TypeError(network + ' is not an instance of Network');
33
+ }
34
+ if (!this.mapMatching) {
35
+ throw new Error('MapMatching is not enabled');
36
+ }
37
+ this.mapMatching.setNetwork(network);
38
+ }
39
+
40
+ /**
41
+ * Is mapmatching activated?
42
+ * @returns is mapmatching activated
43
+ * @public
44
+ */
45
+ get hasMapMatching() {
46
+ return this.mapMatching !== null;
47
+ }
48
+
49
+ /**
50
+ * Returns projection of a location on network
51
+ * @param {WGS84} location if location is null, projection of this.pose.location is used
52
+ * @returns The projected location {WGS84} or null.
53
+ * @public
54
+ */
55
+ getProjectionOnNetwork(location = null) {
56
+
57
+ if (!this.mapMatching) {
58
+ return null;
59
+ }
60
+
61
+ let locationToProject = location;
62
+ if (!location) {
63
+ locationToProject = this.pose.location;
64
+ }
65
+ if (!locationToProject) {
66
+ return null;
67
+ }
68
+
69
+ return this.mapMatching.getProjection(locationToProject, false, false);
70
+ }
71
+
72
+
73
+ /**
74
+ * Itinerary
75
+ */
76
+
77
+ /**
78
+ * Update itinerary of Location Source.
79
+ * Itinerary is different from network, it is not used for mapmatching. It can be used for others tricks like HeadingUnlocker
80
+ * @param {Itinerary} itinerary a given itinerary
81
+ * @public
82
+ */
83
+ setItinerary(itinerary) {
84
+ if (!(itinerary instanceof Itinerary)) {
85
+ throw new TypeError(itinerary + ' is not an instance of Itinerary');
86
+ }
87
+ this.itinerary = itinerary;
88
+ }
89
+
90
+
91
+ /**
92
+ * Returns projection and itinerary info of a location on network
93
+ * @param {WGS84} location if location is null, projection of this.pose.location is used
94
+ * @returns An object of the projected location and itinerary info or null.
95
+ * @public
96
+ */
97
+ getItineraryInfo(location = null) {
98
+
99
+ if (!this.itinerary) {
100
+ throw new Error('No itinerary found');
101
+ }
102
+ const projection = this.getProjectionOnNetwork(location);
103
+ if (!projection) {
104
+ return null;
105
+ }
106
+
107
+ const totalDistance = this.itinerary.distance;
108
+ let traveledDistance = 0;
109
+ const edges = this.itinerary.edges;
110
+
111
+ if (projection.nearestElement instanceof WGS84) {
112
+
113
+ for (let i = 0; i < edges.length; i++) {
114
+ const edge = edges[i];
115
+ if (edge.node1.equalsTo(projection.nearestElement)) {
116
+ break;
117
+ }
118
+ traveledDistance += edge.getLength();
119
+ }
120
+
121
+
122
+ } else if (projection.nearestElement instanceof Edge) {
123
+
124
+ for (let i = 0; i < edges.length; i++) {
125
+ const edge = edges[i];
126
+ if (edge.equalsTo(projection.nearestElement)) {
127
+ traveledDistance += edge.node1.distanceTo(projection.projection);
128
+ break;
129
+ }
130
+ traveledDistance += edge.getLength();
131
+ }
132
+
133
+ } else {
134
+ throw new Error('No projection found');
135
+ }
136
+
137
+ return {
138
+ projection: projection,
139
+ traveledDistance,
140
+ traveledPercentage: traveledDistance / totalDistance,
141
+ remainingDistance: totalDistance - traveledDistance,
142
+ remainingPercentage: 1 - traveledDistance / totalDistance
143
+ };
144
+ }
145
+ }
146
+
147
+ export default MapMatchingProvider;