@wemap/providers 6.0.0 → 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.
@@ -1,9 +1,10 @@
1
1
  import { deg2rad, Vector3, Matrix, Quaternion, Matrix3, Matrix4, Vector, Rotations, std, diffAngleLines, diffAngle, rad2deg } from '@wemap/maths';
2
2
  import { BrowserUtils, Browser, PromiseUtils, TimeUtils } from '@wemap/utils';
3
3
  import Logger from '@wemap/logger';
4
- import { Attitude, RelativePosition, Constants as Constants$1, MapMatching, UserPosition, GraphEdge, AbsoluteHeading, GeoRelativePosition as GeoRelativePosition$1, Network } from '@wemap/geo';
4
+ import { GraphUtils, Level, Coordinates, Network, GraphRouterOptions, Attitude, RelativePosition, Constants as Constants$1, MapMatching, UserPosition, GraphEdge, AbsoluteHeading, GeoRelativePosition as GeoRelativePosition$1 } from '@wemap/geo';
5
5
  import geomagnetism from '@wemap/geomagnetism';
6
- import { Itinerary } from '@wemap/osm';
6
+ import '@wemap/osm';
7
+ import { SharedCameras, Camera, CameraUtils } from '@wemap/camera';
7
8
 
8
9
  /**
9
10
  * Event data types handled by {@link ProviderEvent}
@@ -185,10 +186,18 @@ const ProvidersOptions = {
185
186
 
186
187
  /**
187
188
  * Providers listed here will not be used by the system
188
- * List of {@link Provider#name}
189
+ * List of {@link Provider#pname}
189
190
  */
190
191
  ignoreProviders: [],
191
192
 
193
+ /**
194
+ * Some providers are not used by default (VPS, QRCodeScanner) because they
195
+ * require data from an optional external service or because they drain more
196
+ * battery. They can be added to this list to be used
197
+ * List of {@link Provider#pname}
198
+ */
199
+ optionalProviders: ['VisionRelocalization'],
200
+
192
201
  /**
193
202
  * Define the list of EventType that are optionals
194
203
  * List of {@link EventType}
@@ -201,7 +210,11 @@ const ProvidersOptions = {
201
210
 
202
211
  stepdetectionlocker: true,
203
212
 
204
- smoother: true
213
+ smoother: true,
214
+
215
+ get hasVps() {
216
+ return this.optionalProviders.includes('VisionRelocalization');
217
+ }
205
218
  };
206
219
 
207
220
  /**
@@ -321,9 +334,16 @@ class Provider {
321
334
  return false;
322
335
  }
323
336
 
337
+ /**
338
+ * Description of the function
339
+ * @name onEventsFunction
340
+ * @function
341
+ * @param {ProviderEvent[]} events the array of provider events
342
+ */
343
+
324
344
  /**
325
345
  *
326
- * @param {Function} onEvents
346
+ * @param {onEventsFunction} onEvents
327
347
  * @param {Function} onError
328
348
  * @param {Boolean} startIfNecessary
329
349
  * @returns {Number}
@@ -883,6 +903,724 @@ class MissingMagnetometerError extends MissingSensorError {
883
903
  }
884
904
  }
885
905
 
906
+ class LevelChange {
907
+
908
+ /** @type {!string} [up|down] */
909
+ direction;
910
+
911
+ /** @type {!number} [-2, -1, 1, ...] */
912
+ difference;
913
+
914
+ /** @type {?string} [elevator|conveyor|stairs] */
915
+ type = null;
916
+
917
+ /**
918
+ * @param {GraphNode<OsmElement>} firstNode
919
+ * @param {GraphNode<OsmElement>} secondNode
920
+ * @returns {LevelChange}
921
+ */
922
+ static fromTwoNodes(firstNode, secondNode) {
923
+
924
+ const levelChange = new LevelChange();
925
+
926
+ const edge = GraphUtils.getEdgeByNodes(firstNode.edges, firstNode, secondNode);
927
+
928
+ if (edge.builtFrom.isElevator) {
929
+ levelChange.type = 'elevator';
930
+ } else if (edge.builtFrom.isConveying) {
931
+ levelChange.type = 'conveyor';
932
+ } else if (edge.builtFrom.areStairs) {
933
+ levelChange.type = 'stairs';
934
+ }
935
+
936
+ levelChange.difference = Level.diff(firstNode.coords.level, secondNode.coords.level);
937
+ levelChange.direction = levelChange.difference > 0 ? 'up' : 'down';
938
+
939
+ return levelChange;
940
+ }
941
+
942
+ /**
943
+ * @returns {object}
944
+ */
945
+ toJson() {
946
+ return {
947
+ direction: this.direction,
948
+ difference: this.difference,
949
+ type: this.type
950
+ };
951
+ }
952
+
953
+ /**
954
+ * @param {object} json
955
+ * @returns {LevelChange}
956
+ */
957
+ static fromJson(json) {
958
+ const levelChange = new LevelChange();
959
+ levelChange.direction = json.direction;
960
+ levelChange.difference = json.difference;
961
+ levelChange.type = json.type;
962
+ return levelChange;
963
+ }
964
+ }
965
+
966
+ class Step {
967
+
968
+ /** @type {!boolean} */
969
+ firstStep = false;
970
+
971
+ /** @type {!boolean} */
972
+ lastStep = false;
973
+
974
+ /** @type {!number} */
975
+ number;
976
+
977
+ /** @type {!Coordinates} */
978
+ coords = [];
979
+
980
+
981
+ /** @type {!number} */
982
+ angle;
983
+
984
+ /** @type {!number} */
985
+ previousBearing;
986
+
987
+ /** @type {!number} */
988
+ nextBearing;
989
+
990
+
991
+ /** @type {!number} */
992
+ distance;
993
+
994
+ /** @type {?number} */
995
+ duration = null;
996
+
997
+ /** @type {?string} */
998
+ name = null;
999
+
1000
+
1001
+ /** @type {?LevelChange} */
1002
+ levelChange = null;
1003
+
1004
+ /** @type {?{?subwayEntrance: boolean, ?subwayEntranceRef: string}} */
1005
+ extras = {};
1006
+
1007
+ /** @type {!number} */
1008
+ _idCoordsInLeg = null;
1009
+
1010
+ /**
1011
+ * @returns {object}
1012
+ */
1013
+ toJson() {
1014
+ const output = {
1015
+ number: this.number,
1016
+ coords: this.coords.toCompressedJson(),
1017
+ angle: this.angle,
1018
+ previousBearing: this.previousBearing,
1019
+ nextBearing: this.nextBearing,
1020
+ distance: this.distance,
1021
+ _idCoordsInLeg: this._idCoordsInLeg
1022
+ };
1023
+ if (this.firstStep) {
1024
+ output.firstStep = true;
1025
+ }
1026
+ if (this.lastStep) {
1027
+ output.lastStep = true;
1028
+ }
1029
+ if (this.duration !== null) {
1030
+ output.duration = this.duration;
1031
+ }
1032
+ if (this.name !== null) {
1033
+ output.name = this.name;
1034
+ }
1035
+ if (this.levelChange !== null) {
1036
+ output.levelChange = this.levelChange.toJson();
1037
+ }
1038
+ if (this.extras && Object.keys(this.extras).length !== 0) {
1039
+ output.extras = this.extras;
1040
+ }
1041
+ return output;
1042
+ }
1043
+
1044
+ /**
1045
+ * @param {object} json
1046
+ * @returns {Step}
1047
+ */
1048
+ static fromJson(json) {
1049
+ const step = new Step();
1050
+ step.number = json.number;
1051
+ step.coords = Coordinates.fromCompressedJson(json.coords);
1052
+ step.angle = json.angle;
1053
+ step.previousBearing = json.previousBearing;
1054
+ step.nextBearing = json.nextBearing;
1055
+ step.distance = json.distance;
1056
+ step._idCoordsInLeg = json._idCoordsInLeg;
1057
+ if (json.firstStep) {
1058
+ step.firstStep = json.firstStep;
1059
+ }
1060
+ if (json.lastStep) {
1061
+ step.lastStep = json.lastStep;
1062
+ }
1063
+ if (json.duration) {
1064
+ step.duration = json.duration;
1065
+ }
1066
+ if (json.name) {
1067
+ step.name = json.name;
1068
+ }
1069
+ if (json.levelChange) {
1070
+ step.levelChange = LevelChange.fromJson(json.levelChange);
1071
+ }
1072
+ if (json.extras) {
1073
+ step.extras = json.extras;
1074
+ }
1075
+ return step;
1076
+ }
1077
+ }
1078
+
1079
+ class Leg {
1080
+
1081
+ /** @type {!string} can be WALK, BIKE, BUS, TRAM, CAR, FUNICULAR */
1082
+ mode;
1083
+
1084
+ /** @type {!number} */
1085
+ distance;
1086
+
1087
+ /** @type {!number} */
1088
+ duration;
1089
+
1090
+ /** @type {?number} */
1091
+ startTime = null;
1092
+
1093
+ /** @type {?number} */
1094
+ endTime = null;
1095
+
1096
+ /** @type {!{name: ?string, coords: !Coordinates}} */
1097
+ from;
1098
+
1099
+ /** @type {!{name: ?string, coords: !Coordinates}} */
1100
+ to;
1101
+
1102
+ /** @type {!Coordinates[]} */
1103
+ coords;
1104
+
1105
+ /** @type {?{name: !string, routeColor: ?string, routeTextColor: ?string, directionName: ?string}} */
1106
+ transportInfo = null;
1107
+
1108
+ /** @type {?(Step[])} */
1109
+ steps = null;
1110
+
1111
+ /**
1112
+ * @returns {Network}
1113
+ */
1114
+ toNetwork() {
1115
+ return Network.fromCoordinates([this.coords]);
1116
+ }
1117
+
1118
+ /**
1119
+ * @returns {object}
1120
+ */
1121
+ toJson() {
1122
+ const output = {
1123
+ mode: this.mode,
1124
+ from: { coords: this.from.coords.toCompressedJson() },
1125
+ to: { coords: this.to.coords.toCompressedJson() },
1126
+ distance: this.distance,
1127
+ duration: this.duration,
1128
+ coords: this.coords.map(coords => coords.toCompressedJson())
1129
+ };
1130
+ if (this.from.name) {
1131
+ output.from.name = this.from.name;
1132
+ }
1133
+ if (this.to.name) {
1134
+ output.to.name = this.to.name;
1135
+ }
1136
+ if (this.startTime !== null) {
1137
+ output.startTime = this.startTime;
1138
+ }
1139
+ if (this.endTime !== null) {
1140
+ output.endTime = this.endTime;
1141
+ }
1142
+ if (this.transportInfo !== null) {
1143
+ output.transportInfo = this.transportInfo;
1144
+ }
1145
+ if (this.steps !== null && this.steps.length > 0) {
1146
+ output.steps = this.steps.map(step => step.toJson());
1147
+ }
1148
+ return output;
1149
+ }
1150
+
1151
+
1152
+ /**
1153
+ * @param {object} json
1154
+ * @returns {Leg}
1155
+ */
1156
+ static fromJson(json) {
1157
+ const leg = new Leg();
1158
+ leg.mode = json.mode;
1159
+ leg.from = { coords: Coordinates.fromCompressedJson(json.from.coords) };
1160
+ leg.to = { coords: Coordinates.fromCompressedJson(json.to.coords) };
1161
+ leg.distance = json.distance;
1162
+ leg.duration = json.duration;
1163
+ leg.coords = json.coords.map(Coordinates.fromCompressedJson);
1164
+ if (json.from.name) {
1165
+ leg.from.name = json.from.name;
1166
+ }
1167
+ if (json.to.name) {
1168
+ leg.to.name = json.to.name;
1169
+ }
1170
+ if (json.startTime) {
1171
+ leg.startTime = json.startTime;
1172
+ }
1173
+ if (json.endTime) {
1174
+ leg.endTime = json.endTime;
1175
+ }
1176
+ if (json.transportInfo) {
1177
+ leg.transportInfo = json.transportInfo;
1178
+ }
1179
+ if (json.steps) {
1180
+ leg.steps = json.steps.map(Step.fromJson);
1181
+ }
1182
+ return leg;
1183
+ }
1184
+
1185
+ }
1186
+
1187
+ /**
1188
+ * Get route duration
1189
+ * @param {Number} speed in km/h
1190
+ * @returns {Number} duration in seconds
1191
+ */
1192
+ function getDurationFromLength(length, speed = 5) {
1193
+ return length / (speed * 1000 / 3600);
1194
+ }
1195
+
1196
+ /* eslint-disable max-statements */
1197
+
1198
+ /**
1199
+ * Main attributes are:
1200
+ * nodes: the ordered list of Node
1201
+ * edges: the ordered list of Edge
1202
+ * start: the start point (Coordinates)
1203
+ * end: the end point (Coordinates)
1204
+ * length: the route length
1205
+ */
1206
+ class Itinerary {
1207
+
1208
+ /** @type {!Coordinates} */
1209
+ from;
1210
+
1211
+ /** @type {!Coordinates} */
1212
+ to;
1213
+
1214
+ /** @type {!number} */
1215
+ distance;
1216
+
1217
+ /** @type {!number} */
1218
+ duration;
1219
+
1220
+ /** @type {!string} can be WALK, BIKE, CAR, PT */
1221
+ _mode;
1222
+
1223
+ /** @type {?number} */
1224
+ startTime = null;
1225
+
1226
+ /** @type {?number} */
1227
+ endTime = null;
1228
+
1229
+ /** @type {!(Leg[])} */
1230
+ legs = [];
1231
+
1232
+ /** @type {?Coordinates[]} */
1233
+ _coords = null;
1234
+
1235
+ set coords(_) {
1236
+ throw new Error('Itinerary.coords cannot be set. They are calculated from Itinerary.legs.');
1237
+ }
1238
+
1239
+ /** @type {!(Coordinates[])} */
1240
+ get coords() {
1241
+ if (!this._coords) {
1242
+ // Returns the coordinates contained in all legs and remove duplicates between array
1243
+ this._coords = this.legs.reduce((acc, val) => {
1244
+ const isDuplicate = acc.length && val.coords.length && acc[acc.length - 1].equalsTo(val.coords[0]);
1245
+ acc.push(...val.coords.slice(isDuplicate ? 1 : 0));
1246
+ return acc;
1247
+ }, []);
1248
+ }
1249
+ return this._coords;
1250
+ }
1251
+
1252
+ set steps(_) {
1253
+ throw new Error('Itinerary.step cannot be set. They are calculated from Itinerary.legs.');
1254
+ }
1255
+
1256
+ /** @type {!(Step[])} */
1257
+ get steps() {
1258
+ return this.legs.map(leg => leg.steps).flat();
1259
+ }
1260
+
1261
+ set mode(_) {
1262
+ throw new Error('Itinerary.mode cannot be set. They are calculated from Itinerary.legs.');
1263
+ }
1264
+
1265
+ get mode() {
1266
+ if (!this._mode) {
1267
+ let isPublicTransport;
1268
+ let isBicycle;
1269
+ let isDriving;
1270
+
1271
+ this.legs.forEach((leg) => {
1272
+ isPublicTransport = isPublicTransport || ['BUS', 'FUNICULAR', 'TRAM', 'TRAIN'].includes(leg.mode);
1273
+ isBicycle = isBicycle || ['BIKE', 'BICYCLE'].includes(leg.mode);
1274
+ isDriving = isDriving || leg.mode === 'CAR';
1275
+ });
1276
+
1277
+ if (isPublicTransport) {
1278
+ this._mode = 'PT';
1279
+ } else if (isDriving) {
1280
+ this._mode = 'CAR';
1281
+ } else if (isBicycle) {
1282
+ this._mode = 'BIKE';
1283
+ } else {
1284
+ this._mode = 'WALK';
1285
+ }
1286
+ }
1287
+
1288
+ return this._mode;
1289
+
1290
+ }
1291
+
1292
+ /**
1293
+ * @returns {Network}
1294
+ */
1295
+ toNetwork() {
1296
+ return Network.fromCoordinates([this.coords]);
1297
+ }
1298
+
1299
+ /**
1300
+ * @param {Itinerary[]} itineraries
1301
+ * @returns {Itinerary}
1302
+ */
1303
+ static fromItineraries(...itineraries) {
1304
+ const itinerary = new Itinerary();
1305
+ itinerary.from = itineraries[0].from;
1306
+ itinerary.to = itineraries[itineraries.length - 1].to;
1307
+ itinerary.distance = 0;
1308
+ itinerary.duration = 0;
1309
+ itinerary.legs = [];
1310
+
1311
+ itineraries.forEach(_itinerary => {
1312
+ itinerary.distance += _itinerary.distance;
1313
+ itinerary.duration += _itinerary.duration;
1314
+ itinerary.legs.push(..._itinerary.legs);
1315
+ itinerary.legs.forEach(leg => {
1316
+ leg.steps[0].firstStep = false;
1317
+ leg.steps[leg.steps.length - 1].lastStep = false;
1318
+ });
1319
+ });
1320
+
1321
+ itinerary.legs[0].steps[0].firstStep = true;
1322
+ const lastLeg = itinerary.legs[itinerary.legs.length - 1];
1323
+ lastLeg.steps[lastLeg.steps.length - 1].lastStep = true;
1324
+
1325
+ return itinerary;
1326
+ }
1327
+
1328
+ /**
1329
+ * Convert lat/lng/level points to Itinerary
1330
+ * @param {number[][]} points 2D points array of lat/lng/level (level is optional)
1331
+ * @param {Coordinates} from
1332
+ * @param {Coordinates} to
1333
+ * @param {string} mode
1334
+ * @returns {Itinerary}
1335
+ */
1336
+ static fromOrderedPointsArray(points, start, end) {
1337
+
1338
+ const pointToCoordinates = point => new Coordinates(point[0], point[1], null, point[2]);
1339
+
1340
+ return this.fromOrderedCoordinates(
1341
+ points.map(pointToCoordinates),
1342
+ pointToCoordinates(start),
1343
+ pointToCoordinates(end)
1344
+ );
1345
+ }
1346
+
1347
+ /**
1348
+ * Convert ordered Coordinates to Itinerary
1349
+ * @param {Coordinates[]} points
1350
+ * @param {Coordinates} from
1351
+ * @param {Coordinates} to
1352
+ * @param {string} mode
1353
+ * @returns {Itinerary}
1354
+ */
1355
+ static fromOrderedCoordinates(points, from, to, mode = 'WALK') {
1356
+
1357
+ const itinerary = new Itinerary();
1358
+ itinerary.from = from;
1359
+ itinerary.to = to;
1360
+
1361
+ const leg = new Leg();
1362
+ leg.mode = mode;
1363
+ leg.from = { name: null, coords: from };
1364
+ leg.to = { name: null, coords: to };
1365
+
1366
+ leg.coords = points;
1367
+ leg.distance = points.reduce((acc, coords, idx, arr) => {
1368
+ if (idx !== 0) {
1369
+ return acc + arr[idx - 1].distanceTo(coords);
1370
+ }
1371
+ return acc;
1372
+ }, 0);
1373
+ leg.duration = getDurationFromLength(leg.distance);
1374
+ itinerary.legs.push(leg);
1375
+
1376
+ itinerary.distance = leg.distance;
1377
+ itinerary.duration = leg.duration;
1378
+
1379
+ return itinerary;
1380
+ }
1381
+
1382
+ /**
1383
+ * @returns {object}
1384
+ */
1385
+ toJson() {
1386
+ const output = {
1387
+ from: this.from.toCompressedJson(),
1388
+ to: this.to.toCompressedJson(),
1389
+ distance: this.distance,
1390
+ duration: this.duration,
1391
+ mode: this.mode,
1392
+ legs: this.legs.map(leg => leg.toJson())
1393
+ };
1394
+ if (this.startTime !== null) {
1395
+ output.startTime = this.startTime;
1396
+ }
1397
+ if (this.endTime !== null) {
1398
+ output.endTime = this.endTime;
1399
+ }
1400
+ return output;
1401
+ }
1402
+
1403
+ /**
1404
+ * @param {object} json
1405
+ * @returns {Itinerary}
1406
+ */
1407
+ static fromJson(json) {
1408
+ const itinerary = new Itinerary();
1409
+ itinerary.from = Coordinates.fromCompressedJson(json.from);
1410
+ itinerary.to = Coordinates.fromCompressedJson(json.to);
1411
+ itinerary.distance = json.distance;
1412
+ itinerary.duration = json.duration;
1413
+ itinerary.legs = json.legs.map(Leg.fromJson);
1414
+ if (json.startTime) {
1415
+ itinerary.startTime = json.startTime;
1416
+ }
1417
+ if (json.endTime) {
1418
+ itinerary.endTime = json.endTime;
1419
+ }
1420
+ return itinerary;
1421
+ }
1422
+ }
1423
+
1424
+ class WemapRouterOptions extends GraphRouterOptions {
1425
+
1426
+ /** @type {WemapRouterOptions} */
1427
+ static DEFAULT = new WemapRouterOptions();
1428
+
1429
+ /**
1430
+ * @returns {WemapRouterOptions}
1431
+ */
1432
+ static get WITHOUT_STAIRS() {
1433
+ const options = new WemapRouterOptions();
1434
+ options.acceptEdgeFn = edge => edge.builtFrom.tags.highway !== 'steps';
1435
+ return options;
1436
+ }
1437
+
1438
+ /**
1439
+ * Get route duration
1440
+ * @param {Number} speed in km/h
1441
+ */
1442
+ static getDurationFromLength(length, speed = 5) {
1443
+ return length / (speed * 1000 / 3600);
1444
+ }
1445
+
1446
+ /** @type {function(GraphEdge<OsmElement>):boolean} */
1447
+ weightEdgeFn = edge => edge.builtFrom.isElevator ? 30 : WemapRouterOptions.getDurationFromLength(edge.length);
1448
+
1449
+
1450
+ }
1451
+
1452
+ /* eslint-disable complexity */
1453
+
1454
+ deg2rad(20);
1455
+
1456
+ function createCommonjsModule(fn) {
1457
+ var module = { exports: {} };
1458
+ return fn(module, module.exports), module.exports;
1459
+ }
1460
+
1461
+ createCommonjsModule(function (module) {
1462
+
1463
+ /**
1464
+ * Based off of [the offical Google document](https://developers.google.com/maps/documentation/utilities/polylinealgorithm)
1465
+ *
1466
+ * Some parts from [this implementation](http://facstaff.unca.edu/mcmcclur/GoogleMaps/EncodePolyline/PolylineEncoder.js)
1467
+ * by [Mark McClure](http://facstaff.unca.edu/mcmcclur/)
1468
+ *
1469
+ * @module polyline
1470
+ */
1471
+
1472
+ var polyline = {};
1473
+
1474
+ function py2_round(value) {
1475
+ // Google's polyline algorithm uses the same rounding strategy as Python 2, which is different from JS for negative values
1476
+ return Math.floor(Math.abs(value) + 0.5) * (value >= 0 ? 1 : -1);
1477
+ }
1478
+
1479
+ function encode(current, previous, factor) {
1480
+ current = py2_round(current * factor);
1481
+ previous = py2_round(previous * factor);
1482
+ var coordinate = current - previous;
1483
+ coordinate <<= 1;
1484
+ if (current - previous < 0) {
1485
+ coordinate = ~coordinate;
1486
+ }
1487
+ var output = '';
1488
+ while (coordinate >= 0x20) {
1489
+ output += String.fromCharCode((0x20 | (coordinate & 0x1f)) + 63);
1490
+ coordinate >>= 5;
1491
+ }
1492
+ output += String.fromCharCode(coordinate + 63);
1493
+ return output;
1494
+ }
1495
+
1496
+ /**
1497
+ * Decodes to a [latitude, longitude] coordinates array.
1498
+ *
1499
+ * This is adapted from the implementation in Project-OSRM.
1500
+ *
1501
+ * @param {String} str
1502
+ * @param {Number} precision
1503
+ * @returns {Array}
1504
+ *
1505
+ * @see https://github.com/Project-OSRM/osrm-frontend/blob/master/WebContent/routing/OSRM.RoutingGeometry.js
1506
+ */
1507
+ polyline.decode = function(str, precision) {
1508
+ var index = 0,
1509
+ lat = 0,
1510
+ lng = 0,
1511
+ coordinates = [],
1512
+ shift = 0,
1513
+ result = 0,
1514
+ byte = null,
1515
+ latitude_change,
1516
+ longitude_change,
1517
+ factor = Math.pow(10, Number.isInteger(precision) ? precision : 5);
1518
+
1519
+ // Coordinates have variable length when encoded, so just keep
1520
+ // track of whether we've hit the end of the string. In each
1521
+ // loop iteration, a single coordinate is decoded.
1522
+ while (index < str.length) {
1523
+
1524
+ // Reset shift, result, and byte
1525
+ byte = null;
1526
+ shift = 0;
1527
+ result = 0;
1528
+
1529
+ do {
1530
+ byte = str.charCodeAt(index++) - 63;
1531
+ result |= (byte & 0x1f) << shift;
1532
+ shift += 5;
1533
+ } while (byte >= 0x20);
1534
+
1535
+ latitude_change = ((result & 1) ? ~(result >> 1) : (result >> 1));
1536
+
1537
+ shift = result = 0;
1538
+
1539
+ do {
1540
+ byte = str.charCodeAt(index++) - 63;
1541
+ result |= (byte & 0x1f) << shift;
1542
+ shift += 5;
1543
+ } while (byte >= 0x20);
1544
+
1545
+ longitude_change = ((result & 1) ? ~(result >> 1) : (result >> 1));
1546
+
1547
+ lat += latitude_change;
1548
+ lng += longitude_change;
1549
+
1550
+ coordinates.push([lat / factor, lng / factor]);
1551
+ }
1552
+
1553
+ return coordinates;
1554
+ };
1555
+
1556
+ /**
1557
+ * Encodes the given [latitude, longitude] coordinates array.
1558
+ *
1559
+ * @param {Array.<Array.<Number>>} coordinates
1560
+ * @param {Number} precision
1561
+ * @returns {String}
1562
+ */
1563
+ polyline.encode = function(coordinates, precision) {
1564
+ if (!coordinates.length) { return ''; }
1565
+
1566
+ var factor = Math.pow(10, Number.isInteger(precision) ? precision : 5),
1567
+ output = encode(coordinates[0][0], 0, factor) + encode(coordinates[0][1], 0, factor);
1568
+
1569
+ for (var i = 1; i < coordinates.length; i++) {
1570
+ var a = coordinates[i], b = coordinates[i - 1];
1571
+ output += encode(a[0], b[0], factor);
1572
+ output += encode(a[1], b[1], factor);
1573
+ }
1574
+
1575
+ return output;
1576
+ };
1577
+
1578
+ function flipped(coords) {
1579
+ var flipped = [];
1580
+ for (var i = 0; i < coords.length; i++) {
1581
+ var coord = coords[i].slice();
1582
+ flipped.push([coord[1], coord[0]]);
1583
+ }
1584
+ return flipped;
1585
+ }
1586
+
1587
+ /**
1588
+ * Encodes a GeoJSON LineString feature/geometry.
1589
+ *
1590
+ * @param {Object} geojson
1591
+ * @param {Number} precision
1592
+ * @returns {String}
1593
+ */
1594
+ polyline.fromGeoJSON = function(geojson, precision) {
1595
+ if (geojson && geojson.type === 'Feature') {
1596
+ geojson = geojson.geometry;
1597
+ }
1598
+ if (!geojson || geojson.type !== 'LineString') {
1599
+ throw new Error('Input must be a GeoJSON LineString');
1600
+ }
1601
+ return polyline.encode(flipped(geojson.coordinates), precision);
1602
+ };
1603
+
1604
+ /**
1605
+ * Decodes to a GeoJSON LineString geometry.
1606
+ *
1607
+ * @param {String} str
1608
+ * @param {Number} precision
1609
+ * @returns {Object}
1610
+ */
1611
+ polyline.toGeoJSON = function(str, precision) {
1612
+ var coords = polyline.decode(str, precision);
1613
+ return {
1614
+ type: 'LineString',
1615
+ coordinates: flipped(coords)
1616
+ };
1617
+ };
1618
+
1619
+ if (module.exports) {
1620
+ module.exports = polyline;
1621
+ }
1622
+ });
1623
+
886
1624
  const DEFAULT_RELATIVE_NOISES = {
887
1625
  acc: 0.5,
888
1626
  gyr: 0.3
@@ -2933,6 +3671,277 @@ class GeoRelativePosition extends Provider {
2933
3671
 
2934
3672
  var GeoRelativePositionProvider = new GeoRelativePosition();
2935
3673
 
3674
+ function preciseTime() {
3675
+ if (typeof window !== 'undefined' && window.performance) {
3676
+ return window.performance.now();
3677
+ }
3678
+ const hrtime = process.hrtime();
3679
+ return Math.round((hrtime[0] * 1e3) + (hrtime[1] / 1e6));
3680
+ }
3681
+
3682
+ /* eslint-disable max-statements */
3683
+
3684
+ class VisionRelocalization extends Provider {
3685
+
3686
+ // static SERVICE_URL = 'http://localhost:45678/';
3687
+ static SERVICE_URL = 'https://vps.maaap.it/';
3688
+ static MAP_ID = 'wemap';
3689
+ static MIN_TIME_BETWEEN_TWO_REQUESTS = 1000;
3690
+
3691
+ /** @type {boolean} */
3692
+ _serverError = false;
3693
+
3694
+ /** @type {boolean} */
3695
+ _cameraError = false;
3696
+
3697
+ /** @type {Camera} */
3698
+ _camera = null;
3699
+
3700
+ /**
3701
+ * @override
3702
+ */
3703
+ static get pname() {
3704
+ return 'VisionRelocalization';
3705
+ }
3706
+
3707
+ /**
3708
+ * @override
3709
+ */
3710
+ static get eventsType() {
3711
+ return [EventType.AbsoluteAttitude, EventType.AbsolutePosition];
3712
+ }
3713
+
3714
+ /**
3715
+ * @override
3716
+ */
3717
+ get _availability() {
3718
+ return Promise.resolve();
3719
+ }
3720
+
3721
+ /**
3722
+ * @override
3723
+ */
3724
+ start() {
3725
+
3726
+ // 1. Add listeners on shared cameras to detect new cameras
3727
+ SharedCameras.on('added', this._onCameraDetected);
3728
+ SharedCameras.on('removed', this._onCameraRemoved);
3729
+
3730
+ // 2. If a camera already exists, use it
3731
+ if (SharedCameras.list.length) {
3732
+ if (SharedCameras.list.length > 1) {
3733
+ Logger.warn('It seems that more than 1 camera has been detected'
3734
+ + ' for VPS. Taking the first...');
3735
+ }
3736
+ this._useCamera(SharedCameras.list[0].camera);
3737
+ }
3738
+ }
3739
+
3740
+ /**
3741
+ * @override
3742
+ */
3743
+ stop() {
3744
+ SharedCameras.off('added', this._onCameraDetected);
3745
+ SharedCameras.off('removed', this._onCameraRemoved);
3746
+
3747
+ this._camera = null;
3748
+ }
3749
+
3750
+
3751
+ _onCameraDetected = ({ camera }) => {
3752
+ if (this._camera) {
3753
+ Logger.warn('It seems that more than 1 camera has been detected'
3754
+ + ' for VPS. Taking the first...');
3755
+ }
3756
+ this._useCamera(camera);
3757
+ }
3758
+
3759
+ _onCameraRemoved = () => {
3760
+ if (this._camera) {
3761
+ this._camera.off('started', this._internalStart);
3762
+ this._camera.off('stopped', this._internalStop);
3763
+ } else {
3764
+ Logger.warn('There is no previously detected camera but once has stopped');
3765
+ }
3766
+ this._camera = null;
3767
+ }
3768
+
3769
+ /**
3770
+ * @param {Camera} camera
3771
+ */
3772
+ _useCamera(camera) {
3773
+ this._camera = camera;
3774
+
3775
+ camera.on('started', this._internalStart);
3776
+ camera.on('stopped', this._internalStop);
3777
+
3778
+ if (camera.isStarted) {
3779
+ this._internalStart();
3780
+ }
3781
+ }
3782
+
3783
+
3784
+ _internalStart = async () => {
3785
+
3786
+ this._serverError = false;
3787
+
3788
+ let lastTimestamp = -1;
3789
+
3790
+ while (this.state !== ProviderState.STOPPPED) {
3791
+
3792
+ const diffTime = preciseTime() - lastTimestamp;
3793
+ const timeToWait = Math.max(0, VisionRelocalization.MIN_TIME_BETWEEN_TWO_REQUESTS - diffTime);
3794
+ await new Promise(resolve => setTimeout(resolve, timeToWait));
3795
+
3796
+ if (this.state === ProviderState.STOPPPED) {
3797
+ break;
3798
+ }
3799
+
3800
+ const url = VisionRelocalization.SERVICE_URL + VisionRelocalization.MAP_ID;
3801
+
3802
+ // 1. Prepare the request
3803
+ if (!this._camera || this._camera.state !== Camera.State.STARTED) {
3804
+ break;
3805
+ }
3806
+ const payload = await this._prepareRequest();
3807
+
3808
+ if (this.state === ProviderState.STOPPPED) {
3809
+ break;
3810
+ }
3811
+
3812
+ // 2. Send the request
3813
+ const serverResponse = await fetch(url, {
3814
+ method: 'POST',
3815
+ body: JSON.stringify(payload),
3816
+ headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' }
3817
+ });
3818
+
3819
+ if (this.state === ProviderState.STOPPPED) {
3820
+ break;
3821
+ }
3822
+
3823
+ // 3. Parse the response
3824
+ const events = await this._parseResponse(serverResponse, url, payload.time);
3825
+ if (this._serverError || this.state === ProviderState.STOPPPED) {
3826
+ break;
3827
+ }
3828
+
3829
+ // 4. Notify events
3830
+ if (events.length) {
3831
+ this.notify(...events);
3832
+ }
3833
+
3834
+ lastTimestamp = preciseTime();
3835
+ }
3836
+
3837
+ if (this.state !== ProviderState.STOPPPED) {
3838
+ this.stop();
3839
+ }
3840
+ }
3841
+
3842
+ _internalStop = () => {
3843
+ // do nothing
3844
+ }
3845
+
3846
+
3847
+ /**
3848
+ * @returns {Object}
3849
+ */
3850
+ async _prepareRequest() {
3851
+
3852
+ const camera = this._camera;
3853
+
3854
+ // Retrieve the image
3855
+ const image = await camera.currentImage;
3856
+ const time = preciseTime();
3857
+ // TODO: move the grayscale conversion in the currentImage getter
3858
+ CameraUtils.convertToGrayscale(image);
3859
+ const reducedImage = CameraUtils.reduceImageSize(image, 1280);
3860
+ // const reducedImage = CameraUtils.reduceImageSize(image, 720, 720);
3861
+ const { height, width } = reducedImage;
3862
+ const base64Image = CameraUtils.canvasToBase64(reducedImage);
3863
+
3864
+ // Retrieve the calibration matrix
3865
+ const calibration = CameraUtils.createCameraCalibrationFromWidthHeightFov(
3866
+ width, height, deg2rad(camera.hardwareVerticalFov)
3867
+ );
3868
+
3869
+ // Build the payload
3870
+ return {
3871
+ calibration,
3872
+ size: [width, height],
3873
+ image: base64Image,
3874
+ time
3875
+ };
3876
+ }
3877
+
3878
+ /**
3879
+ * @param {Response} res
3880
+ * @param {String} url
3881
+ * @param {Number} requestTime
3882
+ * @returns {ProviderEvent[]}
3883
+ */
3884
+ async _parseResponse(res, url, requestTime) {
3885
+ if (res.status !== 200) {
3886
+ Logger.warn(`The VPS server (${url}) has encountered a problem`);
3887
+ this._serverError = true;
3888
+ return [];
3889
+ }
3890
+
3891
+ const json = await res.json();
3892
+ if (json.error) {
3893
+ return [];
3894
+ }
3895
+
3896
+ const { attitude, userPosition } = VisionRelocalization._parseJsonResponse(json, requestTime);
3897
+
3898
+ const events = [
3899
+ this.createEvent(EventType.AbsoluteAttitude, attitude),
3900
+ this.createEvent(EventType.AbsolutePosition, userPosition)
3901
+ ];
3902
+
3903
+ return events;
3904
+ }
3905
+
3906
+
3907
+ static _parseJsonResponse(json, requestTime) {
3908
+
3909
+ const quaternion = [
3910
+ json.attitude.w,
3911
+ json.attitude.x,
3912
+ json.attitude.y,
3913
+ json.attitude.z
3914
+ ];
3915
+ const quaternionNorm = Quaternion.norm(quaternion);
3916
+ quaternion[0] /= quaternionNorm;
3917
+ quaternion[1] /= quaternionNorm;
3918
+ quaternion[2] /= quaternionNorm;
3919
+ quaternion[3] /= quaternionNorm;
3920
+
3921
+ const quaternionWithScreenRotation = Quaternion.multiply(
3922
+ Quaternion.fromAxisAngle([0, 0, 1], deg2rad(window.orientation || 0)), quaternion
3923
+ );
3924
+
3925
+ const attitude = new Attitude(quaternionWithScreenRotation, requestTime, 0);
3926
+
3927
+
3928
+ const userPosition = new UserPosition(
3929
+ json.coordinates.lat,
3930
+ json.coordinates.lng,
3931
+ json.coordinates.alt,
3932
+ json.coordinates.level ? new Level(json.coordinates.level) : null,
3933
+ requestTime,
3934
+ 0,
3935
+ attitude.heading
3936
+ );
3937
+
3938
+
3939
+ return { userPosition, attitude };
3940
+ }
3941
+ }
3942
+
3943
+ var VisionRelocalization$1 = new VisionRelocalization();
3944
+
2936
3945
  class AbsolutePosition extends Provider {
2937
3946
 
2938
3947
  // Use the new absolute position if its accuracy is at least x times better than the last one.
@@ -2990,13 +3999,29 @@ class AbsolutePosition extends Provider {
2990
3999
  // do nothing
2991
4000
  });
2992
4001
 
2993
- this._gnssWifiProviderId = GnssWifi$1.addEventListener(
2994
- events => {
2995
- // bearing from GnssWifi is not reliable for our usecase
2996
- events[0].data.bearing = null;
2997
- this._onAbsolutePosition(events[0], false);
2998
- }
2999
- );
4002
+ if (ProvidersOptions.hasVps) {
4003
+
4004
+ const vpsProviderId = VisionRelocalization$1.addEventListener(events => {
4005
+ for (const providerEvent of events) {
4006
+ if (providerEvent.dataType === EventType.AbsolutePosition) {
4007
+ this._onAbsolutePosition(providerEvent);
4008
+ VisionRelocalization$1.removeEventListener(vpsProviderId);
4009
+ return;
4010
+ }
4011
+ }
4012
+ });
4013
+
4014
+ } else {
4015
+
4016
+ this._gnssWifiProviderId = GnssWifi$1.addEventListener(
4017
+ events => {
4018
+ // bearing from GnssWifi is not reliable for our usecase
4019
+ events[0].data.bearing = null;
4020
+ this._onAbsolutePosition(events[0], false);
4021
+ }
4022
+ );
4023
+
4024
+ }
3000
4025
 
3001
4026
  this._mapMatchingHandlerId = MapMatchingHandler$1.addEventListener();
3002
4027
  }
@@ -4267,6 +5292,7 @@ var Providers = /*#__PURE__*/Object.freeze({
4267
5292
  GnssWifi: GnssWifi$1,
4268
5293
  Ip: Ip$1,
4269
5294
  AbsolutePosition: AbsolutePosition$1,
5295
+ VisionRelocalization: VisionRelocalization$1,
4270
5296
  Barcode: Barcode$1,
4271
5297
  CameraNative: CameraNative$1,
4272
5298
  CameraProjectionMatrix: CameraProjectionMatrix$1
@@ -4357,6 +5383,9 @@ class ProvidersInterface {
4357
5383
  case EventType.CameraProjectionMatrix:
4358
5384
  return CameraProjectionMatrix$1;
4359
5385
 
5386
+ case EventType.MagnetometerNeedCalibration:
5387
+ return MagnetometerCalibrationDetector$1;
5388
+
4360
5389
  default:
4361
5390
  throw new Error(`Unable to deal with this event type: ${eventType}`);
4362
5391
  }