@mapslibvn/core 0.4.0 → 0.5.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/README.md +72 -9
- package/THIRD_PARTY_NOTICES.md +33 -2
- package/dist/index.d.ts +404 -1
- package/dist/index.js +847 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -95,6 +95,7 @@ function poiSourceClause(arrayExpr) {
|
|
|
95
95
|
}
|
|
96
96
|
|
|
97
97
|
// src/client.ts
|
|
98
|
+
var latLng = ([lat, lng]) => `${lat},${lng}`;
|
|
98
99
|
function createClient(options) {
|
|
99
100
|
const baseUrl = options.baseUrl.replace(/\/+$/, "");
|
|
100
101
|
const poiSources = normalizePoiSources(options.poiSources ?? DEFAULT_POI_SOURCES);
|
|
@@ -177,6 +178,15 @@ function createClient(options) {
|
|
|
177
178
|
limit: opts.limit
|
|
178
179
|
}),
|
|
179
180
|
reverse: (lat, lng) => get("/v1/reverse", { lat, lng, sources }),
|
|
181
|
+
/** Chỉ đường (spec dẫn đường A). Response dùng [lng, lat]; tham số vào dùng [lat, lng]. */
|
|
182
|
+
directions: (opts) => get("/v1/directions", {
|
|
183
|
+
from: latLng(opts.from),
|
|
184
|
+
to: latLng(opts.to),
|
|
185
|
+
via: opts.via && opts.via.length > 0 ? opts.via.map(latLng).join(";") : void 0,
|
|
186
|
+
mode: opts.mode,
|
|
187
|
+
lang: opts.lang,
|
|
188
|
+
alternatives: opts.alternatives === void 0 ? void 0 : opts.alternatives ? 1 : 0
|
|
189
|
+
}),
|
|
180
190
|
/** Gửi đóng góp/sửa POI (spec 6.1). Khoá phải có scope edits:write. */
|
|
181
191
|
suggestEdit: (edit) => post("/v1/edits", edit)
|
|
182
192
|
};
|
|
@@ -594,6 +604,10 @@ function adminAliasKeys(input) {
|
|
|
594
604
|
|
|
595
605
|
// src/style-transform.ts
|
|
596
606
|
var POI_LAYER_ID = "poi";
|
|
607
|
+
var FIRST_SYMBOL_LAYER_ID = {
|
|
608
|
+
light: "road_one_way_arrow",
|
|
609
|
+
dark: "water_name"
|
|
610
|
+
};
|
|
597
611
|
var SOVEREIGNTY_LABEL_ID = "sovereignty-label";
|
|
598
612
|
function isPoiStyleLayer(layer) {
|
|
599
613
|
return layer.id === POI_LAYER_ID || layer.source === "poi";
|
|
@@ -792,39 +806,872 @@ function looksLikeTelex(normalized) {
|
|
|
792
806
|
function foldTelex(normalized) {
|
|
793
807
|
return normalized.replace(/aa/g, "a").replace(/ee/g, "e").replace(/oo/g, "o").replace(/dd/g, "d").replace(/aw/g, "a").replace(/ow/g, "o").replace(/uw/g, "u").replace(/([aeiouy](?:ch|ng|nh|[cmnpt])?)[sfrxj]\b/g, "$1").replace(/([a-z])[1-9]\b/g, "$1").replace(/\s+/g, " ").trim();
|
|
794
808
|
}
|
|
809
|
+
|
|
810
|
+
// src/polyline.ts
|
|
811
|
+
function decodePolyline6(encoded) {
|
|
812
|
+
const coords = [];
|
|
813
|
+
let index = 0;
|
|
814
|
+
let lat = 0;
|
|
815
|
+
let lng = 0;
|
|
816
|
+
const next = () => {
|
|
817
|
+
let result = 0;
|
|
818
|
+
let shift = 0;
|
|
819
|
+
let byte;
|
|
820
|
+
do {
|
|
821
|
+
byte = encoded.charCodeAt(index++) - 63;
|
|
822
|
+
result |= (byte & 31) << shift;
|
|
823
|
+
shift += 5;
|
|
824
|
+
} while (byte >= 32);
|
|
825
|
+
return result & 1 ? ~(result >> 1) : result >> 1;
|
|
826
|
+
};
|
|
827
|
+
while (index < encoded.length) {
|
|
828
|
+
lat += next();
|
|
829
|
+
lng += next();
|
|
830
|
+
coords.push([lng / 1e6, lat / 1e6]);
|
|
831
|
+
}
|
|
832
|
+
return coords;
|
|
833
|
+
}
|
|
834
|
+
function encodePolyline6(coords) {
|
|
835
|
+
let out = "";
|
|
836
|
+
let prevLat = 0;
|
|
837
|
+
let prevLng = 0;
|
|
838
|
+
for (const [lng, lat] of coords) {
|
|
839
|
+
const iLat = Math.round(lat * 1e6);
|
|
840
|
+
const iLng = Math.round(lng * 1e6);
|
|
841
|
+
out += encodeValue(iLat - prevLat) + encodeValue(iLng - prevLng);
|
|
842
|
+
prevLat = iLat;
|
|
843
|
+
prevLng = iLng;
|
|
844
|
+
}
|
|
845
|
+
return out;
|
|
846
|
+
}
|
|
847
|
+
function encodeValue(value) {
|
|
848
|
+
let v = value < 0 ? ~(value << 1) : value << 1;
|
|
849
|
+
let out = "";
|
|
850
|
+
while (v >= 32) {
|
|
851
|
+
out += String.fromCharCode((32 | v & 31) + 63);
|
|
852
|
+
v >>= 5;
|
|
853
|
+
}
|
|
854
|
+
return out + String.fromCharCode(v + 63);
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
// src/maneuver.ts
|
|
858
|
+
var MANEUVER_KINDS = [
|
|
859
|
+
"depart",
|
|
860
|
+
"arrive",
|
|
861
|
+
"continue",
|
|
862
|
+
"slight_right",
|
|
863
|
+
"slight_left",
|
|
864
|
+
"turn_right",
|
|
865
|
+
"turn_left",
|
|
866
|
+
"sharp_right",
|
|
867
|
+
"sharp_left",
|
|
868
|
+
"uturn_right",
|
|
869
|
+
"uturn_left",
|
|
870
|
+
"ramp_straight",
|
|
871
|
+
"ramp_right",
|
|
872
|
+
"ramp_left",
|
|
873
|
+
"exit_right",
|
|
874
|
+
"exit_left",
|
|
875
|
+
"keep_right",
|
|
876
|
+
"keep_left",
|
|
877
|
+
"merge",
|
|
878
|
+
"merge_right",
|
|
879
|
+
"merge_left",
|
|
880
|
+
"roundabout_enter",
|
|
881
|
+
"roundabout_exit",
|
|
882
|
+
"ferry_enter",
|
|
883
|
+
"ferry_exit",
|
|
884
|
+
"elevator",
|
|
885
|
+
"steps",
|
|
886
|
+
"escalator",
|
|
887
|
+
"building_enter",
|
|
888
|
+
"building_exit",
|
|
889
|
+
"other"
|
|
890
|
+
];
|
|
891
|
+
var VALHALLA_MANEUVER_KIND = {
|
|
892
|
+
1: "depart",
|
|
893
|
+
2: "depart",
|
|
894
|
+
3: "depart",
|
|
895
|
+
4: "arrive",
|
|
896
|
+
5: "arrive",
|
|
897
|
+
6: "arrive",
|
|
898
|
+
7: "continue",
|
|
899
|
+
8: "continue",
|
|
900
|
+
9: "slight_right",
|
|
901
|
+
10: "turn_right",
|
|
902
|
+
11: "sharp_right",
|
|
903
|
+
12: "uturn_right",
|
|
904
|
+
13: "uturn_left",
|
|
905
|
+
14: "sharp_left",
|
|
906
|
+
15: "turn_left",
|
|
907
|
+
16: "slight_left",
|
|
908
|
+
17: "ramp_straight",
|
|
909
|
+
18: "ramp_right",
|
|
910
|
+
19: "ramp_left",
|
|
911
|
+
20: "exit_right",
|
|
912
|
+
21: "exit_left",
|
|
913
|
+
22: "continue",
|
|
914
|
+
23: "keep_right",
|
|
915
|
+
24: "keep_left",
|
|
916
|
+
25: "merge",
|
|
917
|
+
26: "roundabout_enter",
|
|
918
|
+
27: "roundabout_exit",
|
|
919
|
+
28: "ferry_enter",
|
|
920
|
+
29: "ferry_exit",
|
|
921
|
+
37: "merge_right",
|
|
922
|
+
38: "merge_left",
|
|
923
|
+
39: "elevator",
|
|
924
|
+
40: "steps",
|
|
925
|
+
41: "escalator",
|
|
926
|
+
42: "building_enter",
|
|
927
|
+
43: "building_exit"
|
|
928
|
+
};
|
|
929
|
+
function maneuverKindFromValhalla(type) {
|
|
930
|
+
return VALHALLA_MANEUVER_KIND[type] ?? "other";
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
// src/navigation/types.ts
|
|
934
|
+
var NAVIGATION_THRESHOLDS = {
|
|
935
|
+
walk: {
|
|
936
|
+
offRoute_m: 25,
|
|
937
|
+
offRouteFixes: 3,
|
|
938
|
+
offRouteSeconds: 5,
|
|
939
|
+
maxAccuracy_m: 60,
|
|
940
|
+
approach_m: 40,
|
|
941
|
+
pre_m: 15,
|
|
942
|
+
arrive_m: 15,
|
|
943
|
+
rerouteCooldown_s: 15,
|
|
944
|
+
rerouteMaxFailures: 3
|
|
945
|
+
},
|
|
946
|
+
motorbike: {
|
|
947
|
+
offRoute_m: 40,
|
|
948
|
+
offRouteFixes: 3,
|
|
949
|
+
offRouteSeconds: 5,
|
|
950
|
+
maxAccuracy_m: 100,
|
|
951
|
+
approach_m: 200,
|
|
952
|
+
pre_m: 50,
|
|
953
|
+
arrive_m: 25,
|
|
954
|
+
rerouteCooldown_s: 15,
|
|
955
|
+
rerouteMaxFailures: 3
|
|
956
|
+
},
|
|
957
|
+
car: {
|
|
958
|
+
offRoute_m: 50,
|
|
959
|
+
offRouteFixes: 3,
|
|
960
|
+
offRouteSeconds: 5,
|
|
961
|
+
maxAccuracy_m: 100,
|
|
962
|
+
approach_m: 400,
|
|
963
|
+
pre_m: 80,
|
|
964
|
+
arrive_m: 30,
|
|
965
|
+
rerouteCooldown_s: 15,
|
|
966
|
+
rerouteMaxFailures: 3
|
|
967
|
+
}
|
|
968
|
+
};
|
|
969
|
+
|
|
970
|
+
// src/navigation/geometry.ts
|
|
971
|
+
var EARTH_RADIUS_M = 63710088e-1;
|
|
972
|
+
var M_PER_DEG_LAT = Math.PI / 180 * EARTH_RADIUS_M;
|
|
973
|
+
var toRad = (deg) => deg * Math.PI / 180;
|
|
974
|
+
var toDeg = (rad) => rad * 180 / Math.PI;
|
|
975
|
+
function haversineM(a, b) {
|
|
976
|
+
const dLat = toRad(b[1] - a[1]);
|
|
977
|
+
const dLng = toRad(b[0] - a[0]);
|
|
978
|
+
const h = Math.sin(dLat / 2) ** 2 + Math.cos(toRad(a[1])) * Math.cos(toRad(b[1])) * Math.sin(dLng / 2) ** 2;
|
|
979
|
+
return 2 * EARTH_RADIUS_M * Math.asin(Math.min(1, Math.sqrt(h)));
|
|
980
|
+
}
|
|
981
|
+
function bearingDeg(a, b) {
|
|
982
|
+
const phi1 = toRad(a[1]);
|
|
983
|
+
const phi2 = toRad(b[1]);
|
|
984
|
+
const dLambda = toRad(b[0] - a[0]);
|
|
985
|
+
const y = Math.sin(dLambda) * Math.cos(phi2);
|
|
986
|
+
const x = Math.cos(phi1) * Math.sin(phi2) - Math.sin(phi1) * Math.cos(phi2) * Math.cos(dLambda);
|
|
987
|
+
return (toDeg(Math.atan2(y, x)) + 360) % 360;
|
|
988
|
+
}
|
|
989
|
+
function angleDiffDeg(a, b) {
|
|
990
|
+
const d = Math.abs(((a - b) % 360 + 360) % 360);
|
|
991
|
+
return d > 180 ? 360 - d : d;
|
|
992
|
+
}
|
|
993
|
+
function projectOnSegment(p, a, b) {
|
|
994
|
+
const mPerDegLng = M_PER_DEG_LAT * Math.cos(toRad(a[1]));
|
|
995
|
+
const bx = (b[0] - a[0]) * mPerDegLng;
|
|
996
|
+
const by = (b[1] - a[1]) * M_PER_DEG_LAT;
|
|
997
|
+
const px = (p[0] - a[0]) * mPerDegLng;
|
|
998
|
+
const py = (p[1] - a[1]) * M_PER_DEG_LAT;
|
|
999
|
+
const len2 = bx * bx + by * by;
|
|
1000
|
+
const t = len2 === 0 ? 0 : Math.max(0, Math.min(1, (px * bx + py * by) / len2));
|
|
1001
|
+
const qx = bx * t;
|
|
1002
|
+
const qy = by * t;
|
|
1003
|
+
const point = t === 1 ? [b[0], b[1]] : [a[0] + qx / mPerDegLng, a[1] + qy / M_PER_DEG_LAT];
|
|
1004
|
+
return { t, point, distance_m: Math.hypot(px - qx, py - qy) };
|
|
1005
|
+
}
|
|
1006
|
+
function cumulativeDistances(coords) {
|
|
1007
|
+
const cum = [];
|
|
1008
|
+
let total = 0;
|
|
1009
|
+
for (let i = 0; i < coords.length; i++) {
|
|
1010
|
+
const prev = coords[i - 1];
|
|
1011
|
+
const cur = coords[i];
|
|
1012
|
+
if (i > 0 && prev && cur) total += haversineM(prev, cur);
|
|
1013
|
+
cum.push(total);
|
|
1014
|
+
}
|
|
1015
|
+
return cum;
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
// src/navigation/progress.ts
|
|
1019
|
+
function buildRouteIndex(route) {
|
|
1020
|
+
const coords = decodePolyline6(route.geometry);
|
|
1021
|
+
const cum = cumulativeDistances(coords);
|
|
1022
|
+
const total_m = cum[cum.length - 1] ?? 0;
|
|
1023
|
+
const at = (vertex) => cum[Math.min(Math.max(vertex, 0), cum.length - 1)] ?? 0;
|
|
1024
|
+
const steps = [];
|
|
1025
|
+
route.legs.forEach((leg, legIndex) => {
|
|
1026
|
+
leg.steps.forEach((step, indexInLeg) => {
|
|
1027
|
+
const begin_m = at(step.shape_begin);
|
|
1028
|
+
steps.push({ step, legIndex, indexInLeg, begin_m, end_m: begin_m });
|
|
1029
|
+
});
|
|
1030
|
+
});
|
|
1031
|
+
for (let i = 0; i < steps.length; i++) {
|
|
1032
|
+
const current = steps[i];
|
|
1033
|
+
if (current) current.end_m = steps[i + 1]?.begin_m ?? total_m;
|
|
1034
|
+
}
|
|
1035
|
+
return { coords, cum, total_m, steps, legBegin_m: route.legs.map((leg) => at(leg.shape_offset)) };
|
|
1036
|
+
}
|
|
1037
|
+
function stepAt(index, along_m) {
|
|
1038
|
+
const { steps, total_m } = index;
|
|
1039
|
+
if (steps.length === 0) return 0;
|
|
1040
|
+
if (along_m >= total_m) return steps.length - 1;
|
|
1041
|
+
let found = 0;
|
|
1042
|
+
for (let i = 0; i < steps.length; i++) {
|
|
1043
|
+
const s = steps[i];
|
|
1044
|
+
if (!s) continue;
|
|
1045
|
+
if (s.begin_m > along_m) break;
|
|
1046
|
+
if (along_m < s.end_m) {
|
|
1047
|
+
found = i;
|
|
1048
|
+
break;
|
|
1049
|
+
}
|
|
1050
|
+
found = i;
|
|
1051
|
+
}
|
|
1052
|
+
return found;
|
|
1053
|
+
}
|
|
1054
|
+
function progressAt(index, along_m) {
|
|
1055
|
+
const clamped = Math.max(0, Math.min(along_m, index.total_m));
|
|
1056
|
+
const stepIndex = stepAt(index, clamped);
|
|
1057
|
+
const current = index.steps[stepIndex];
|
|
1058
|
+
const next = index.steps[stepIndex + 1];
|
|
1059
|
+
let remaining_s = 0;
|
|
1060
|
+
if (current) {
|
|
1061
|
+
const length = current.end_m - current.begin_m;
|
|
1062
|
+
const fraction = length > 0 ? Math.max(0, Math.min(1, (current.end_m - clamped) / length)) : 0;
|
|
1063
|
+
remaining_s += fraction * current.step.duration_s;
|
|
1064
|
+
}
|
|
1065
|
+
for (let i = stepIndex + 1; i < index.steps.length; i++) {
|
|
1066
|
+
remaining_s += index.steps[i]?.step.duration_s ?? 0;
|
|
1067
|
+
}
|
|
1068
|
+
return {
|
|
1069
|
+
stepIndex,
|
|
1070
|
+
legIndex: current?.legIndex ?? 0,
|
|
1071
|
+
distanceToStep_m: next ? Math.max(0, next.begin_m - clamped) : 0,
|
|
1072
|
+
remaining_m: Math.max(0, index.total_m - clamped),
|
|
1073
|
+
remaining_s: Math.round(remaining_s)
|
|
1074
|
+
};
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
// src/navigation/snap.ts
|
|
1078
|
+
var TIE_M = 10;
|
|
1079
|
+
var LOOKBACK_SEGMENTS = 2;
|
|
1080
|
+
function snapToRoute(index, p, opts) {
|
|
1081
|
+
const { coords, cum } = index;
|
|
1082
|
+
const segments = coords.length - 1;
|
|
1083
|
+
if (segments < 1) return null;
|
|
1084
|
+
let lo = 0;
|
|
1085
|
+
let hi = segments - 1;
|
|
1086
|
+
if (opts.fromShapeIndex !== null) {
|
|
1087
|
+
const from = Math.max(0, Math.min(opts.fromShapeIndex, segments - 1));
|
|
1088
|
+
lo = Math.max(0, from - LOOKBACK_SEGMENTS);
|
|
1089
|
+
const limit = (cum[from] ?? 0) + opts.window_m;
|
|
1090
|
+
hi = from;
|
|
1091
|
+
for (let i = from + 1; i < segments; i++) {
|
|
1092
|
+
if ((cum[i] ?? 0) <= limit) hi = i;
|
|
1093
|
+
else break;
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
const heading = typeof opts.heading === "number" && Number.isFinite(opts.heading) ? opts.heading : null;
|
|
1097
|
+
let best = null;
|
|
1098
|
+
for (let i = lo; i <= hi; i++) {
|
|
1099
|
+
const a = coords[i];
|
|
1100
|
+
const b = coords[i + 1];
|
|
1101
|
+
if (!a || !b) continue;
|
|
1102
|
+
const proj = projectOnSegment(p, a, b);
|
|
1103
|
+
const segStart = cum[i] ?? 0;
|
|
1104
|
+
const segEnd = cum[i + 1] ?? segStart;
|
|
1105
|
+
const candidate = {
|
|
1106
|
+
shapeIndex: i,
|
|
1107
|
+
t: proj.t,
|
|
1108
|
+
point: proj.point,
|
|
1109
|
+
distance_m: proj.distance_m,
|
|
1110
|
+
along_m: segStart + proj.t * (segEnd - segStart)
|
|
1111
|
+
};
|
|
1112
|
+
if (!best) {
|
|
1113
|
+
best = candidate;
|
|
1114
|
+
continue;
|
|
1115
|
+
}
|
|
1116
|
+
const diff = candidate.distance_m - best.distance_m;
|
|
1117
|
+
const adjacent = candidate.shapeIndex - best.shapeIndex <= 1;
|
|
1118
|
+
if (!adjacent && Math.abs(diff) <= TIE_M) {
|
|
1119
|
+
best = preferByHeadingOrFurther(coords, best, candidate, heading);
|
|
1120
|
+
} else if (diff < 0) {
|
|
1121
|
+
best = candidate;
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
return best;
|
|
1125
|
+
}
|
|
1126
|
+
function segmentBearing(coords, shapeIndex) {
|
|
1127
|
+
const a = coords[shapeIndex];
|
|
1128
|
+
const b = coords[shapeIndex + 1];
|
|
1129
|
+
return a && b ? bearingDeg(a, b) : 0;
|
|
1130
|
+
}
|
|
1131
|
+
function preferByHeadingOrFurther(coords, current, candidate, heading) {
|
|
1132
|
+
if (heading === null) return candidate;
|
|
1133
|
+
const dCurrent = angleDiffDeg(segmentBearing(coords, current.shapeIndex), heading);
|
|
1134
|
+
const dCandidate = angleDiffDeg(segmentBearing(coords, candidate.shapeIndex), heading);
|
|
1135
|
+
return dCandidate <= dCurrent ? candidate : current;
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
// src/navigation/announce.ts
|
|
1139
|
+
var viNumber = (value) => value.replace(".", ",");
|
|
1140
|
+
function formatDistance(m, lang = "vi") {
|
|
1141
|
+
const vi = lang === "vi";
|
|
1142
|
+
if (m < 1e3) {
|
|
1143
|
+
const n2 = Math.max(0, Math.round(m));
|
|
1144
|
+
return vi ? `${n2} m\xE9t` : `${n2} meters`;
|
|
1145
|
+
}
|
|
1146
|
+
const km = m / 1e3;
|
|
1147
|
+
if (km < 10) {
|
|
1148
|
+
const s = km.toFixed(1);
|
|
1149
|
+
return vi ? `${viNumber(s)} ki-l\xF4-m\xE9t` : `${s} kilometers`;
|
|
1150
|
+
}
|
|
1151
|
+
const n = Math.round(km);
|
|
1152
|
+
return vi ? `${n} ki-l\xF4-m\xE9t` : `${n} kilometers`;
|
|
1153
|
+
}
|
|
1154
|
+
function formatDistanceShort(m) {
|
|
1155
|
+
if (m < 1e3) return `${Math.max(0, Math.round(m))} m`;
|
|
1156
|
+
const km = m / 1e3;
|
|
1157
|
+
return km < 10 ? `${viNumber(km.toFixed(1))} km` : `${Math.round(km)} km`;
|
|
1158
|
+
}
|
|
1159
|
+
function roundForSpeech(m) {
|
|
1160
|
+
if (m >= 200) return Math.round(m / 50) * 50;
|
|
1161
|
+
return Math.max(10, Math.round(m / 10) * 10);
|
|
1162
|
+
}
|
|
1163
|
+
function lowerFirst(text) {
|
|
1164
|
+
return text.length === 0 ? text : text.charAt(0).toLowerCase() + text.slice(1);
|
|
1165
|
+
}
|
|
1166
|
+
function composeApproach(distance_m, step, lang) {
|
|
1167
|
+
const cue = step.verbal_alert ?? step.verbal_pre;
|
|
1168
|
+
if (!cue) return null;
|
|
1169
|
+
const d = formatDistance(roundForSpeech(distance_m), lang);
|
|
1170
|
+
return lang === "vi" ? `Trong ${d} n\u1EEFa, ${lowerFirst(cue)}` : `In ${d}, ${lowerFirst(cue)}`;
|
|
1171
|
+
}
|
|
1172
|
+
function planAnnouncements(p, th, lang, announced, stepChanged) {
|
|
1173
|
+
const out = [];
|
|
1174
|
+
const push = (stepIndex, kind, text, priority) => {
|
|
1175
|
+
if (!text) return;
|
|
1176
|
+
const key = `${stepIndex}:${kind}`;
|
|
1177
|
+
if (announced.has(key)) return;
|
|
1178
|
+
announced.add(key);
|
|
1179
|
+
out.push({ text, kind, stepIndex, priority });
|
|
1180
|
+
};
|
|
1181
|
+
const longEnough = p.step.distance_m > th.approach_m + th.pre_m;
|
|
1182
|
+
if (p.step.kind === "depart") push(p.stepIndex, "depart", p.step.verbal_pre, 3);
|
|
1183
|
+
else if (stepChanged && longEnough) push(p.stepIndex, "post", p.step.verbal_post, 1);
|
|
1184
|
+
const next = p.nextStep;
|
|
1185
|
+
if (next) {
|
|
1186
|
+
const nextIndex = p.stepIndex + 1;
|
|
1187
|
+
const d = p.distanceToStep_m;
|
|
1188
|
+
if (d <= th.approach_m && longEnough) {
|
|
1189
|
+
push(nextIndex, "approach", composeApproach(d, next, lang), 2);
|
|
1190
|
+
}
|
|
1191
|
+
if (d <= th.pre_m)
|
|
1192
|
+
push(nextIndex, next.kind === "arrive" ? "arrive" : "pre", next.verbal_pre, 3);
|
|
1193
|
+
}
|
|
1194
|
+
return out;
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
// src/navigation/simulate.ts
|
|
1198
|
+
var SIMULATE_DEFAULT_SPEED_MPS = {
|
|
1199
|
+
walk: 1.4,
|
|
1200
|
+
motorbike: 8,
|
|
1201
|
+
car: 12
|
|
1202
|
+
};
|
|
1203
|
+
var M_PER_DEG_LAT2 = Math.PI / 180 * 63710088e-1;
|
|
1204
|
+
function mulberry32(seed) {
|
|
1205
|
+
let a = seed >>> 0;
|
|
1206
|
+
return () => {
|
|
1207
|
+
a = a + 1831565813 >>> 0;
|
|
1208
|
+
let t = a;
|
|
1209
|
+
t = Math.imul(t ^ t >>> 15, t | 1);
|
|
1210
|
+
t ^= t + Math.imul(t ^ t >>> 7, t | 61);
|
|
1211
|
+
return ((t ^ t >>> 14) >>> 0) / 4294967296;
|
|
1212
|
+
};
|
|
1213
|
+
}
|
|
1214
|
+
function simulateFixes(route, opts = {}) {
|
|
1215
|
+
const coords = decodePolyline6(route.geometry);
|
|
1216
|
+
const cum = cumulativeDistances(coords);
|
|
1217
|
+
const total = cum[cum.length - 1] ?? 0;
|
|
1218
|
+
const speed = opts.speed_mps ?? SIMULATE_DEFAULT_SPEED_MPS[route.mode];
|
|
1219
|
+
const interval = opts.interval_s ?? 1;
|
|
1220
|
+
const accuracy = opts.accuracy_m ?? 8;
|
|
1221
|
+
const jitter = opts.jitter_m ?? 0;
|
|
1222
|
+
const start = opts.start_ms ?? 17e11;
|
|
1223
|
+
const rand = mulberry32(opts.seed ?? 1);
|
|
1224
|
+
const stepM = speed * interval;
|
|
1225
|
+
if (coords.length === 0 || stepM <= 0) return [];
|
|
1226
|
+
const fixes = [];
|
|
1227
|
+
let segment = 0;
|
|
1228
|
+
const distances = [];
|
|
1229
|
+
for (let d = 0; d < total; d += stepM) distances.push(d);
|
|
1230
|
+
distances.push(total);
|
|
1231
|
+
distances.forEach((d, k) => {
|
|
1232
|
+
while (segment < coords.length - 2 && (cum[segment + 1] ?? 0) < d) segment += 1;
|
|
1233
|
+
const a = coords[segment];
|
|
1234
|
+
const b = coords[segment + 1] ?? a;
|
|
1235
|
+
if (!a || !b) return;
|
|
1236
|
+
const segStart = cum[segment] ?? 0;
|
|
1237
|
+
const segLen = (cum[segment + 1] ?? segStart) - segStart;
|
|
1238
|
+
const t = segLen > 0 ? Math.min(1, (d - segStart) / segLen) : 0;
|
|
1239
|
+
let lng = a[0] + (b[0] - a[0]) * t;
|
|
1240
|
+
let lat = a[1] + (b[1] - a[1]) * t;
|
|
1241
|
+
if (jitter > 0) {
|
|
1242
|
+
const angle = rand() * 2 * Math.PI;
|
|
1243
|
+
const radius = jitter * Math.sqrt(rand());
|
|
1244
|
+
lat += radius * Math.cos(angle) / M_PER_DEG_LAT2;
|
|
1245
|
+
lng += radius * Math.sin(angle) / (M_PER_DEG_LAT2 * Math.cos(lat * Math.PI / 180));
|
|
1246
|
+
}
|
|
1247
|
+
fixes.push({
|
|
1248
|
+
lng,
|
|
1249
|
+
lat,
|
|
1250
|
+
accuracy_m: accuracy,
|
|
1251
|
+
heading: bearingDeg(a, b),
|
|
1252
|
+
speed_mps: speed,
|
|
1253
|
+
timestamp: start + k * interval * 1e3
|
|
1254
|
+
});
|
|
1255
|
+
});
|
|
1256
|
+
return fixes;
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1259
|
+
// src/navigation/navigator.ts
|
|
1260
|
+
var DEFAULT_ACCURACY_M = 10;
|
|
1261
|
+
var WINDOW_BASE_M = 300;
|
|
1262
|
+
var WINDOW_SPEED_MPS = 40;
|
|
1263
|
+
var WINDOW_MAX_M = 3e3;
|
|
1264
|
+
var ARRIVE_NEAR_REMAINING_M = 150;
|
|
1265
|
+
var MOVING_SPEED_MPS = 1;
|
|
1266
|
+
function buildState(response, routeIndex, override) {
|
|
1267
|
+
const route = response.routes[routeIndex];
|
|
1268
|
+
if (!route) throw new Error(`createNavigator: response kh\xF4ng c\xF3 routes[${routeIndex}]`);
|
|
1269
|
+
return {
|
|
1270
|
+
response,
|
|
1271
|
+
routeIndex,
|
|
1272
|
+
route,
|
|
1273
|
+
index: buildRouteIndex(route),
|
|
1274
|
+
th: { ...NAVIGATION_THRESHOLDS[route.mode], ...override }
|
|
1275
|
+
};
|
|
1276
|
+
}
|
|
1277
|
+
function createNavigator(opts) {
|
|
1278
|
+
const rerouteMode = opts.reroute ?? "auto";
|
|
1279
|
+
const provider = opts.provider;
|
|
1280
|
+
if (rerouteMode === "auto" && !provider) {
|
|
1281
|
+
throw new Error("createNavigator: reroute 'auto' c\u1EA7n provider (v\xED d\u1EE5 client c\u1EE7a createClient)");
|
|
1282
|
+
}
|
|
1283
|
+
const lang = opts.lang ?? "vi";
|
|
1284
|
+
let rs = buildState(opts.response, opts.routeIndex ?? 0, opts.thresholds);
|
|
1285
|
+
let status = "idle";
|
|
1286
|
+
let progress = null;
|
|
1287
|
+
let last = null;
|
|
1288
|
+
let maxAlong_m = 0;
|
|
1289
|
+
let offCount = 0;
|
|
1290
|
+
let offSince = null;
|
|
1291
|
+
let backCount = 0;
|
|
1292
|
+
let announced = /* @__PURE__ */ new Set();
|
|
1293
|
+
let rerouteAttempts = 0;
|
|
1294
|
+
let lastRerouteAt = null;
|
|
1295
|
+
let inflight = false;
|
|
1296
|
+
let rerouteToken = 0;
|
|
1297
|
+
let rerouteReason = null;
|
|
1298
|
+
const listeners = {
|
|
1299
|
+
status: /* @__PURE__ */ new Set(),
|
|
1300
|
+
progress: /* @__PURE__ */ new Set(),
|
|
1301
|
+
step: /* @__PURE__ */ new Set(),
|
|
1302
|
+
waypoint: /* @__PURE__ */ new Set(),
|
|
1303
|
+
offRoute: /* @__PURE__ */ new Set(),
|
|
1304
|
+
reroute: /* @__PURE__ */ new Set(),
|
|
1305
|
+
rerouteFailed: /* @__PURE__ */ new Set(),
|
|
1306
|
+
announce: /* @__PURE__ */ new Set(),
|
|
1307
|
+
arrive: /* @__PURE__ */ new Set()
|
|
1308
|
+
};
|
|
1309
|
+
const emit = (k, e) => {
|
|
1310
|
+
for (const fn of listeners[k]) fn(e);
|
|
1311
|
+
};
|
|
1312
|
+
const setStatus = (next) => {
|
|
1313
|
+
if (next === status) return;
|
|
1314
|
+
const previous = status;
|
|
1315
|
+
status = next;
|
|
1316
|
+
emit("status", { status, previous });
|
|
1317
|
+
};
|
|
1318
|
+
const resetTracking = () => {
|
|
1319
|
+
last = null;
|
|
1320
|
+
maxAlong_m = 0;
|
|
1321
|
+
offCount = 0;
|
|
1322
|
+
offSince = null;
|
|
1323
|
+
backCount = 0;
|
|
1324
|
+
announced = /* @__PURE__ */ new Set();
|
|
1325
|
+
};
|
|
1326
|
+
const applyRoute = (response, routeIndex) => {
|
|
1327
|
+
rs = buildState(response, routeIndex, opts.thresholds);
|
|
1328
|
+
resetTracking();
|
|
1329
|
+
progress = null;
|
|
1330
|
+
};
|
|
1331
|
+
const destinationLatLng = () => {
|
|
1332
|
+
const w = rs.response.waypoints[rs.response.waypoints.length - 1];
|
|
1333
|
+
const end = rs.index.coords[rs.index.coords.length - 1];
|
|
1334
|
+
const [lng, lat] = w ? w.location : end ?? [0, 0];
|
|
1335
|
+
return [lat, lng];
|
|
1336
|
+
};
|
|
1337
|
+
const rerouteRequest = (fix, legIndex) => {
|
|
1338
|
+
const via = rs.response.waypoints.slice(legIndex + 1, -1).map((w) => [w.location[1], w.location[0]]);
|
|
1339
|
+
const request = {
|
|
1340
|
+
from: [fix.lat, fix.lng],
|
|
1341
|
+
to: destinationLatLng(),
|
|
1342
|
+
mode: rs.route.mode,
|
|
1343
|
+
lang,
|
|
1344
|
+
alternatives: false
|
|
1345
|
+
};
|
|
1346
|
+
if (via.length > 0) request.via = via;
|
|
1347
|
+
return request;
|
|
1348
|
+
};
|
|
1349
|
+
async function runReroute(reason, fix, legIndex) {
|
|
1350
|
+
if (!provider) return;
|
|
1351
|
+
const token = ++rerouteToken;
|
|
1352
|
+
inflight = true;
|
|
1353
|
+
rerouteReason = reason;
|
|
1354
|
+
lastRerouteAt = fix.timestamp;
|
|
1355
|
+
setStatus("rerouting");
|
|
1356
|
+
try {
|
|
1357
|
+
const next = await provider.directions(rerouteRequest(fix, legIndex));
|
|
1358
|
+
inflight = false;
|
|
1359
|
+
if (token !== rerouteToken || status !== "rerouting") return;
|
|
1360
|
+
rerouteAttempts = 0;
|
|
1361
|
+
applyRoute(next, 0);
|
|
1362
|
+
emit("reroute", { reason, response: next });
|
|
1363
|
+
setStatus("navigating");
|
|
1364
|
+
} catch (error) {
|
|
1365
|
+
inflight = false;
|
|
1366
|
+
if (token !== rerouteToken) return;
|
|
1367
|
+
rerouteAttempts += 1;
|
|
1368
|
+
if (status === "rerouting") setStatus("off_route");
|
|
1369
|
+
emit("rerouteFailed", {
|
|
1370
|
+
error,
|
|
1371
|
+
attempts: rerouteAttempts,
|
|
1372
|
+
final: rerouteAttempts >= rs.th.rerouteMaxFailures
|
|
1373
|
+
});
|
|
1374
|
+
}
|
|
1375
|
+
}
|
|
1376
|
+
const maybeAutoReroute = (fix, legIndex) => {
|
|
1377
|
+
if (rerouteMode !== "auto" || !provider || inflight) return;
|
|
1378
|
+
if (rerouteAttempts >= rs.th.rerouteMaxFailures) return;
|
|
1379
|
+
if (lastRerouteAt !== null && fix.timestamp - lastRerouteAt < rs.th.rerouteCooldown_s * 1e3) {
|
|
1380
|
+
return;
|
|
1381
|
+
}
|
|
1382
|
+
void runReroute("off_route", fix, legIndex);
|
|
1383
|
+
};
|
|
1384
|
+
const segmentBearing2 = (shapeIndex) => {
|
|
1385
|
+
const a = rs.index.coords[shapeIndex];
|
|
1386
|
+
const b = rs.index.coords[shapeIndex + 1];
|
|
1387
|
+
return a && b ? bearingDeg(a, b) : 0;
|
|
1388
|
+
};
|
|
1389
|
+
function update(fix) {
|
|
1390
|
+
if (status === "arrived" || status === "stopped") return;
|
|
1391
|
+
const accuracy = fix.accuracy_m ?? DEFAULT_ACCURACY_M;
|
|
1392
|
+
if (accuracy > rs.th.maxAccuracy_m) return;
|
|
1393
|
+
const prev = last;
|
|
1394
|
+
if (prev && fix.timestamp <= prev.fix.timestamp) return;
|
|
1395
|
+
if (status === "idle") setStatus("navigating");
|
|
1396
|
+
const dt_s = prev ? (fix.timestamp - prev.fix.timestamp) / 1e3 : 0;
|
|
1397
|
+
const window_m = Math.min(WINDOW_MAX_M, WINDOW_BASE_M + WINDOW_SPEED_MPS * dt_s);
|
|
1398
|
+
const here = [fix.lng, fix.lat];
|
|
1399
|
+
const anchorShapeIndex = prev ? prev.shapeIndex : null;
|
|
1400
|
+
const snap = snapToRoute(rs.index, here, {
|
|
1401
|
+
fromShapeIndex: anchorShapeIndex,
|
|
1402
|
+
window_m,
|
|
1403
|
+
heading: fix.heading
|
|
1404
|
+
});
|
|
1405
|
+
if (!snap) return;
|
|
1406
|
+
let along_m = snap.along_m;
|
|
1407
|
+
let skippedViaStep = null;
|
|
1408
|
+
const currentLeg = prev?.legIndex ?? 0;
|
|
1409
|
+
const nextLegBegin = rs.index.legBegin_m[currentLeg + 1];
|
|
1410
|
+
const nextVia = rs.response.waypoints[currentLeg + 1];
|
|
1411
|
+
if (nextLegBegin !== void 0 && nextVia && currentLeg + 1 < rs.route.legs.length && along_m < nextLegBegin && haversineM(here, nextVia.snapped) <= rs.th.arrive_m) {
|
|
1412
|
+
along_m = nextLegBegin;
|
|
1413
|
+
skippedViaStep = rs.index.steps.find((s) => s.legIndex === currentLeg && s.step.kind === "arrive") ?? null;
|
|
1414
|
+
}
|
|
1415
|
+
const threshold = Math.max(rs.th.offRoute_m, 1.5 * accuracy);
|
|
1416
|
+
const perpendicularOk = snap.distance_m <= threshold;
|
|
1417
|
+
if (perpendicularOk && prev && along_m < maxAlong_m - rs.th.offRoute_m) backCount += 1;
|
|
1418
|
+
else backCount = 0;
|
|
1419
|
+
const onRoute = perpendicularOk && backCount < rs.th.offRouteFixes;
|
|
1420
|
+
if (onRoute) {
|
|
1421
|
+
offCount = 0;
|
|
1422
|
+
offSince = null;
|
|
1423
|
+
maxAlong_m = prev ? Math.max(maxAlong_m, along_m) : along_m;
|
|
1424
|
+
if (status === "off_route" || status === "rerouting" && rerouteReason === "off_route") {
|
|
1425
|
+
rerouteAttempts = 0;
|
|
1426
|
+
setStatus("navigating");
|
|
1427
|
+
}
|
|
1428
|
+
} else {
|
|
1429
|
+
offCount += 1;
|
|
1430
|
+
offSince ??= fix.timestamp;
|
|
1431
|
+
if (status === "navigating" && offCount >= rs.th.offRouteFixes && fix.timestamp - offSince >= rs.th.offRouteSeconds * 1e3) {
|
|
1432
|
+
setStatus("off_route");
|
|
1433
|
+
emit("offRoute", { distance_m: snap.distance_m, fix });
|
|
1434
|
+
}
|
|
1435
|
+
}
|
|
1436
|
+
const displayAlong_m = Math.max(along_m, maxAlong_m);
|
|
1437
|
+
const at = progressAt(rs.index, displayAlong_m);
|
|
1438
|
+
const rawFlat = rs.index.steps[at.stepIndex];
|
|
1439
|
+
if (!rawFlat) return;
|
|
1440
|
+
const rawNextFlat = rs.index.steps[at.stepIndex + 1];
|
|
1441
|
+
const moving = (fix.speed_mps ?? 0) > MOVING_SPEED_MPS;
|
|
1442
|
+
const bearing = moving && typeof fix.heading === "number" && Number.isFinite(fix.heading) ? fix.heading : segmentBearing2(snap.shapeIndex);
|
|
1443
|
+
const end = rs.index.coords[rs.index.coords.length - 1];
|
|
1444
|
+
const lastLeg = at.legIndex === rs.route.legs.length - 1;
|
|
1445
|
+
const nearEnd = end !== void 0 && lastLeg && haversineM(here, end) <= rs.th.arrive_m && at.remaining_m <= ARRIVE_NEAR_REMAINING_M;
|
|
1446
|
+
const arrivingNow = status === "navigating" && (at.remaining_m <= rs.th.arrive_m || nearEnd);
|
|
1447
|
+
const rawStepChanged = prev === null || prev.stepIndex !== at.stepIndex;
|
|
1448
|
+
const announceProgress = {
|
|
1449
|
+
status,
|
|
1450
|
+
route: rs.route,
|
|
1451
|
+
routeIndex: rs.routeIndex,
|
|
1452
|
+
legIndex: at.legIndex,
|
|
1453
|
+
stepIndex: at.stepIndex,
|
|
1454
|
+
step: rawFlat.step,
|
|
1455
|
+
nextStep: rawNextFlat ? rawNextFlat.step : null,
|
|
1456
|
+
snapped: snap.point,
|
|
1457
|
+
bearing,
|
|
1458
|
+
shapeIndex: snap.shapeIndex,
|
|
1459
|
+
traveled_m: displayAlong_m,
|
|
1460
|
+
remaining_m: at.remaining_m,
|
|
1461
|
+
remaining_s: at.remaining_s,
|
|
1462
|
+
distanceToStep_m: at.distanceToStep_m,
|
|
1463
|
+
offRoute_m: snap.distance_m,
|
|
1464
|
+
fix
|
|
1465
|
+
};
|
|
1466
|
+
const finalIndex = rs.index.steps.length - 1;
|
|
1467
|
+
const finalFlat = rs.index.steps[finalIndex];
|
|
1468
|
+
const effectiveAt = arrivingNow && finalFlat ? {
|
|
1469
|
+
stepIndex: finalIndex,
|
|
1470
|
+
legIndex: finalFlat.legIndex,
|
|
1471
|
+
distanceToStep_m: 0,
|
|
1472
|
+
remaining_m: 0,
|
|
1473
|
+
remaining_s: 0
|
|
1474
|
+
} : at;
|
|
1475
|
+
const flat = arrivingNow && finalFlat ? finalFlat : rawFlat;
|
|
1476
|
+
const nextFlat = arrivingNow ? void 0 : rawNextFlat;
|
|
1477
|
+
const stepChanged = prev === null || prev.stepIndex !== effectiveAt.stepIndex;
|
|
1478
|
+
const legChanged = prev !== null && effectiveAt.legIndex > prev.legIndex;
|
|
1479
|
+
progress = {
|
|
1480
|
+
status,
|
|
1481
|
+
route: rs.route,
|
|
1482
|
+
routeIndex: rs.routeIndex,
|
|
1483
|
+
legIndex: effectiveAt.legIndex,
|
|
1484
|
+
stepIndex: effectiveAt.stepIndex,
|
|
1485
|
+
step: flat.step,
|
|
1486
|
+
nextStep: nextFlat ? nextFlat.step : null,
|
|
1487
|
+
snapped: snap.point,
|
|
1488
|
+
bearing,
|
|
1489
|
+
shapeIndex: snap.shapeIndex,
|
|
1490
|
+
traveled_m: arrivingNow ? rs.index.total_m : displayAlong_m,
|
|
1491
|
+
remaining_m: effectiveAt.remaining_m,
|
|
1492
|
+
remaining_s: effectiveAt.remaining_s,
|
|
1493
|
+
distanceToStep_m: effectiveAt.distanceToStep_m,
|
|
1494
|
+
offRoute_m: snap.distance_m,
|
|
1495
|
+
fix
|
|
1496
|
+
};
|
|
1497
|
+
last = {
|
|
1498
|
+
fix,
|
|
1499
|
+
// Neo cửa sổ chỉ đi theo fix ĐANG ở trên tuyến; fix lệch giữ nguyên neo tốt gần nhất.
|
|
1500
|
+
shapeIndex: onRoute ? snap.shapeIndex : anchorShapeIndex ?? snap.shapeIndex,
|
|
1501
|
+
along_m: arrivingNow ? rs.index.total_m : displayAlong_m,
|
|
1502
|
+
stepIndex: effectiveAt.stepIndex,
|
|
1503
|
+
legIndex: effectiveAt.legIndex
|
|
1504
|
+
};
|
|
1505
|
+
if (status === "navigating") {
|
|
1506
|
+
if (stepChanged && prev !== null) {
|
|
1507
|
+
emit("step", { stepIndex: effectiveAt.stepIndex, step: flat.step });
|
|
1508
|
+
}
|
|
1509
|
+
if (legChanged) {
|
|
1510
|
+
const waypoint = rs.response.waypoints[effectiveAt.legIndex];
|
|
1511
|
+
if (waypoint) emit("waypoint", { legIndex: effectiveAt.legIndex, waypoint });
|
|
1512
|
+
}
|
|
1513
|
+
}
|
|
1514
|
+
emit("progress", progress);
|
|
1515
|
+
if (status === "navigating") {
|
|
1516
|
+
if (skippedViaStep) {
|
|
1517
|
+
const key = `via:${currentLeg}:arrive`;
|
|
1518
|
+
const text = skippedViaStep.step.verbal_pre;
|
|
1519
|
+
if (text && !announced.has(key)) {
|
|
1520
|
+
announced.add(key);
|
|
1521
|
+
const viaStepIndex = rs.index.steps.indexOf(skippedViaStep);
|
|
1522
|
+
emit("announce", { text, kind: "arrive", stepIndex: viaStepIndex, priority: 3 });
|
|
1523
|
+
}
|
|
1524
|
+
}
|
|
1525
|
+
for (const a of planAnnouncements(announceProgress, rs.th, lang, announced, rawStepChanged)) {
|
|
1526
|
+
emit("announce", a);
|
|
1527
|
+
}
|
|
1528
|
+
if (arrivingNow) {
|
|
1529
|
+
setStatus("arrived");
|
|
1530
|
+
progress = { ...progress, status };
|
|
1531
|
+
const waypoint = rs.response.waypoints[rs.response.waypoints.length - 1];
|
|
1532
|
+
if (waypoint) emit("arrive", { waypoint, fix });
|
|
1533
|
+
}
|
|
1534
|
+
} else if (status === "off_route") {
|
|
1535
|
+
maybeAutoReroute(fix, effectiveAt.legIndex);
|
|
1536
|
+
}
|
|
1537
|
+
}
|
|
1538
|
+
return {
|
|
1539
|
+
get status() {
|
|
1540
|
+
return status;
|
|
1541
|
+
},
|
|
1542
|
+
get progress() {
|
|
1543
|
+
return progress;
|
|
1544
|
+
},
|
|
1545
|
+
update,
|
|
1546
|
+
setRoute(response, routeIndex = 0) {
|
|
1547
|
+
rerouteToken += 1;
|
|
1548
|
+
inflight = false;
|
|
1549
|
+
rerouteAttempts = 0;
|
|
1550
|
+
lastRerouteAt = null;
|
|
1551
|
+
applyRoute(response, routeIndex);
|
|
1552
|
+
if (status === "off_route" || status === "rerouting") setStatus("navigating");
|
|
1553
|
+
},
|
|
1554
|
+
async reroute() {
|
|
1555
|
+
if (!provider) throw new Error("createNavigator: kh\xF4ng c\xF3 provider \u0111\u1EC3 t\xEDnh l\u1EA1i");
|
|
1556
|
+
if (status === "arrived" || status === "stopped") return;
|
|
1557
|
+
if (!last) throw new Error("createNavigator: ch\u01B0a c\xF3 v\u1ECB tr\xED \u0111\u1EC3 t\xEDnh l\u1EA1i");
|
|
1558
|
+
await runReroute("manual", last.fix, last.legIndex);
|
|
1559
|
+
},
|
|
1560
|
+
stop() {
|
|
1561
|
+
rerouteToken += 1;
|
|
1562
|
+
inflight = false;
|
|
1563
|
+
setStatus("stopped");
|
|
1564
|
+
},
|
|
1565
|
+
on(event, handler) {
|
|
1566
|
+
listeners[event].add(handler);
|
|
1567
|
+
},
|
|
1568
|
+
off(event, handler) {
|
|
1569
|
+
listeners[event].delete(handler);
|
|
1570
|
+
}
|
|
1571
|
+
};
|
|
1572
|
+
}
|
|
1573
|
+
|
|
1574
|
+
// src/navigation/route-features.ts
|
|
1575
|
+
var EMPTY_ROUTE_FEATURES = {
|
|
1576
|
+
type: "FeatureCollection",
|
|
1577
|
+
features: []
|
|
1578
|
+
};
|
|
1579
|
+
function decodeRoutes(response) {
|
|
1580
|
+
return response.routes.map((route) => decodePolyline6(route.geometry));
|
|
1581
|
+
}
|
|
1582
|
+
var line = (kind, index, coordinates) => ({
|
|
1583
|
+
type: "Feature",
|
|
1584
|
+
geometry: { type: "LineString", coordinates },
|
|
1585
|
+
properties: { kind, index }
|
|
1586
|
+
});
|
|
1587
|
+
function routeFeatures(coords, opts) {
|
|
1588
|
+
const progress = opts.progress ?? null;
|
|
1589
|
+
const features = [];
|
|
1590
|
+
for (const [i, c] of coords.entries()) {
|
|
1591
|
+
if (i !== opts.active) {
|
|
1592
|
+
features.push(line("alt", i, [...c]));
|
|
1593
|
+
continue;
|
|
1594
|
+
}
|
|
1595
|
+
if (progress && progress.shapeIndex < c.length - 1) {
|
|
1596
|
+
features.push(
|
|
1597
|
+
line("traveled", i, [...c.slice(0, progress.shapeIndex + 1), progress.snapped]),
|
|
1598
|
+
line("active", i, [progress.snapped, ...c.slice(progress.shapeIndex + 1)])
|
|
1599
|
+
);
|
|
1600
|
+
} else {
|
|
1601
|
+
features.push(line("active", i, [...c]));
|
|
1602
|
+
}
|
|
1603
|
+
}
|
|
1604
|
+
if (opts.puck && progress) {
|
|
1605
|
+
features.push({
|
|
1606
|
+
type: "Feature",
|
|
1607
|
+
geometry: { type: "Point", coordinates: progress.snapped },
|
|
1608
|
+
properties: { kind: "puck", bearing: progress.bearing ?? 0 }
|
|
1609
|
+
});
|
|
1610
|
+
}
|
|
1611
|
+
return { type: "FeatureCollection", features };
|
|
1612
|
+
}
|
|
795
1613
|
export {
|
|
796
1614
|
ATTRIBUTION_LINKS,
|
|
797
1615
|
DEFAULT_POI_SOURCES,
|
|
1616
|
+
EMPTY_ROUTE_FEATURES,
|
|
1617
|
+
FIRST_SYMBOL_LAYER_ID,
|
|
1618
|
+
MANEUVER_KINDS,
|
|
798
1619
|
MapsLibVNError,
|
|
799
1620
|
NAME_FILLERS,
|
|
1621
|
+
NAVIGATION_THRESHOLDS,
|
|
800
1622
|
POI_LAYER_ID,
|
|
801
1623
|
POI_SOURCES,
|
|
802
1624
|
POI_SOURCE_PROFILES,
|
|
1625
|
+
SIMULATE_DEFAULT_SPEED_MPS,
|
|
803
1626
|
TOPONYM_ALIAS,
|
|
1627
|
+
VALHALLA_MANEUVER_KIND,
|
|
804
1628
|
adminAliasKeys,
|
|
1629
|
+
angleDiffDeg,
|
|
805
1630
|
applyBrandAlias,
|
|
806
1631
|
applyToponymAlias,
|
|
807
1632
|
attributionHtml,
|
|
808
1633
|
attributionText,
|
|
1634
|
+
bearingDeg,
|
|
1635
|
+
buildRouteIndex,
|
|
1636
|
+
composeApproach,
|
|
809
1637
|
createClient,
|
|
1638
|
+
createNavigator,
|
|
1639
|
+
cumulativeDistances,
|
|
1640
|
+
decodePolyline6,
|
|
1641
|
+
decodeRoutes,
|
|
1642
|
+
encodePolyline6,
|
|
810
1643
|
expandAbbrev,
|
|
811
1644
|
filterNameAlt,
|
|
812
1645
|
foldTelex,
|
|
1646
|
+
formatDistance,
|
|
1647
|
+
formatDistanceShort,
|
|
1648
|
+
haversineM,
|
|
813
1649
|
hidePoiLayer,
|
|
814
1650
|
isNameLabelLayer,
|
|
815
1651
|
isPoiStyleLayer,
|
|
816
1652
|
localizeStyle,
|
|
817
1653
|
looksLikeTelex,
|
|
1654
|
+
lowerFirst,
|
|
1655
|
+
maneuverKindFromValhalla,
|
|
1656
|
+
mulberry32,
|
|
818
1657
|
nameCore,
|
|
819
1658
|
nameExpression,
|
|
820
1659
|
normalizePoiSources,
|
|
821
1660
|
normalizeVi,
|
|
822
1661
|
parseAddress,
|
|
823
1662
|
parsePoiSourcesCsv,
|
|
1663
|
+
planAnnouncements,
|
|
824
1664
|
poiSourceClause,
|
|
825
1665
|
poiSourcesKey,
|
|
826
1666
|
profileForSources,
|
|
1667
|
+
progressAt,
|
|
1668
|
+
projectOnSegment,
|
|
1669
|
+
roundForSpeech,
|
|
1670
|
+
routeFeatures,
|
|
827
1671
|
searchKeys,
|
|
1672
|
+
simulateFixes,
|
|
1673
|
+
snapToRoute,
|
|
1674
|
+
stepAt,
|
|
828
1675
|
stripDiacritics,
|
|
829
1676
|
viKey
|
|
830
1677
|
};
|