@wemap/providers 6.0.2 → 6.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.
package/package.json CHANGED
@@ -8,6 +8,7 @@
8
8
  "Guillaume Pannetier <guillaume.pannetier@getwemap.com>"
9
9
  ],
10
10
  "dependencies": {
11
+ "@wemap/camera": "^6.1.0",
11
12
  "@wemap/geo": "^6.0.0",
12
13
  "@wemap/geomagnetism": "^0.1.1",
13
14
  "@wemap/logger": "^6.0.0",
@@ -39,6 +40,6 @@
39
40
  "url": "git+https://github.com/wemap/wemap-modules-js.git"
40
41
  },
41
42
  "type": "module",
42
- "version": "6.0.2",
43
- "gitHead": "8851e7c4537556e51a3f0c52de9fc80d5e37ad73"
43
+ "version": "6.1.0",
44
+ "gitHead": "367774b6d8cbfa864886d9cc3344244c7da20639"
44
45
  }
package/src/Providers.js CHANGED
@@ -32,6 +32,8 @@ export { default as GnssWifi } from './providers/position/absolute/GnssWifi.js';
32
32
  export { default as Ip } from './providers/position/absolute/Ip.js';
33
33
  export { default as AbsolutePosition } from './providers/position/absolute/AbsolutePosition.js';
34
34
 
35
+ export { default as VisionRelocalization } from './providers/vision/VisionRelocalization.js';
36
+
35
37
  export { default as Barcode } from './providers/others/Barcode.js';
36
38
  export { default as CameraNative } from './providers/others/CameraNative.js';
37
39
  export { default as CameraProjectionMatrix } from './providers/others/CameraProjectionMatrix.js';
@@ -7,10 +7,18 @@ const ProvidersOptions = {
7
7
 
8
8
  /**
9
9
  * Providers listed here will not be used by the system
10
- * List of {@link Provider#name}
10
+ * List of {@link Provider#pname}
11
11
  */
12
12
  ignoreProviders: [],
13
13
 
14
+ /**
15
+ * Some providers are not used by default (VPS, QRCodeScanner) because they
16
+ * require data from an optional external service or because they drain more
17
+ * battery. They can be added to this list to be used
18
+ * List of {@link Provider#pname}
19
+ */
20
+ optionalProviders: ['VisionRelocalization'],
21
+
14
22
  /**
15
23
  * Define the list of EventType that are optionals
16
24
  * List of {@link EventType}
@@ -23,7 +31,11 @@ const ProvidersOptions = {
23
31
 
24
32
  stepdetectionlocker: true,
25
33
 
26
- smoother: true
34
+ smoother: true,
35
+
36
+ get hasVps() {
37
+ return this.optionalProviders.includes('VisionRelocalization');
38
+ }
27
39
  };
28
40
 
29
41
  export default ProvidersOptions;
@@ -123,9 +123,16 @@ class Provider {
123
123
  return false;
124
124
  }
125
125
 
126
+ /**
127
+ * Description of the function
128
+ * @name onEventsFunction
129
+ * @function
130
+ * @param {ProviderEvent[]} events the array of provider events
131
+ */
132
+
126
133
  /**
127
134
  *
128
- * @param {Function} onEvents
135
+ * @param {onEventsFunction} onEvents
129
136
  * @param {Function} onError
130
137
  * @param {Boolean} startIfNecessary
131
138
  * @returns {Number}
@@ -7,6 +7,8 @@ import ProviderEvent from '../../../events/ProviderEvent.js';
7
7
  import MapMatchingHandler from '../../../mapmatching/MapMatchingHandler.js';
8
8
  import GnssWifi from './GnssWifi.js';
9
9
  import { default as GeoRelativePositionProvider } from '../relative/GeoRelativePosition.js';
10
+ import ProvidersOptions from '../../../ProvidersOptions.js';
11
+ import VisionRelocalization from '../../vision/VisionRelocalization.js';
10
12
 
11
13
  class AbsolutePosition extends Provider {
12
14
 
@@ -65,13 +67,29 @@ class AbsolutePosition extends Provider {
65
67
  // do nothing
66
68
  });
67
69
 
68
- this._gnssWifiProviderId = GnssWifi.addEventListener(
69
- events => {
70
- // bearing from GnssWifi is not reliable for our usecase
71
- events[0].data.bearing = null;
72
- this._onAbsolutePosition(events[0], false);
73
- }
74
- );
70
+ if (ProvidersOptions.hasVps) {
71
+
72
+ const vpsProviderId = VisionRelocalization.addEventListener(events => {
73
+ for (const providerEvent of events) {
74
+ if (providerEvent.dataType === EventType.AbsolutePosition) {
75
+ this._onAbsolutePosition(providerEvent);
76
+ VisionRelocalization.removeEventListener(vpsProviderId);
77
+ return;
78
+ }
79
+ }
80
+ });
81
+
82
+ } else {
83
+
84
+ this._gnssWifiProviderId = GnssWifi.addEventListener(
85
+ events => {
86
+ // bearing from GnssWifi is not reliable for our usecase
87
+ events[0].data.bearing = null;
88
+ this._onAbsolutePosition(events[0], false);
89
+ }
90
+ );
91
+
92
+ }
75
93
 
76
94
  this._mapMatchingHandlerId = MapMatchingHandler.addEventListener();
77
95
  }
@@ -0,0 +1,272 @@
1
+ /* eslint-disable max-statements */
2
+ import { SharedCameras, CameraUtils, Camera } from '@wemap/camera';
3
+ import { Attitude, Level, UserPosition } from '@wemap/geo';
4
+ import Logger from '@wemap/logger';
5
+ import { deg2rad, Quaternion } from '@wemap/maths';
6
+ import { TimeUtils } from '../../../../utils/index.js';
7
+
8
+ import EventType from '../../events/EventType.js';
9
+ import ProviderEvent from '../../events/ProviderEvent.js';
10
+ import Provider from '../Provider.js';
11
+ import ProviderState from '../ProviderState.js';
12
+
13
+ class VisionRelocalization extends Provider {
14
+
15
+ // static SERVICE_URL = 'http://localhost:45678/';
16
+ static SERVICE_URL = 'https://vps.maaap.it/';
17
+ static MAP_ID = 'wemap';
18
+ static MIN_TIME_BETWEEN_TWO_REQUESTS = 1000;
19
+
20
+ /** @type {boolean} */
21
+ _serverError = false;
22
+
23
+ /** @type {boolean} */
24
+ _cameraError = false;
25
+
26
+ /** @type {Camera} */
27
+ _camera = null;
28
+
29
+ /**
30
+ * @override
31
+ */
32
+ static get pname() {
33
+ return 'VisionRelocalization';
34
+ }
35
+
36
+ /**
37
+ * @override
38
+ */
39
+ static get eventsType() {
40
+ return [EventType.AbsoluteAttitude, EventType.AbsolutePosition];
41
+ }
42
+
43
+ /**
44
+ * @override
45
+ */
46
+ get _availability() {
47
+ return Promise.resolve();
48
+ }
49
+
50
+ /**
51
+ * @override
52
+ */
53
+ start() {
54
+
55
+ // 1. Add listeners on shared cameras to detect new cameras
56
+ SharedCameras.on('added', this._onCameraDetected);
57
+ SharedCameras.on('removed', this._onCameraRemoved);
58
+
59
+ // 2. If a camera already exists, use it
60
+ if (SharedCameras.list.length) {
61
+ if (SharedCameras.list.length > 1) {
62
+ Logger.warn('It seems that more than 1 camera has been detected'
63
+ + ' for VPS. Taking the first...');
64
+ }
65
+ this._useCamera(SharedCameras.list[0].camera);
66
+ }
67
+ }
68
+
69
+ /**
70
+ * @override
71
+ */
72
+ stop() {
73
+ SharedCameras.off('added', this._onCameraDetected);
74
+ SharedCameras.off('removed', this._onCameraRemoved);
75
+
76
+ this._camera = null;
77
+ }
78
+
79
+
80
+ _onCameraDetected = ({ camera }) => {
81
+ if (this._camera) {
82
+ Logger.warn('It seems that more than 1 camera has been detected'
83
+ + ' for VPS. Taking the first...');
84
+ }
85
+ this._useCamera(camera);
86
+ }
87
+
88
+ _onCameraRemoved = () => {
89
+ if (this._camera) {
90
+ this._camera.off('started', this._internalStart);
91
+ this._camera.off('stopped', this._internalStop);
92
+ } else {
93
+ Logger.warn('There is no previously detected camera but once has stopped');
94
+ }
95
+ this._camera = null;
96
+ }
97
+
98
+ /**
99
+ * @param {Camera} camera
100
+ */
101
+ _useCamera(camera) {
102
+ this._camera = camera;
103
+
104
+ camera.on('started', this._internalStart);
105
+ camera.on('stopped', this._internalStop);
106
+
107
+ if (camera.isStarted) {
108
+ this._internalStart();
109
+ }
110
+ }
111
+
112
+
113
+ _internalStart = async () => {
114
+
115
+ this._serverError = false;
116
+
117
+ let lastTimestamp = -1;
118
+
119
+ while (this.state !== ProviderState.STOPPPED) {
120
+
121
+ const diffTime = TimeUtils.preciseTime() - lastTimestamp;
122
+ const timeToWait = Math.max(0, VisionRelocalization.MIN_TIME_BETWEEN_TWO_REQUESTS - diffTime);
123
+ await new Promise(resolve => setTimeout(resolve, timeToWait));
124
+
125
+ if (this.state === ProviderState.STOPPPED) {
126
+ break;
127
+ }
128
+
129
+ const url = VisionRelocalization.SERVICE_URL + VisionRelocalization.MAP_ID;
130
+
131
+ // 1. Prepare the request
132
+ if (!this._camera || this._camera.state !== Camera.State.STARTED) {
133
+ break;
134
+ }
135
+ const payload = await this._prepareRequest();
136
+
137
+ if (this.state === ProviderState.STOPPPED) {
138
+ break;
139
+ }
140
+
141
+ // 2. Send the request
142
+ const serverResponse = await fetch(url, {
143
+ method: 'POST',
144
+ body: JSON.stringify(payload),
145
+ headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' }
146
+ });
147
+
148
+ if (this.state === ProviderState.STOPPPED) {
149
+ break;
150
+ }
151
+
152
+ // 3. Parse the response
153
+ const events = await this._parseResponse(serverResponse, url, payload.time);
154
+ if (this._serverError || this.state === ProviderState.STOPPPED) {
155
+ break;
156
+ }
157
+
158
+ // 4. Notify events
159
+ if (events.length) {
160
+ this.notify(...events);
161
+ }
162
+
163
+ lastTimestamp = TimeUtils.preciseTime();
164
+ }
165
+
166
+ if (this.state !== ProviderState.STOPPPED) {
167
+ this.stop();
168
+ }
169
+ }
170
+
171
+ _internalStop = () => {
172
+ // do nothing
173
+ }
174
+
175
+
176
+ /**
177
+ * @returns {Object}
178
+ */
179
+ async _prepareRequest() {
180
+
181
+ const camera = this._camera;
182
+
183
+ // Retrieve the image
184
+ const image = await camera.currentImage;
185
+ const time = TimeUtils.preciseTime();
186
+ // TODO: move the grayscale conversion in the currentImage getter
187
+ CameraUtils.convertToGrayscale(image);
188
+ const reducedImage = CameraUtils.reduceImageSize(image, 1280);
189
+ // const reducedImage = CameraUtils.reduceImageSize(image, 720, 720);
190
+ const { height, width } = reducedImage;
191
+ const base64Image = CameraUtils.canvasToBase64(reducedImage);
192
+
193
+ // Retrieve the calibration matrix
194
+ const calibration = CameraUtils.createCameraCalibrationFromWidthHeightFov(
195
+ width, height, deg2rad(camera.hardwareVerticalFov)
196
+ );
197
+
198
+ // Build the payload
199
+ return {
200
+ calibration,
201
+ size: [width, height],
202
+ image: base64Image,
203
+ time
204
+ };
205
+ }
206
+
207
+ /**
208
+ * @param {Response} res
209
+ * @param {String} url
210
+ * @param {Number} requestTime
211
+ * @returns {ProviderEvent[]}
212
+ */
213
+ async _parseResponse(res, url, requestTime) {
214
+ if (res.status !== 200) {
215
+ Logger.warn(`The VPS server (${url}) has encountered a problem`);
216
+ this._serverError = true;
217
+ return [];
218
+ }
219
+
220
+ const json = await res.json();
221
+ if (json.error) {
222
+ return [];
223
+ }
224
+
225
+ const { attitude, userPosition } = VisionRelocalization._parseJsonResponse(json, requestTime);
226
+
227
+ const events = [
228
+ this.createEvent(EventType.AbsoluteAttitude, attitude),
229
+ this.createEvent(EventType.AbsolutePosition, userPosition)
230
+ ];
231
+
232
+ return events;
233
+ }
234
+
235
+
236
+ static _parseJsonResponse(json, requestTime) {
237
+
238
+ const quaternion = [
239
+ json.attitude.w,
240
+ json.attitude.x,
241
+ json.attitude.y,
242
+ json.attitude.z
243
+ ];
244
+ const quaternionNorm = Quaternion.norm(quaternion);
245
+ quaternion[0] /= quaternionNorm;
246
+ quaternion[1] /= quaternionNorm;
247
+ quaternion[2] /= quaternionNorm;
248
+ quaternion[3] /= quaternionNorm;
249
+
250
+ const quaternionWithScreenRotation = Quaternion.multiply(
251
+ Quaternion.fromAxisAngle([0, 0, 1], deg2rad(window.orientation || 0)), quaternion
252
+ );
253
+
254
+ const attitude = new Attitude(quaternionWithScreenRotation, requestTime, 0);
255
+
256
+
257
+ const userPosition = new UserPosition(
258
+ json.coordinates.lat,
259
+ json.coordinates.lng,
260
+ json.coordinates.alt,
261
+ json.coordinates.level ? new Level(json.coordinates.level) : null,
262
+ requestTime,
263
+ 0,
264
+ attitude.heading
265
+ );
266
+
267
+
268
+ return { userPosition, attitude };
269
+ }
270
+ }
271
+
272
+ export default new VisionRelocalization();