@wemap/routers 11.4.0 → 11.7.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/dist/index.js +249 -84
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +236 -65
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -4
- package/src/wemap-osm/OsmGraph.spec.ts +5 -5
- package/src/wemap-osm/OsmGraph.ts +129 -62
- package/src/wemap-osm/OsmRouter.elevators.spec.ts +105 -0
- package/src/wemap-osm/OsmRouter.spec.ts +14 -14
package/dist/index.js
CHANGED
|
@@ -5,7 +5,7 @@ var __publicField = (obj, key, value) => {
|
|
|
5
5
|
__defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
6
6
|
return value;
|
|
7
7
|
};
|
|
8
|
-
Object.
|
|
8
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
9
9
|
const geo = require("@wemap/geo");
|
|
10
10
|
const maths = require("@wemap/maths");
|
|
11
11
|
const salesman = require("@wemap/salesman.js");
|
|
@@ -14,12 +14,6 @@ const Logger = require("@wemap/logger");
|
|
|
14
14
|
const Polyline = require("@mapbox/polyline");
|
|
15
15
|
const pointInPolygon = require("@turf/boolean-point-in-polygon");
|
|
16
16
|
const convexHullFn = require("@turf/convex");
|
|
17
|
-
const _interopDefaultLegacy = (e) => e && typeof e === "object" && "default" in e ? e : { default: e };
|
|
18
|
-
const salesman__default = /* @__PURE__ */ _interopDefaultLegacy(salesman);
|
|
19
|
-
const Logger__default = /* @__PURE__ */ _interopDefaultLegacy(Logger);
|
|
20
|
-
const Polyline__default = /* @__PURE__ */ _interopDefaultLegacy(Polyline);
|
|
21
|
-
const pointInPolygon__default = /* @__PURE__ */ _interopDefaultLegacy(pointInPolygon);
|
|
22
|
-
const convexHullFn__default = /* @__PURE__ */ _interopDefaultLegacy(convexHullFn);
|
|
23
17
|
function getDurationFromLength(length, speed = 5) {
|
|
24
18
|
return length / (speed * 1e3 / 3600);
|
|
25
19
|
}
|
|
@@ -245,6 +239,8 @@ class Leg {
|
|
|
245
239
|
stepsGenerationRules
|
|
246
240
|
});
|
|
247
241
|
}
|
|
242
|
+
// TODO: Remove when possible...
|
|
243
|
+
// Livemap specific
|
|
248
244
|
multiplyLevel(levelFactor) {
|
|
249
245
|
this.from.coords.level = geo.Level.multiplyBy(this.from.coords.level, levelFactor);
|
|
250
246
|
this.to.coords.level = geo.Level.multiplyBy(this.to.coords.level, levelFactor);
|
|
@@ -285,6 +281,7 @@ class Leg {
|
|
|
285
281
|
firstStep: stepId === 0,
|
|
286
282
|
lastStep: stepId === stepsInfo.length - 1,
|
|
287
283
|
distance,
|
|
284
|
+
// stepInfo.distance is overwritten
|
|
288
285
|
duration: stepInfo.duration || getDurationFromLength(distance),
|
|
289
286
|
levelChange: stepInfo.levelChange || null,
|
|
290
287
|
extras: stepInfo.extras || null
|
|
@@ -384,6 +381,9 @@ class Itinerary {
|
|
|
384
381
|
legs
|
|
385
382
|
});
|
|
386
383
|
}
|
|
384
|
+
/**
|
|
385
|
+
* Convert lat/lng/level? points to Itinerary
|
|
386
|
+
*/
|
|
387
387
|
static fromOrderedPointsArray(points, start, end) {
|
|
388
388
|
const pointToCoordinates = (point) => new geo.Coordinates(point[0], point[1], null, point[2]);
|
|
389
389
|
return this.fromOrderedCoordinates(
|
|
@@ -392,6 +392,9 @@ class Itinerary {
|
|
|
392
392
|
pointToCoordinates(end)
|
|
393
393
|
);
|
|
394
394
|
}
|
|
395
|
+
/**
|
|
396
|
+
* Convert ordered Coordinates to Itinerary
|
|
397
|
+
*/
|
|
395
398
|
static fromOrderedCoordinates(coords, from, to, mode = "WALK") {
|
|
396
399
|
const leg = new Leg({
|
|
397
400
|
from: { coords: from },
|
|
@@ -446,11 +449,15 @@ class Itinerary {
|
|
|
446
449
|
legs: [leg]
|
|
447
450
|
});
|
|
448
451
|
}
|
|
452
|
+
// TODO: Remove when possible...
|
|
453
|
+
// Livemap specific
|
|
449
454
|
multiplyLevel(levelFactor) {
|
|
450
455
|
this.from.level = geo.Level.multiplyBy(this.from.level, levelFactor);
|
|
451
456
|
this.to.level = geo.Level.multiplyBy(this.to.level, levelFactor);
|
|
452
457
|
this.legs.forEach((leg) => leg.multiplyLevel(levelFactor));
|
|
453
458
|
}
|
|
459
|
+
// TODO: Remove when possible...
|
|
460
|
+
// Livemap specific
|
|
454
461
|
forceUnknownLevelTo0() {
|
|
455
462
|
this.from.level = this.from.level || 0;
|
|
456
463
|
this.to.level = this.to.level || 0;
|
|
@@ -482,6 +489,14 @@ class Itinerary {
|
|
|
482
489
|
}
|
|
483
490
|
};
|
|
484
491
|
}
|
|
492
|
+
/**
|
|
493
|
+
* Update steps info thanks to the coordinates of the whole itinerary.
|
|
494
|
+
* This method will update:
|
|
495
|
+
* - all steps number
|
|
496
|
+
* - first/last steps
|
|
497
|
+
* - previousBearing/nextBearing/angle of first and last step of each leg
|
|
498
|
+
* - distance/duration of first and last step of each leg
|
|
499
|
+
*/
|
|
485
500
|
updateStepsFromLegs() {
|
|
486
501
|
const itineraryCoords = this.coords.filter((coords, idx, arr) => idx === 0 || !arr[idx - 1].equals(coords));
|
|
487
502
|
const steps = this.legs.map((leg) => leg.steps).flat();
|
|
@@ -566,6 +581,8 @@ class RouterResponse {
|
|
|
566
581
|
error: json.error
|
|
567
582
|
});
|
|
568
583
|
}
|
|
584
|
+
// TODO: Remove when possible...
|
|
585
|
+
// Livemap specific
|
|
569
586
|
multiplyLevel(levelFactor) {
|
|
570
587
|
this.from.level = geo.Level.multiplyBy(this.from.level, levelFactor);
|
|
571
588
|
this.to.level = geo.Level.multiplyBy(this.to.level, levelFactor);
|
|
@@ -579,7 +596,7 @@ const DEFAULT_WAY_SELECTOR = (way) => {
|
|
|
579
596
|
const isElevatorArea = way.tags.highway === "elevator" && way.isArea;
|
|
580
597
|
return HIGHWAYS_PEDESTRIANS.includes(way.tags.highway) && !isElevatorArea && !["no", "private"].includes(way.tags.access) || way.tags.footway === "sidewalk" || way.tags.public_transport === "platform" || way.tags.railway === "platform";
|
|
581
598
|
};
|
|
582
|
-
const _OsmGraph = class extends geo.GeoGraph {
|
|
599
|
+
const _OsmGraph = class _OsmGraph extends geo.GeoGraph {
|
|
583
600
|
getVertexByCoords(coords) {
|
|
584
601
|
return geo.GeoGraph.getVertexByCoords(this.vertices, coords);
|
|
585
602
|
}
|
|
@@ -590,53 +607,98 @@ const _OsmGraph = class extends geo.GeoGraph {
|
|
|
590
607
|
return super.getEdgeByName(name);
|
|
591
608
|
}
|
|
592
609
|
static fromOsmModel(osmModel, waySelectionFilter = DEFAULT_WAY_SELECTOR) {
|
|
593
|
-
const
|
|
610
|
+
const vertices = [];
|
|
594
611
|
const edges = [];
|
|
595
|
-
const
|
|
596
|
-
const
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
612
|
+
const elevatorVertices = [];
|
|
613
|
+
const getOrCreateVertex = (osmNode, forceLevel = null) => {
|
|
614
|
+
let vertex = vertices.find(
|
|
615
|
+
(vertex2) => vertex2.data.id === osmNode.id && (forceLevel !== null ? geo.Level.intersect(vertex2.coords.level, forceLevel) : true)
|
|
616
|
+
) || null;
|
|
617
|
+
if (vertex) {
|
|
618
|
+
return vertex;
|
|
619
|
+
}
|
|
620
|
+
const otherVertex = vertices.find((v) => v.data.id === osmNode.id);
|
|
621
|
+
if (otherVertex) {
|
|
622
|
+
if (otherVertex && otherVertex.coords.level !== null) {
|
|
623
|
+
throw new Error(`An error occured because repeat_on as not been set on node ${osmNode.coords} for level ${forceLevel}`);
|
|
624
|
+
}
|
|
625
|
+
otherVertex.coords.level = forceLevel;
|
|
626
|
+
}
|
|
627
|
+
let levelsToGenerate = [null];
|
|
628
|
+
if ("level" in osmNode.tags) {
|
|
629
|
+
levelsToGenerate = [osmNode.coords.level];
|
|
630
|
+
if ("repeat_on" in osmNode.tags) {
|
|
631
|
+
levelsToGenerate.push(...osmNode.tags.repeat_on.split(";").map(geo.Level.fromString));
|
|
605
632
|
}
|
|
633
|
+
} else if (forceLevel !== null) {
|
|
634
|
+
levelsToGenerate = [forceLevel];
|
|
606
635
|
}
|
|
607
|
-
|
|
636
|
+
levelsToGenerate.forEach((level) => {
|
|
637
|
+
const newVertex = new geo.GeoGraphVertex(osmNode.coords.clone(), { data: osmNode, name: osmNode.tags.name });
|
|
638
|
+
newVertex.coords.level = level;
|
|
639
|
+
vertices.push(newVertex);
|
|
640
|
+
if (geo.Level.intersect(newVertex.coords.level, forceLevel) || forceLevel === null && "level" in osmNode.tags) {
|
|
641
|
+
vertex = newVertex;
|
|
642
|
+
}
|
|
643
|
+
});
|
|
644
|
+
if (!vertex) {
|
|
645
|
+
throw new Error(`Unable to parse vertex {id: ${osmNode.id}, coords: ${osmNode.coords.lat}, ${osmNode.coords.lng}}. Please check "level" tag.`);
|
|
646
|
+
}
|
|
647
|
+
if (osmNode.tags.highway === "elevator" && vertex) {
|
|
648
|
+
elevatorVertices.push(vertex);
|
|
649
|
+
}
|
|
650
|
+
return vertex;
|
|
608
651
|
};
|
|
609
652
|
osmModel.ways.forEach((way) => {
|
|
610
653
|
if (!waySelectionFilter(way)) {
|
|
611
654
|
return;
|
|
612
655
|
}
|
|
613
|
-
let
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
{ data: way, name: way.tags.name, level: way.level }
|
|
620
|
-
);
|
|
621
|
-
_OsmGraph.manageOneWay(edge, way);
|
|
622
|
-
edges.push(edge);
|
|
623
|
-
firstNode = secondNode;
|
|
656
|
+
let levelsToGenerate = [null];
|
|
657
|
+
if ("level" in way.tags) {
|
|
658
|
+
levelsToGenerate = [way.level];
|
|
659
|
+
if ("repeat_on" in way.tags) {
|
|
660
|
+
levelsToGenerate.push(...way.tags.repeat_on.split(";").map(geo.Level.fromString));
|
|
661
|
+
}
|
|
624
662
|
}
|
|
663
|
+
levelsToGenerate.forEach((level) => {
|
|
664
|
+
let firstVertex = getOrCreateVertex(way.nodes[0], level);
|
|
665
|
+
for (let i = 1; i < way.nodes.length; i++) {
|
|
666
|
+
const secondVertex = getOrCreateVertex(way.nodes[i], level);
|
|
667
|
+
const edge = new geo.GeoGraphEdge(
|
|
668
|
+
firstVertex,
|
|
669
|
+
secondVertex,
|
|
670
|
+
{ data: way, name: way.tags.name, level }
|
|
671
|
+
);
|
|
672
|
+
_OsmGraph.manageOneWay(edge, way);
|
|
673
|
+
edges.push(edge);
|
|
674
|
+
firstVertex = secondVertex;
|
|
675
|
+
}
|
|
676
|
+
});
|
|
625
677
|
});
|
|
626
678
|
osmModel.ways.filter((way) => way.isElevator).forEach((way) => {
|
|
627
|
-
way.nodes.
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
679
|
+
const entryVertices = way.nodes.map((node) => vertices.filter((v) => v.data.id === node.id)).flat();
|
|
680
|
+
const elevatorLevel = entryVertices.reduce((acc, v) => geo.Level.union(acc, v.coords.level), null);
|
|
681
|
+
const elevatorCenter = way.nodes.reduce((acc, node) => [acc[0] + node.coords.lat, acc[1] + node.coords.lng], [0, 0]).map((val) => val / way.nodes.length);
|
|
682
|
+
const elevatorCenterCoords = new geo.Coordinates(elevatorCenter[0], elevatorCenter[1], null, elevatorLevel);
|
|
683
|
+
const elevatorCenterVertex = new geo.GeoGraphVertex(elevatorCenterCoords, { data: way, name: way.tags.name });
|
|
684
|
+
vertices.push(elevatorCenterVertex);
|
|
685
|
+
entryVertices.forEach((entryVertex) => {
|
|
686
|
+
const newEdge = new geo.GeoGraphEdge(
|
|
687
|
+
elevatorCenterVertex,
|
|
688
|
+
entryVertex,
|
|
689
|
+
{
|
|
690
|
+
level: geo.Level.intersection(elevatorCenterVertex.coords.level, entryVertex.coords.level),
|
|
691
|
+
data: way
|
|
692
|
+
}
|
|
693
|
+
);
|
|
694
|
+
edges.push(newEdge);
|
|
634
695
|
});
|
|
696
|
+
elevatorVertices.push(elevatorCenterVertex);
|
|
635
697
|
});
|
|
636
|
-
|
|
637
|
-
_OsmGraph.createNodesAndEdgesFromElevator(
|
|
698
|
+
elevatorVertices.forEach((node) => {
|
|
699
|
+
_OsmGraph.createNodesAndEdgesFromElevator(vertices, edges, node);
|
|
638
700
|
});
|
|
639
|
-
return new _OsmGraph(
|
|
701
|
+
return new _OsmGraph(vertices, edges, true);
|
|
640
702
|
}
|
|
641
703
|
static manageOneWay(edge, way) {
|
|
642
704
|
const { highway, oneway, conveying } = way.tags;
|
|
@@ -647,59 +709,58 @@ const _OsmGraph = class extends geo.GeoGraph {
|
|
|
647
709
|
edge.vertex2 = tmpNode;
|
|
648
710
|
}
|
|
649
711
|
}
|
|
650
|
-
static createNodesAndEdgesFromElevator(
|
|
651
|
-
const
|
|
712
|
+
static createNodesAndEdgesFromElevator(vertices, edges, elevatorVertex) {
|
|
713
|
+
const createdVertices = [];
|
|
652
714
|
const getOrCreateLevelVertex = (level) => {
|
|
653
|
-
let levelVertex =
|
|
715
|
+
let levelVertex = createdVertices.find(({ coords }) => geo.Level.equals(level, coords.level));
|
|
654
716
|
if (!levelVertex) {
|
|
655
|
-
levelVertex = new geo.GeoGraphVertex(
|
|
656
|
-
data:
|
|
657
|
-
name: `${
|
|
717
|
+
levelVertex = new geo.GeoGraphVertex(elevatorVertex.coords.clone(), {
|
|
718
|
+
data: elevatorVertex.data,
|
|
719
|
+
name: `${elevatorVertex.name} (elevator lvl: ${level})`
|
|
658
720
|
});
|
|
659
721
|
levelVertex.coords.level = level;
|
|
660
|
-
|
|
661
|
-
|
|
722
|
+
createdVertices.push(levelVertex);
|
|
723
|
+
vertices.push(levelVertex);
|
|
662
724
|
}
|
|
663
725
|
return levelVertex;
|
|
664
726
|
};
|
|
665
|
-
|
|
727
|
+
elevatorVertex.edges.forEach((edge) => {
|
|
666
728
|
if (geo.Level.isRange(edge.level)) {
|
|
667
|
-
throw new Error(
|
|
729
|
+
throw new Error(`Cannot handle this elevator edge due to ambiguity (vertex: ${edge.vertex1.coords})`);
|
|
668
730
|
}
|
|
669
731
|
const levelVertex = getOrCreateLevelVertex(edge.level);
|
|
670
|
-
if (edge.vertex1 ===
|
|
732
|
+
if (edge.vertex1 === elevatorVertex) {
|
|
671
733
|
edge.vertex1 = levelVertex;
|
|
672
734
|
} else {
|
|
673
735
|
edge.vertex2 = levelVertex;
|
|
674
736
|
}
|
|
675
|
-
levelVertex.edges.push(edge);
|
|
676
737
|
});
|
|
677
|
-
for (let i = 0; i <
|
|
678
|
-
for (let j = i + 1; j <
|
|
679
|
-
const
|
|
680
|
-
const
|
|
681
|
-
if (
|
|
738
|
+
for (let i = 0; i < createdVertices.length; i++) {
|
|
739
|
+
for (let j = i + 1; j < createdVertices.length; j++) {
|
|
740
|
+
const createdVertex1 = createdVertices[i];
|
|
741
|
+
const createdVertex2 = createdVertices[j];
|
|
742
|
+
if (createdVertex1.coords.level === null || createdVertex2.coords.level === null) {
|
|
682
743
|
continue;
|
|
683
744
|
}
|
|
684
|
-
const minLevel = Math.min(
|
|
685
|
-
const maxLevel = Math.max(
|
|
745
|
+
const minLevel = Math.min(createdVertex1.coords.level, createdVertex2.coords.level);
|
|
746
|
+
const maxLevel = Math.max(createdVertex1.coords.level, createdVertex2.coords.level);
|
|
686
747
|
const newEdge = new geo.GeoGraphEdge(
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
{ data:
|
|
748
|
+
createdVertex1,
|
|
749
|
+
createdVertex2,
|
|
750
|
+
{ data: elevatorVertex.data, name: elevatorVertex.name, level: [minLevel, maxLevel] }
|
|
690
751
|
);
|
|
691
752
|
edges.push(newEdge);
|
|
692
753
|
}
|
|
693
754
|
}
|
|
694
|
-
const elevatorNodeIndex =
|
|
755
|
+
const elevatorNodeIndex = vertices.indexOf(elevatorVertex);
|
|
695
756
|
if (elevatorNodeIndex > -1) {
|
|
696
|
-
|
|
757
|
+
vertices.splice(elevatorNodeIndex, 1);
|
|
697
758
|
}
|
|
698
759
|
}
|
|
699
760
|
};
|
|
761
|
+
__publicField(_OsmGraph, "HIGHWAYS_PEDESTRIANS", HIGHWAYS_PEDESTRIANS);
|
|
762
|
+
__publicField(_OsmGraph, "DEFAULT_WAY_SELECTOR", DEFAULT_WAY_SELECTOR);
|
|
700
763
|
let OsmGraph = _OsmGraph;
|
|
701
|
-
__publicField(OsmGraph, "HIGHWAYS_PEDESTRIANS", HIGHWAYS_PEDESTRIANS);
|
|
702
|
-
__publicField(OsmGraph, "DEFAULT_WAY_SELECTOR", DEFAULT_WAY_SELECTOR);
|
|
703
764
|
const buildStepsRules = (graphItinerary) => (currentCoords, nextCoords, previousStep) => {
|
|
704
765
|
var _a, _b, _c, _d, _e;
|
|
705
766
|
const edges = graphItinerary.edges;
|
|
@@ -764,7 +825,7 @@ const buildStepsRules = (graphItinerary) => (currentCoords, nextCoords, previous
|
|
|
764
825
|
...forceEndOfLevelChange && { forceEndOfLevelChange }
|
|
765
826
|
};
|
|
766
827
|
};
|
|
767
|
-
const _WemapOsmRouter = class extends geo.GeoGraphRouter {
|
|
828
|
+
const _WemapOsmRouter = class _WemapOsmRouter extends geo.GeoGraphRouter {
|
|
768
829
|
constructor(graph) {
|
|
769
830
|
super(graph);
|
|
770
831
|
}
|
|
@@ -796,12 +857,12 @@ const _WemapOsmRouter = class extends geo.GeoGraphRouter {
|
|
|
796
857
|
}
|
|
797
858
|
getShortestTrip(waypoints, options = _WemapOsmRouter.DEFAULT_TRIP_OPTIONS) {
|
|
798
859
|
const points = waypoints.map((waypoint) => {
|
|
799
|
-
const point = new
|
|
860
|
+
const point = new salesman.Point(0, 0);
|
|
800
861
|
point.coords = waypoint;
|
|
801
862
|
return point;
|
|
802
863
|
});
|
|
803
864
|
const cache = [];
|
|
804
|
-
const solution =
|
|
865
|
+
const solution = salesman.solve(points, options.tspTempCoeff, void 0, (p, q) => {
|
|
805
866
|
const osmItinerary = this.getShortestPath(
|
|
806
867
|
p.coords,
|
|
807
868
|
q.coords,
|
|
@@ -875,10 +936,10 @@ const _WemapOsmRouter = class extends geo.GeoGraphRouter {
|
|
|
875
936
|
);
|
|
876
937
|
}
|
|
877
938
|
};
|
|
939
|
+
__publicField(_WemapOsmRouter, "DEFAULT_OPTIONS", _WemapOsmRouter.buildOptions());
|
|
940
|
+
__publicField(_WemapOsmRouter, "WITHOUT_STAIRS_OPTIONS", _WemapOsmRouter.buildOptions({ useStairs: false }));
|
|
941
|
+
__publicField(_WemapOsmRouter, "DEFAULT_TRIP_OPTIONS", _WemapOsmRouter.buildTripOptions());
|
|
878
942
|
let WemapOsmRouter = _WemapOsmRouter;
|
|
879
|
-
__publicField(WemapOsmRouter, "DEFAULT_OPTIONS", _WemapOsmRouter.buildOptions());
|
|
880
|
-
__publicField(WemapOsmRouter, "WITHOUT_STAIRS_OPTIONS", _WemapOsmRouter.buildOptions({ useStairs: false }));
|
|
881
|
-
__publicField(WemapOsmRouter, "DEFAULT_TRIP_OPTIONS", _WemapOsmRouter.buildTripOptions());
|
|
882
943
|
class RemoteRouter {
|
|
883
944
|
}
|
|
884
945
|
class RemoteRouterServerUnreachable extends Error {
|
|
@@ -978,9 +1039,17 @@ function parseWKTGeometry(wktGeometry) {
|
|
|
978
1039
|
});
|
|
979
1040
|
}
|
|
980
1041
|
class CitywayRemoteRouter extends RemoteRouter {
|
|
1042
|
+
/**
|
|
1043
|
+
* @override
|
|
1044
|
+
*/
|
|
981
1045
|
get rname() {
|
|
982
1046
|
return "cityway";
|
|
983
1047
|
}
|
|
1048
|
+
/**
|
|
1049
|
+
* @override
|
|
1050
|
+
* @throws {RoutingModeCorrespondanceNotFound}
|
|
1051
|
+
* @throws {RemoteRouterServerUnreachable}
|
|
1052
|
+
*/
|
|
984
1053
|
async getItineraries(endpointUrl, mode, waypoints) {
|
|
985
1054
|
const url = this.getURL(endpointUrl, mode, waypoints);
|
|
986
1055
|
const res = await fetch(url).catch(() => {
|
|
@@ -991,13 +1060,16 @@ class CitywayRemoteRouter extends RemoteRouter {
|
|
|
991
1060
|
});
|
|
992
1061
|
return this.createRouterResponseFromJson(jsonResponse, waypoints[0], waypoints[1]);
|
|
993
1062
|
}
|
|
1063
|
+
/**
|
|
1064
|
+
* @throws {RoutingModeCorrespondanceNotFound}
|
|
1065
|
+
*/
|
|
994
1066
|
getURL(endpointUrl, mode, waypoints) {
|
|
995
1067
|
const citywayMode = inputModeCorrespondance$2.get(mode);
|
|
996
1068
|
if (!citywayMode) {
|
|
997
1069
|
throw new RoutingModeCorrespondanceNotFound(this.rname, mode);
|
|
998
1070
|
}
|
|
999
1071
|
if (waypoints.length > 2) {
|
|
1000
|
-
|
|
1072
|
+
Logger.warn(`${this.rname} router uses only the first 2 waypoints (asked ${waypoints.length})`);
|
|
1001
1073
|
}
|
|
1002
1074
|
const fromPlace = `DepartureLatitude=${waypoints[0].latitude}&DepartureLongitude=${waypoints[0].longitude}`;
|
|
1003
1075
|
const toPlace = `ArrivalLatitude=${waypoints[1].latitude}&ArrivalLongitude=${waypoints[1].longitude}`;
|
|
@@ -1007,6 +1079,11 @@ class CitywayRemoteRouter extends RemoteRouter {
|
|
|
1007
1079
|
search = (search ? `${search}&` : "?") + `${fromPlace}&${toPlace}&${queryMode}`;
|
|
1008
1080
|
return `${url.origin}${url.pathname}${search}`;
|
|
1009
1081
|
}
|
|
1082
|
+
/**
|
|
1083
|
+
* Generate multi itineraries from Cityway JSON
|
|
1084
|
+
* @returns {!RouterResponse}
|
|
1085
|
+
* @example https://preprod.api.lia2.cityway.fr/journeyplanner/api/opt/PlanTrips/json?DepartureLatitude=49.51509388236216&DepartureLongitude=0.09341749619366316&ArrivalLatitude=49.5067090188444&ArrivalLongitude=0.1694842115417831&DepartureType=COORDINATES&ArrivalType=COORDINATES
|
|
1086
|
+
*/
|
|
1010
1087
|
createRouterResponseFromJson(json, from, to) {
|
|
1011
1088
|
const routerResponse = new RouterResponse({ routerName: this.rname, from, to });
|
|
1012
1089
|
if (json.StatusCode !== 200 || !json.Data || !json.Data.length) {
|
|
@@ -1100,7 +1177,7 @@ class CitywayRemoteRouter extends RemoteRouter {
|
|
|
1100
1177
|
distance: jsonLeg.Distance
|
|
1101
1178
|
}];
|
|
1102
1179
|
} else {
|
|
1103
|
-
|
|
1180
|
+
Logger.warn(`[CitywayParser] Unknown leg mode: ${jsonLeg.TransportMode}`);
|
|
1104
1181
|
continue;
|
|
1105
1182
|
}
|
|
1106
1183
|
const leg = new Leg({
|
|
@@ -1128,6 +1205,10 @@ class CitywayRemoteRouter extends RemoteRouter {
|
|
|
1128
1205
|
}
|
|
1129
1206
|
return routerResponse;
|
|
1130
1207
|
}
|
|
1208
|
+
/**
|
|
1209
|
+
* @param {string} iso8601Duration
|
|
1210
|
+
* @see https://stackoverflow.com/a/29153059/2239938
|
|
1211
|
+
*/
|
|
1131
1212
|
parseDuration(iso8601Duration) {
|
|
1132
1213
|
const iso8601DurationRegex = /(-)?P(?:([.,\d]+)Y)?(?:([.,\d]+)M)?(?:([.,\d]+)W)?(?:([.,\d]+)D)?T(?:([.,\d]+)H)?(?:([.,\d]+)M)?(?:([.,\d]+)S)?/;
|
|
1133
1214
|
const matches = iso8601Duration.match(iso8601DurationRegex);
|
|
@@ -1143,9 +1224,16 @@ class CitywayRemoteRouter extends RemoteRouter {
|
|
|
1143
1224
|
}
|
|
1144
1225
|
const CitywayRemoteRouter$1 = new CitywayRemoteRouter();
|
|
1145
1226
|
class DeutscheBahnRemoteRouter extends RemoteRouter {
|
|
1227
|
+
/**
|
|
1228
|
+
* @override
|
|
1229
|
+
*/
|
|
1146
1230
|
get rname() {
|
|
1147
1231
|
return "deutsche-bahn";
|
|
1148
1232
|
}
|
|
1233
|
+
/**
|
|
1234
|
+
* @override
|
|
1235
|
+
* @throws {RemoteRouterServerUnreachable}
|
|
1236
|
+
*/
|
|
1149
1237
|
async getItineraries(endpointUrl, mode, waypoints) {
|
|
1150
1238
|
const url = this.getURL(endpointUrl, mode, waypoints);
|
|
1151
1239
|
const res = await fetch(url).catch(() => {
|
|
@@ -1201,7 +1289,7 @@ routingModeCorrespondance.set("Funicular", "FUNICULAR");
|
|
|
1201
1289
|
routingModeCorrespondance.set("LocalTrain", "TRAIN");
|
|
1202
1290
|
routingModeCorrespondance.set("LongDistanceTrain", "TRAIN");
|
|
1203
1291
|
routingModeCorrespondance.set("Metro", "METRO");
|
|
1204
|
-
routingModeCorrespondance.set("
|
|
1292
|
+
routingModeCorrespondance.set("Métro", "METRO");
|
|
1205
1293
|
routingModeCorrespondance.set("RailShuttle", "TRAIN");
|
|
1206
1294
|
routingModeCorrespondance.set("RapidTransit", "BUS");
|
|
1207
1295
|
routingModeCorrespondance.set("Shuttle", "BUS");
|
|
@@ -1256,9 +1344,16 @@ function dateStringToTimestamp(stringDate, timeZone) {
|
|
|
1256
1344
|
).getTime();
|
|
1257
1345
|
}
|
|
1258
1346
|
class IdfmRemoteRouter extends RemoteRouter {
|
|
1347
|
+
/**
|
|
1348
|
+
* @override
|
|
1349
|
+
*/
|
|
1259
1350
|
get rname() {
|
|
1260
1351
|
return "idfm";
|
|
1261
1352
|
}
|
|
1353
|
+
/**
|
|
1354
|
+
* @throws {IdfmRemoteRouterTokenError}
|
|
1355
|
+
* @throws {RemoteRouterServerUnreachable}
|
|
1356
|
+
*/
|
|
1262
1357
|
async getItineraries(endpointUrl, mode, waypoints, options = {}) {
|
|
1263
1358
|
const url = this.getURL(endpointUrl, mode, waypoints, options);
|
|
1264
1359
|
const res = await fetch(url, {
|
|
@@ -1282,7 +1377,7 @@ class IdfmRemoteRouter extends RemoteRouter {
|
|
|
1282
1377
|
}
|
|
1283
1378
|
getURL(endpointUrl, mode, waypoints, options) {
|
|
1284
1379
|
if (waypoints.length > 2) {
|
|
1285
|
-
|
|
1380
|
+
Logger.warn(`${this.rname} router uses only the first 2 waypoints (asked ${waypoints.length})`);
|
|
1286
1381
|
}
|
|
1287
1382
|
const url = new URL(endpointUrl);
|
|
1288
1383
|
const coreParams = new URLSearchParams();
|
|
@@ -1347,6 +1442,11 @@ class IdfmRemoteRouter extends RemoteRouter {
|
|
|
1347
1442
|
to
|
|
1348
1443
|
};
|
|
1349
1444
|
}
|
|
1445
|
+
/**
|
|
1446
|
+
* Since the IDFM API does not provide coords for each step, we need to compute them
|
|
1447
|
+
* We trim the coordinates of the leg with the distance of each step and keep the last result as the coords of the step
|
|
1448
|
+
* @param {Leg} leg
|
|
1449
|
+
*/
|
|
1350
1450
|
findStepsCoord(legCoords, steps) {
|
|
1351
1451
|
const coords = legCoords;
|
|
1352
1452
|
const duplicatedCoords = [...coords];
|
|
@@ -1472,9 +1572,16 @@ inputModeCorrespondance$1.set("BIKE", "bike");
|
|
|
1472
1572
|
inputModeCorrespondance$1.set("BUS", "bus");
|
|
1473
1573
|
inputModeCorrespondance$1.set("MULTI", "walking");
|
|
1474
1574
|
class OsrmRemoteRouter extends RemoteRouter {
|
|
1575
|
+
/**
|
|
1576
|
+
* @override
|
|
1577
|
+
*/
|
|
1475
1578
|
get rname() {
|
|
1476
1579
|
return "osrm";
|
|
1477
1580
|
}
|
|
1581
|
+
/**
|
|
1582
|
+
* @throws {RemoteRouterServerUnreachable}
|
|
1583
|
+
* @throws {RoutingModeCorrespondanceNotFound}
|
|
1584
|
+
*/
|
|
1478
1585
|
async getItineraries(endpointUrl, mode, waypoints, options = {}) {
|
|
1479
1586
|
const url = this.getURL(endpointUrl, mode, waypoints, options);
|
|
1480
1587
|
const res = await fetch(url).catch(() => {
|
|
@@ -1488,6 +1595,9 @@ class OsrmRemoteRouter extends RemoteRouter {
|
|
|
1488
1595
|
const osrmMode = inputModeCorrespondance$1.get(mode);
|
|
1489
1596
|
return this.createRouterResponseFromJson(jsonResponse, from, to, osrmMode);
|
|
1490
1597
|
}
|
|
1598
|
+
/**
|
|
1599
|
+
* @throws {RoutingModeCorrespondanceNotFound}
|
|
1600
|
+
*/
|
|
1491
1601
|
getURL(endpointUrl, mode, waypoints, options = {}) {
|
|
1492
1602
|
let osrmMode = inputModeCorrespondance$1.get(mode);
|
|
1493
1603
|
if (!osrmMode) {
|
|
@@ -1510,6 +1620,10 @@ class OsrmRemoteRouter extends RemoteRouter {
|
|
|
1510
1620
|
}
|
|
1511
1621
|
return [lng, lat, level];
|
|
1512
1622
|
}
|
|
1623
|
+
/**
|
|
1624
|
+
* @param {object} json
|
|
1625
|
+
* @returns {Coordinates}
|
|
1626
|
+
*/
|
|
1513
1627
|
jsonToCoordinates(json) {
|
|
1514
1628
|
const coords = new geo.Coordinates(json[1], json[0]);
|
|
1515
1629
|
if (json.length > 2) {
|
|
@@ -1548,6 +1662,9 @@ class OsrmRemoteRouter extends RemoteRouter {
|
|
|
1548
1662
|
message
|
|
1549
1663
|
};
|
|
1550
1664
|
}
|
|
1665
|
+
/**
|
|
1666
|
+
* @deprecated
|
|
1667
|
+
*/
|
|
1551
1668
|
itineraryToOsrmJson(itinerary) {
|
|
1552
1669
|
const lastLegId = itinerary.legs.length - 1;
|
|
1553
1670
|
const itinerarySteps = itinerary.steps;
|
|
@@ -1671,6 +1788,10 @@ class OtpRemoteRouter extends RemoteRouter {
|
|
|
1671
1788
|
get rname() {
|
|
1672
1789
|
return "otp";
|
|
1673
1790
|
}
|
|
1791
|
+
/**
|
|
1792
|
+
* @throws {RemoteRouterServerUnreachable}
|
|
1793
|
+
* @throws {RoutingModeCorrespondanceNotFound}
|
|
1794
|
+
*/
|
|
1674
1795
|
async getItineraries(endpointUrl, mode, waypoints) {
|
|
1675
1796
|
const url = this.getURL(endpointUrl, mode, waypoints);
|
|
1676
1797
|
const res = await fetch(url).catch(() => {
|
|
@@ -1681,13 +1802,16 @@ class OtpRemoteRouter extends RemoteRouter {
|
|
|
1681
1802
|
});
|
|
1682
1803
|
return this.createRouterResponseFromJson(jsonResponse, waypoints[0], waypoints[1]);
|
|
1683
1804
|
}
|
|
1805
|
+
/**
|
|
1806
|
+
* @throws {RoutingModeCorrespondanceNotFound}
|
|
1807
|
+
*/
|
|
1684
1808
|
getURL(endpointUrl, mode, waypoints) {
|
|
1685
1809
|
const otpMode = inputModeCorrespondance.get(mode);
|
|
1686
1810
|
if (!otpMode) {
|
|
1687
1811
|
throw new RoutingModeCorrespondanceNotFound(this.rname, mode);
|
|
1688
1812
|
}
|
|
1689
1813
|
if (waypoints.length > 2) {
|
|
1690
|
-
|
|
1814
|
+
Logger.warn(`${this.rname} router uses only the first 2 waypoints (asked ${waypoints.length})`);
|
|
1691
1815
|
}
|
|
1692
1816
|
const fromPlace = `fromPlace=${waypoints[0].latitude},${waypoints[0].longitude}`;
|
|
1693
1817
|
const toPlace = `toPlace=${waypoints[1].latitude},${waypoints[1].longitude}`;
|
|
@@ -1697,6 +1821,9 @@ class OtpRemoteRouter extends RemoteRouter {
|
|
|
1697
1821
|
search = (search ? `${search}&` : "?") + `${fromPlace}&${toPlace}&${queryMode}`;
|
|
1698
1822
|
return `${url.origin}${url.pathname}${search}`;
|
|
1699
1823
|
}
|
|
1824
|
+
/**
|
|
1825
|
+
* Generate multi itineraries from OTP JSON
|
|
1826
|
+
*/
|
|
1700
1827
|
createRouterResponseFromJson(json, from, to) {
|
|
1701
1828
|
const routerResponse = new RouterResponse({ routerName: this.rname, from, to });
|
|
1702
1829
|
const { plan: jsonPlan } = json;
|
|
@@ -1708,7 +1835,7 @@ class OtpRemoteRouter extends RemoteRouter {
|
|
|
1708
1835
|
for (const jsonItinerary of jsonPlan.itineraries) {
|
|
1709
1836
|
const legs = [];
|
|
1710
1837
|
for (const jsonLeg of jsonItinerary.legs) {
|
|
1711
|
-
const legCoords =
|
|
1838
|
+
const legCoords = Polyline.decode(jsonLeg.legGeometry.points).map(([lat, lon]) => new geo.Coordinates(lat, lon));
|
|
1712
1839
|
let transportInfo;
|
|
1713
1840
|
let minStepsInfo;
|
|
1714
1841
|
if (isLegPT(jsonLeg)) {
|
|
@@ -1828,6 +1955,11 @@ class RemoteRouterManager {
|
|
|
1828
1955
|
getRouterByName(name) {
|
|
1829
1956
|
return remoteRouters.find((remoteRouter) => remoteRouter.rname === name);
|
|
1830
1957
|
}
|
|
1958
|
+
/**
|
|
1959
|
+
* @throws {RemoteRouterServerUnreachable}
|
|
1960
|
+
* @throws {RoutingModeCorrespondanceNotFound}
|
|
1961
|
+
* @throws {IdfmRemoteRouterTokenError}
|
|
1962
|
+
*/
|
|
1831
1963
|
async getItineraries(name, endpointUrl, mode, waypoints, options) {
|
|
1832
1964
|
const router = this.getRouterByName(name);
|
|
1833
1965
|
if (!router) {
|
|
@@ -1835,6 +1967,11 @@ class RemoteRouterManager {
|
|
|
1835
1967
|
}
|
|
1836
1968
|
return router.getItineraries(endpointUrl, mode, waypoints, options);
|
|
1837
1969
|
}
|
|
1970
|
+
/**
|
|
1971
|
+
* @throws {RemoteRouterServerUnreachable}
|
|
1972
|
+
* @throws {RoutingModeCorrespondanceNotFound}
|
|
1973
|
+
* @throws {IdfmRemoteRouterTokenError}
|
|
1974
|
+
*/
|
|
1838
1975
|
async getItinerariesWithFallback(remoteRouters2, mode, waypoints, options) {
|
|
1839
1976
|
let routerResponse;
|
|
1840
1977
|
for (const { name, endpointUrl } of remoteRouters2) {
|
|
@@ -1876,7 +2013,7 @@ class WemapMultiRouter {
|
|
|
1876
2013
|
if (ioMapsToTest.length !== targetMaps.length) {
|
|
1877
2014
|
ioMapsToTest.forEach((map) => {
|
|
1878
2015
|
if (map.name && !targetMaps.includes(map.name)) {
|
|
1879
|
-
|
|
2016
|
+
Logger.warn(`CustomNetworkMap "${map.name}" not found in WemapMultiRouter`);
|
|
1880
2017
|
}
|
|
1881
2018
|
});
|
|
1882
2019
|
}
|
|
@@ -1920,7 +2057,7 @@ class WemapMultiRouter {
|
|
|
1920
2057
|
async getItineraries(mode, waypoints, options = null) {
|
|
1921
2058
|
var _a;
|
|
1922
2059
|
if (waypoints.length > 2) {
|
|
1923
|
-
|
|
2060
|
+
Logger.warn(`WemapMultiRouter uses only the first 2 waypoints (asked ${waypoints.length})`);
|
|
1924
2061
|
}
|
|
1925
2062
|
const start = waypoints[0];
|
|
1926
2063
|
const end = waypoints[1];
|
|
@@ -2099,7 +2236,7 @@ class CustomNetworkMap {
|
|
|
2099
2236
|
this.bounds = bounds;
|
|
2100
2237
|
} else {
|
|
2101
2238
|
const polygon = [graph.vertices.map((vertex) => [vertex.coords.lng, vertex.coords.lat])];
|
|
2102
|
-
const convexHull =
|
|
2239
|
+
const convexHull = convexHullFn({ type: "polygon", coordinates: polygon });
|
|
2103
2240
|
if (!convexHull) {
|
|
2104
2241
|
throw new Error(`Cannot calculate convexHull of network "${name}"`);
|
|
2105
2242
|
}
|
|
@@ -2138,8 +2275,14 @@ class CustomNetworkMap {
|
|
|
2138
2275
|
return new CustomNetworkMap(graph, entryPoints, bounds, name);
|
|
2139
2276
|
}
|
|
2140
2277
|
isPointInside(coordinates) {
|
|
2141
|
-
return
|
|
2142
|
-
}
|
|
2278
|
+
return pointInPolygon([coordinates.lng, coordinates.lat], this.bounds);
|
|
2279
|
+
}
|
|
2280
|
+
/**
|
|
2281
|
+
* Get the list of entry points sorted by the lowest distance between:
|
|
2282
|
+
* start -> entry point -> end
|
|
2283
|
+
* (as the crow flies)
|
|
2284
|
+
*
|
|
2285
|
+
*/
|
|
2143
2286
|
getOrderedEntryPointsSortedByDistance(start, end) {
|
|
2144
2287
|
const entryPointsCopy = [...this.entryPoints];
|
|
2145
2288
|
const levelDiffFactor = 50;
|
|
@@ -2153,6 +2296,17 @@ class CustomNetworkMap {
|
|
|
2153
2296
|
return distance2D + levelDiff * levelDiffFactor;
|
|
2154
2297
|
});
|
|
2155
2298
|
}
|
|
2299
|
+
/**
|
|
2300
|
+
* Get the best itinerary from any entry point to an end coordinates.
|
|
2301
|
+
*
|
|
2302
|
+
* The algorithm works as following:
|
|
2303
|
+
* 1 - Entry points are sorted using distance (as the crow flies) between start - entry point - end
|
|
2304
|
+
* 2 - Try to calculate an itinerary from the first entry point to the end coordinates.
|
|
2305
|
+
* 3 - If an itinerary is found, it is returned. Otherwise it tries from the next entry point.
|
|
2306
|
+
*
|
|
2307
|
+
* /!\ start is only used to sort the entry points (step 1).
|
|
2308
|
+
*
|
|
2309
|
+
*/
|
|
2156
2310
|
getBestItineraryFromEntryPointsToEnd(start, end, options) {
|
|
2157
2311
|
const sortedEntryPoints = this.getOrderedEntryPointsSortedByDistance(start, end);
|
|
2158
2312
|
for (const entryPoint of sortedEntryPoints) {
|
|
@@ -2167,6 +2321,17 @@ class CustomNetworkMap {
|
|
|
2167
2321
|
`No route found from entry points to ${end.toString()} in map: ${this.name}`
|
|
2168
2322
|
);
|
|
2169
2323
|
}
|
|
2324
|
+
/**
|
|
2325
|
+
* Get the best itinerary from start coordinates to any entry point.
|
|
2326
|
+
*
|
|
2327
|
+
* The algorithm works as following:
|
|
2328
|
+
* 1 - Entry points are sorted using distance (as the crow flies) between start - entry point - end
|
|
2329
|
+
* 2 - Try to calculate an itinerary from the start coordinates to the first entry point.
|
|
2330
|
+
* 3 - If an itinerary is found, it is returned. Otherwise it tries to the next entry point.
|
|
2331
|
+
*
|
|
2332
|
+
* /!\ end is only used to sort the entry points (step 1).
|
|
2333
|
+
*
|
|
2334
|
+
*/
|
|
2170
2335
|
getBestItineraryFromStartToEntryPoints(start, end, options) {
|
|
2171
2336
|
const sortedEntryPoints = this.getOrderedEntryPointsSortedByDistance(start, end);
|
|
2172
2337
|
for (const entryPoint of sortedEntryPoints) {
|