@mapslibvn/react-native 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 +122 -4
- package/THIRD_PARTY_NOTICES.md +33 -2
- package/dist/chunk-PKBMQBKP.js +7 -0
- package/dist/expo/index.d.ts +96 -0
- package/dist/expo/index.js +330 -0
- package/dist/index.d.ts +100 -219
- package/dist/index.js +1458 -58
- package/dist/session-B7Fu_s_b.d.ts +557 -0
- package/package.json +30 -3
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import {
|
|
2
|
+
__publicField
|
|
3
|
+
} from "./chunk-PKBMQBKP.js";
|
|
4
4
|
|
|
5
5
|
// src/map.tsx
|
|
6
6
|
import {
|
|
@@ -70,6 +70,7 @@ function normalizePoiSources(list) {
|
|
|
70
70
|
function poiSourcesKey(sources) {
|
|
71
71
|
return (normalizePoiSources(sources) ?? []).join(",");
|
|
72
72
|
}
|
|
73
|
+
var latLng = ([lat, lng]) => `${lat},${lng}`;
|
|
73
74
|
function createClient(options) {
|
|
74
75
|
const baseUrl = options.baseUrl.replace(/\/+$/, "");
|
|
75
76
|
const poiSources = normalizePoiSources(options.poiSources ?? DEFAULT_POI_SOURCES);
|
|
@@ -152,6 +153,15 @@ function createClient(options) {
|
|
|
152
153
|
limit: opts.limit
|
|
153
154
|
}),
|
|
154
155
|
reverse: (lat, lng) => get("/v1/reverse", { lat, lng, sources }),
|
|
156
|
+
/** Chỉ đường (spec dẫn đường A). Response dùng [lng, lat]; tham số vào dùng [lat, lng]. */
|
|
157
|
+
directions: (opts) => get("/v1/directions", {
|
|
158
|
+
from: latLng(opts.from),
|
|
159
|
+
to: latLng(opts.to),
|
|
160
|
+
via: opts.via && opts.via.length > 0 ? opts.via.map(latLng).join(";") : void 0,
|
|
161
|
+
mode: opts.mode,
|
|
162
|
+
lang: opts.lang,
|
|
163
|
+
alternatives: opts.alternatives === void 0 ? void 0 : opts.alternatives ? 1 : 0
|
|
164
|
+
}),
|
|
155
165
|
/** Gửi đóng góp/sửa POI (spec 6.1). Khoá phải có scope edits:write. */
|
|
156
166
|
suggestEdit: (edit) => post("/v1/edits", edit)
|
|
157
167
|
};
|
|
@@ -280,6 +290,10 @@ var RE_STREET_LIKE = new RegExp(
|
|
|
280
290
|
String.raw`^${PREFIX}${NUM_LEAD}(?:/|\s|$)|\b${ALLEY_KW}\s*\d|^(?:duong|d\.|pho)\s`
|
|
281
291
|
);
|
|
282
292
|
var POI_LAYER_ID = "poi";
|
|
293
|
+
var FIRST_SYMBOL_LAYER_ID = {
|
|
294
|
+
light: "road_one_way_arrow",
|
|
295
|
+
dark: "water_name"
|
|
296
|
+
};
|
|
283
297
|
var SOVEREIGNTY_LABEL_ID = "sovereignty-label";
|
|
284
298
|
function isPoiStyleLayer(layer) {
|
|
285
299
|
return layer.id === POI_LAYER_ID || layer.source === "poi";
|
|
@@ -426,12 +440,701 @@ var compile = (rules) => rules.map(([pattern, replacement]) => ({ re: new RegExp
|
|
|
426
440
|
var WORD_START = compile(RULES2.wordStart);
|
|
427
441
|
var WORD_END = compile(RULES2.wordEnd);
|
|
428
442
|
var ANYWHERE = compile(RULES2.anywhere);
|
|
443
|
+
function decodePolyline6(encoded) {
|
|
444
|
+
const coords = [];
|
|
445
|
+
let index = 0;
|
|
446
|
+
let lat = 0;
|
|
447
|
+
let lng = 0;
|
|
448
|
+
const next = () => {
|
|
449
|
+
let result = 0;
|
|
450
|
+
let shift = 0;
|
|
451
|
+
let byte;
|
|
452
|
+
do {
|
|
453
|
+
byte = encoded.charCodeAt(index++) - 63;
|
|
454
|
+
result |= (byte & 31) << shift;
|
|
455
|
+
shift += 5;
|
|
456
|
+
} while (byte >= 32);
|
|
457
|
+
return result & 1 ? ~(result >> 1) : result >> 1;
|
|
458
|
+
};
|
|
459
|
+
while (index < encoded.length) {
|
|
460
|
+
lat += next();
|
|
461
|
+
lng += next();
|
|
462
|
+
coords.push([lng / 1e6, lat / 1e6]);
|
|
463
|
+
}
|
|
464
|
+
return coords;
|
|
465
|
+
}
|
|
466
|
+
var NAVIGATION_THRESHOLDS = {
|
|
467
|
+
walk: {
|
|
468
|
+
offRoute_m: 25,
|
|
469
|
+
offRouteFixes: 3,
|
|
470
|
+
offRouteSeconds: 5,
|
|
471
|
+
maxAccuracy_m: 60,
|
|
472
|
+
approach_m: 40,
|
|
473
|
+
pre_m: 15,
|
|
474
|
+
arrive_m: 15,
|
|
475
|
+
rerouteCooldown_s: 15,
|
|
476
|
+
rerouteMaxFailures: 3
|
|
477
|
+
},
|
|
478
|
+
motorbike: {
|
|
479
|
+
offRoute_m: 40,
|
|
480
|
+
offRouteFixes: 3,
|
|
481
|
+
offRouteSeconds: 5,
|
|
482
|
+
maxAccuracy_m: 100,
|
|
483
|
+
approach_m: 200,
|
|
484
|
+
pre_m: 50,
|
|
485
|
+
arrive_m: 25,
|
|
486
|
+
rerouteCooldown_s: 15,
|
|
487
|
+
rerouteMaxFailures: 3
|
|
488
|
+
},
|
|
489
|
+
car: {
|
|
490
|
+
offRoute_m: 50,
|
|
491
|
+
offRouteFixes: 3,
|
|
492
|
+
offRouteSeconds: 5,
|
|
493
|
+
maxAccuracy_m: 100,
|
|
494
|
+
approach_m: 400,
|
|
495
|
+
pre_m: 80,
|
|
496
|
+
arrive_m: 30,
|
|
497
|
+
rerouteCooldown_s: 15,
|
|
498
|
+
rerouteMaxFailures: 3
|
|
499
|
+
}
|
|
500
|
+
};
|
|
501
|
+
var EARTH_RADIUS_M = 63710088e-1;
|
|
502
|
+
var M_PER_DEG_LAT = Math.PI / 180 * EARTH_RADIUS_M;
|
|
503
|
+
var toRad = (deg) => deg * Math.PI / 180;
|
|
504
|
+
var toDeg = (rad) => rad * 180 / Math.PI;
|
|
505
|
+
function haversineM(a, b) {
|
|
506
|
+
const dLat = toRad(b[1] - a[1]);
|
|
507
|
+
const dLng = toRad(b[0] - a[0]);
|
|
508
|
+
const h = Math.sin(dLat / 2) ** 2 + Math.cos(toRad(a[1])) * Math.cos(toRad(b[1])) * Math.sin(dLng / 2) ** 2;
|
|
509
|
+
return 2 * EARTH_RADIUS_M * Math.asin(Math.min(1, Math.sqrt(h)));
|
|
510
|
+
}
|
|
511
|
+
function bearingDeg(a, b) {
|
|
512
|
+
const phi1 = toRad(a[1]);
|
|
513
|
+
const phi2 = toRad(b[1]);
|
|
514
|
+
const dLambda = toRad(b[0] - a[0]);
|
|
515
|
+
const y = Math.sin(dLambda) * Math.cos(phi2);
|
|
516
|
+
const x = Math.cos(phi1) * Math.sin(phi2) - Math.sin(phi1) * Math.cos(phi2) * Math.cos(dLambda);
|
|
517
|
+
return (toDeg(Math.atan2(y, x)) + 360) % 360;
|
|
518
|
+
}
|
|
519
|
+
function angleDiffDeg(a, b) {
|
|
520
|
+
const d = Math.abs(((a - b) % 360 + 360) % 360);
|
|
521
|
+
return d > 180 ? 360 - d : d;
|
|
522
|
+
}
|
|
523
|
+
function projectOnSegment(p, a, b) {
|
|
524
|
+
const mPerDegLng = M_PER_DEG_LAT * Math.cos(toRad(a[1]));
|
|
525
|
+
const bx = (b[0] - a[0]) * mPerDegLng;
|
|
526
|
+
const by = (b[1] - a[1]) * M_PER_DEG_LAT;
|
|
527
|
+
const px = (p[0] - a[0]) * mPerDegLng;
|
|
528
|
+
const py = (p[1] - a[1]) * M_PER_DEG_LAT;
|
|
529
|
+
const len2 = bx * bx + by * by;
|
|
530
|
+
const t = len2 === 0 ? 0 : Math.max(0, Math.min(1, (px * bx + py * by) / len2));
|
|
531
|
+
const qx = bx * t;
|
|
532
|
+
const qy = by * t;
|
|
533
|
+
const point = t === 1 ? [b[0], b[1]] : [a[0] + qx / mPerDegLng, a[1] + qy / M_PER_DEG_LAT];
|
|
534
|
+
return { t, point, distance_m: Math.hypot(px - qx, py - qy) };
|
|
535
|
+
}
|
|
536
|
+
function cumulativeDistances(coords) {
|
|
537
|
+
const cum = [];
|
|
538
|
+
let total = 0;
|
|
539
|
+
for (let i = 0; i < coords.length; i++) {
|
|
540
|
+
const prev = coords[i - 1];
|
|
541
|
+
const cur = coords[i];
|
|
542
|
+
if (i > 0 && prev && cur) total += haversineM(prev, cur);
|
|
543
|
+
cum.push(total);
|
|
544
|
+
}
|
|
545
|
+
return cum;
|
|
546
|
+
}
|
|
547
|
+
function buildRouteIndex(route) {
|
|
548
|
+
const coords = decodePolyline6(route.geometry);
|
|
549
|
+
const cum = cumulativeDistances(coords);
|
|
550
|
+
const total_m = cum[cum.length - 1] ?? 0;
|
|
551
|
+
const at = (vertex) => cum[Math.min(Math.max(vertex, 0), cum.length - 1)] ?? 0;
|
|
552
|
+
const steps = [];
|
|
553
|
+
route.legs.forEach((leg, legIndex) => {
|
|
554
|
+
leg.steps.forEach((step, indexInLeg) => {
|
|
555
|
+
const begin_m = at(step.shape_begin);
|
|
556
|
+
steps.push({ step, legIndex, indexInLeg, begin_m, end_m: begin_m });
|
|
557
|
+
});
|
|
558
|
+
});
|
|
559
|
+
for (let i = 0; i < steps.length; i++) {
|
|
560
|
+
const current = steps[i];
|
|
561
|
+
if (current) current.end_m = steps[i + 1]?.begin_m ?? total_m;
|
|
562
|
+
}
|
|
563
|
+
return { coords, cum, total_m, steps, legBegin_m: route.legs.map((leg) => at(leg.shape_offset)) };
|
|
564
|
+
}
|
|
565
|
+
function stepAt(index, along_m) {
|
|
566
|
+
const { steps, total_m } = index;
|
|
567
|
+
if (steps.length === 0) return 0;
|
|
568
|
+
if (along_m >= total_m) return steps.length - 1;
|
|
569
|
+
let found = 0;
|
|
570
|
+
for (let i = 0; i < steps.length; i++) {
|
|
571
|
+
const s = steps[i];
|
|
572
|
+
if (!s) continue;
|
|
573
|
+
if (s.begin_m > along_m) break;
|
|
574
|
+
if (along_m < s.end_m) {
|
|
575
|
+
found = i;
|
|
576
|
+
break;
|
|
577
|
+
}
|
|
578
|
+
found = i;
|
|
579
|
+
}
|
|
580
|
+
return found;
|
|
581
|
+
}
|
|
582
|
+
function progressAt(index, along_m) {
|
|
583
|
+
const clamped = Math.max(0, Math.min(along_m, index.total_m));
|
|
584
|
+
const stepIndex = stepAt(index, clamped);
|
|
585
|
+
const current = index.steps[stepIndex];
|
|
586
|
+
const next = index.steps[stepIndex + 1];
|
|
587
|
+
let remaining_s = 0;
|
|
588
|
+
if (current) {
|
|
589
|
+
const length = current.end_m - current.begin_m;
|
|
590
|
+
const fraction = length > 0 ? Math.max(0, Math.min(1, (current.end_m - clamped) / length)) : 0;
|
|
591
|
+
remaining_s += fraction * current.step.duration_s;
|
|
592
|
+
}
|
|
593
|
+
for (let i = stepIndex + 1; i < index.steps.length; i++) {
|
|
594
|
+
remaining_s += index.steps[i]?.step.duration_s ?? 0;
|
|
595
|
+
}
|
|
596
|
+
return {
|
|
597
|
+
stepIndex,
|
|
598
|
+
legIndex: current?.legIndex ?? 0,
|
|
599
|
+
distanceToStep_m: next ? Math.max(0, next.begin_m - clamped) : 0,
|
|
600
|
+
remaining_m: Math.max(0, index.total_m - clamped),
|
|
601
|
+
remaining_s: Math.round(remaining_s)
|
|
602
|
+
};
|
|
603
|
+
}
|
|
604
|
+
var TIE_M = 10;
|
|
605
|
+
var LOOKBACK_SEGMENTS = 2;
|
|
606
|
+
function snapToRoute(index, p, opts) {
|
|
607
|
+
const { coords, cum } = index;
|
|
608
|
+
const segments = coords.length - 1;
|
|
609
|
+
if (segments < 1) return null;
|
|
610
|
+
let lo = 0;
|
|
611
|
+
let hi = segments - 1;
|
|
612
|
+
if (opts.fromShapeIndex !== null) {
|
|
613
|
+
const from = Math.max(0, Math.min(opts.fromShapeIndex, segments - 1));
|
|
614
|
+
lo = Math.max(0, from - LOOKBACK_SEGMENTS);
|
|
615
|
+
const limit = (cum[from] ?? 0) + opts.window_m;
|
|
616
|
+
hi = from;
|
|
617
|
+
for (let i = from + 1; i < segments; i++) {
|
|
618
|
+
if ((cum[i] ?? 0) <= limit) hi = i;
|
|
619
|
+
else break;
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
const heading = typeof opts.heading === "number" && Number.isFinite(opts.heading) ? opts.heading : null;
|
|
623
|
+
let best = null;
|
|
624
|
+
for (let i = lo; i <= hi; i++) {
|
|
625
|
+
const a = coords[i];
|
|
626
|
+
const b = coords[i + 1];
|
|
627
|
+
if (!a || !b) continue;
|
|
628
|
+
const proj = projectOnSegment(p, a, b);
|
|
629
|
+
const segStart = cum[i] ?? 0;
|
|
630
|
+
const segEnd = cum[i + 1] ?? segStart;
|
|
631
|
+
const candidate = {
|
|
632
|
+
shapeIndex: i,
|
|
633
|
+
t: proj.t,
|
|
634
|
+
point: proj.point,
|
|
635
|
+
distance_m: proj.distance_m,
|
|
636
|
+
along_m: segStart + proj.t * (segEnd - segStart)
|
|
637
|
+
};
|
|
638
|
+
if (!best) {
|
|
639
|
+
best = candidate;
|
|
640
|
+
continue;
|
|
641
|
+
}
|
|
642
|
+
const diff = candidate.distance_m - best.distance_m;
|
|
643
|
+
const adjacent = candidate.shapeIndex - best.shapeIndex <= 1;
|
|
644
|
+
if (!adjacent && Math.abs(diff) <= TIE_M) {
|
|
645
|
+
best = preferByHeadingOrFurther(coords, best, candidate, heading);
|
|
646
|
+
} else if (diff < 0) {
|
|
647
|
+
best = candidate;
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
return best;
|
|
651
|
+
}
|
|
652
|
+
function segmentBearing(coords, shapeIndex) {
|
|
653
|
+
const a = coords[shapeIndex];
|
|
654
|
+
const b = coords[shapeIndex + 1];
|
|
655
|
+
return a && b ? bearingDeg(a, b) : 0;
|
|
656
|
+
}
|
|
657
|
+
function preferByHeadingOrFurther(coords, current, candidate, heading) {
|
|
658
|
+
if (heading === null) return candidate;
|
|
659
|
+
const dCurrent = angleDiffDeg(segmentBearing(coords, current.shapeIndex), heading);
|
|
660
|
+
const dCandidate = angleDiffDeg(segmentBearing(coords, candidate.shapeIndex), heading);
|
|
661
|
+
return dCandidate <= dCurrent ? candidate : current;
|
|
662
|
+
}
|
|
663
|
+
var viNumber = (value) => value.replace(".", ",");
|
|
664
|
+
function formatDistance(m, lang = "vi") {
|
|
665
|
+
const vi = lang === "vi";
|
|
666
|
+
if (m < 1e3) {
|
|
667
|
+
const n2 = Math.max(0, Math.round(m));
|
|
668
|
+
return vi ? `${n2} m\xE9t` : `${n2} meters`;
|
|
669
|
+
}
|
|
670
|
+
const km = m / 1e3;
|
|
671
|
+
if (km < 10) {
|
|
672
|
+
const s = km.toFixed(1);
|
|
673
|
+
return vi ? `${viNumber(s)} ki-l\xF4-m\xE9t` : `${s} kilometers`;
|
|
674
|
+
}
|
|
675
|
+
const n = Math.round(km);
|
|
676
|
+
return vi ? `${n} ki-l\xF4-m\xE9t` : `${n} kilometers`;
|
|
677
|
+
}
|
|
678
|
+
function formatDistanceShort(m) {
|
|
679
|
+
if (m < 1e3) return `${Math.max(0, Math.round(m))} m`;
|
|
680
|
+
const km = m / 1e3;
|
|
681
|
+
return km < 10 ? `${viNumber(km.toFixed(1))} km` : `${Math.round(km)} km`;
|
|
682
|
+
}
|
|
683
|
+
function roundForSpeech(m) {
|
|
684
|
+
if (m >= 200) return Math.round(m / 50) * 50;
|
|
685
|
+
return Math.max(10, Math.round(m / 10) * 10);
|
|
686
|
+
}
|
|
687
|
+
function lowerFirst(text) {
|
|
688
|
+
return text.length === 0 ? text : text.charAt(0).toLowerCase() + text.slice(1);
|
|
689
|
+
}
|
|
690
|
+
function composeApproach(distance_m, step, lang) {
|
|
691
|
+
const cue = step.verbal_alert ?? step.verbal_pre;
|
|
692
|
+
if (!cue) return null;
|
|
693
|
+
const d = formatDistance(roundForSpeech(distance_m), lang);
|
|
694
|
+
return lang === "vi" ? `Trong ${d} n\u1EEFa, ${lowerFirst(cue)}` : `In ${d}, ${lowerFirst(cue)}`;
|
|
695
|
+
}
|
|
696
|
+
function planAnnouncements(p, th, lang, announced, stepChanged) {
|
|
697
|
+
const out = [];
|
|
698
|
+
const push = (stepIndex, kind, text, priority) => {
|
|
699
|
+
if (!text) return;
|
|
700
|
+
const key = `${stepIndex}:${kind}`;
|
|
701
|
+
if (announced.has(key)) return;
|
|
702
|
+
announced.add(key);
|
|
703
|
+
out.push({ text, kind, stepIndex, priority });
|
|
704
|
+
};
|
|
705
|
+
const longEnough = p.step.distance_m > th.approach_m + th.pre_m;
|
|
706
|
+
if (p.step.kind === "depart") push(p.stepIndex, "depart", p.step.verbal_pre, 3);
|
|
707
|
+
else if (stepChanged && longEnough) push(p.stepIndex, "post", p.step.verbal_post, 1);
|
|
708
|
+
const next = p.nextStep;
|
|
709
|
+
if (next) {
|
|
710
|
+
const nextIndex = p.stepIndex + 1;
|
|
711
|
+
const d = p.distanceToStep_m;
|
|
712
|
+
if (d <= th.approach_m && longEnough) {
|
|
713
|
+
push(nextIndex, "approach", composeApproach(d, next, lang), 2);
|
|
714
|
+
}
|
|
715
|
+
if (d <= th.pre_m)
|
|
716
|
+
push(nextIndex, next.kind === "arrive" ? "arrive" : "pre", next.verbal_pre, 3);
|
|
717
|
+
}
|
|
718
|
+
return out;
|
|
719
|
+
}
|
|
720
|
+
var SIMULATE_DEFAULT_SPEED_MPS = {
|
|
721
|
+
walk: 1.4,
|
|
722
|
+
motorbike: 8,
|
|
723
|
+
car: 12
|
|
724
|
+
};
|
|
725
|
+
var M_PER_DEG_LAT2 = Math.PI / 180 * 63710088e-1;
|
|
726
|
+
function mulberry32(seed) {
|
|
727
|
+
let a = seed >>> 0;
|
|
728
|
+
return () => {
|
|
729
|
+
a = a + 1831565813 >>> 0;
|
|
730
|
+
let t = a;
|
|
731
|
+
t = Math.imul(t ^ t >>> 15, t | 1);
|
|
732
|
+
t ^= t + Math.imul(t ^ t >>> 7, t | 61);
|
|
733
|
+
return ((t ^ t >>> 14) >>> 0) / 4294967296;
|
|
734
|
+
};
|
|
735
|
+
}
|
|
736
|
+
function simulateFixes(route, opts = {}) {
|
|
737
|
+
const coords = decodePolyline6(route.geometry);
|
|
738
|
+
const cum = cumulativeDistances(coords);
|
|
739
|
+
const total = cum[cum.length - 1] ?? 0;
|
|
740
|
+
const speed = opts.speed_mps ?? SIMULATE_DEFAULT_SPEED_MPS[route.mode];
|
|
741
|
+
const interval = opts.interval_s ?? 1;
|
|
742
|
+
const accuracy = opts.accuracy_m ?? 8;
|
|
743
|
+
const jitter = opts.jitter_m ?? 0;
|
|
744
|
+
const start = opts.start_ms ?? 17e11;
|
|
745
|
+
const rand = mulberry32(opts.seed ?? 1);
|
|
746
|
+
const stepM = speed * interval;
|
|
747
|
+
if (coords.length === 0 || stepM <= 0) return [];
|
|
748
|
+
const fixes = [];
|
|
749
|
+
let segment = 0;
|
|
750
|
+
const distances = [];
|
|
751
|
+
for (let d = 0; d < total; d += stepM) distances.push(d);
|
|
752
|
+
distances.push(total);
|
|
753
|
+
distances.forEach((d, k) => {
|
|
754
|
+
while (segment < coords.length - 2 && (cum[segment + 1] ?? 0) < d) segment += 1;
|
|
755
|
+
const a = coords[segment];
|
|
756
|
+
const b = coords[segment + 1] ?? a;
|
|
757
|
+
if (!a || !b) return;
|
|
758
|
+
const segStart = cum[segment] ?? 0;
|
|
759
|
+
const segLen = (cum[segment + 1] ?? segStart) - segStart;
|
|
760
|
+
const t = segLen > 0 ? Math.min(1, (d - segStart) / segLen) : 0;
|
|
761
|
+
let lng = a[0] + (b[0] - a[0]) * t;
|
|
762
|
+
let lat = a[1] + (b[1] - a[1]) * t;
|
|
763
|
+
if (jitter > 0) {
|
|
764
|
+
const angle = rand() * 2 * Math.PI;
|
|
765
|
+
const radius = jitter * Math.sqrt(rand());
|
|
766
|
+
lat += radius * Math.cos(angle) / M_PER_DEG_LAT2;
|
|
767
|
+
lng += radius * Math.sin(angle) / (M_PER_DEG_LAT2 * Math.cos(lat * Math.PI / 180));
|
|
768
|
+
}
|
|
769
|
+
fixes.push({
|
|
770
|
+
lng,
|
|
771
|
+
lat,
|
|
772
|
+
accuracy_m: accuracy,
|
|
773
|
+
heading: bearingDeg(a, b),
|
|
774
|
+
speed_mps: speed,
|
|
775
|
+
timestamp: start + k * interval * 1e3
|
|
776
|
+
});
|
|
777
|
+
});
|
|
778
|
+
return fixes;
|
|
779
|
+
}
|
|
780
|
+
var DEFAULT_ACCURACY_M = 10;
|
|
781
|
+
var WINDOW_BASE_M = 300;
|
|
782
|
+
var WINDOW_SPEED_MPS = 40;
|
|
783
|
+
var WINDOW_MAX_M = 3e3;
|
|
784
|
+
var ARRIVE_NEAR_REMAINING_M = 150;
|
|
785
|
+
var MOVING_SPEED_MPS = 1;
|
|
786
|
+
function buildState(response, routeIndex, override) {
|
|
787
|
+
const route = response.routes[routeIndex];
|
|
788
|
+
if (!route) throw new Error(`createNavigator: response kh\xF4ng c\xF3 routes[${routeIndex}]`);
|
|
789
|
+
return {
|
|
790
|
+
response,
|
|
791
|
+
routeIndex,
|
|
792
|
+
route,
|
|
793
|
+
index: buildRouteIndex(route),
|
|
794
|
+
th: { ...NAVIGATION_THRESHOLDS[route.mode], ...override }
|
|
795
|
+
};
|
|
796
|
+
}
|
|
797
|
+
function createNavigator(opts) {
|
|
798
|
+
const rerouteMode = opts.reroute ?? "auto";
|
|
799
|
+
const provider = opts.provider;
|
|
800
|
+
if (rerouteMode === "auto" && !provider) {
|
|
801
|
+
throw new Error("createNavigator: reroute 'auto' c\u1EA7n provider (v\xED d\u1EE5 client c\u1EE7a createClient)");
|
|
802
|
+
}
|
|
803
|
+
const lang = opts.lang ?? "vi";
|
|
804
|
+
let rs = buildState(opts.response, opts.routeIndex ?? 0, opts.thresholds);
|
|
805
|
+
let status = "idle";
|
|
806
|
+
let progress = null;
|
|
807
|
+
let last = null;
|
|
808
|
+
let maxAlong_m = 0;
|
|
809
|
+
let offCount = 0;
|
|
810
|
+
let offSince = null;
|
|
811
|
+
let backCount = 0;
|
|
812
|
+
let announced = /* @__PURE__ */ new Set();
|
|
813
|
+
let rerouteAttempts = 0;
|
|
814
|
+
let lastRerouteAt = null;
|
|
815
|
+
let inflight = false;
|
|
816
|
+
let rerouteToken = 0;
|
|
817
|
+
let rerouteReason = null;
|
|
818
|
+
const listeners = {
|
|
819
|
+
status: /* @__PURE__ */ new Set(),
|
|
820
|
+
progress: /* @__PURE__ */ new Set(),
|
|
821
|
+
step: /* @__PURE__ */ new Set(),
|
|
822
|
+
waypoint: /* @__PURE__ */ new Set(),
|
|
823
|
+
offRoute: /* @__PURE__ */ new Set(),
|
|
824
|
+
reroute: /* @__PURE__ */ new Set(),
|
|
825
|
+
rerouteFailed: /* @__PURE__ */ new Set(),
|
|
826
|
+
announce: /* @__PURE__ */ new Set(),
|
|
827
|
+
arrive: /* @__PURE__ */ new Set()
|
|
828
|
+
};
|
|
829
|
+
const emit = (k, e) => {
|
|
830
|
+
for (const fn of listeners[k]) fn(e);
|
|
831
|
+
};
|
|
832
|
+
const setStatus = (next) => {
|
|
833
|
+
if (next === status) return;
|
|
834
|
+
const previous = status;
|
|
835
|
+
status = next;
|
|
836
|
+
emit("status", { status, previous });
|
|
837
|
+
};
|
|
838
|
+
const resetTracking = () => {
|
|
839
|
+
last = null;
|
|
840
|
+
maxAlong_m = 0;
|
|
841
|
+
offCount = 0;
|
|
842
|
+
offSince = null;
|
|
843
|
+
backCount = 0;
|
|
844
|
+
announced = /* @__PURE__ */ new Set();
|
|
845
|
+
};
|
|
846
|
+
const applyRoute = (response, routeIndex) => {
|
|
847
|
+
rs = buildState(response, routeIndex, opts.thresholds);
|
|
848
|
+
resetTracking();
|
|
849
|
+
progress = null;
|
|
850
|
+
};
|
|
851
|
+
const destinationLatLng = () => {
|
|
852
|
+
const w = rs.response.waypoints[rs.response.waypoints.length - 1];
|
|
853
|
+
const end = rs.index.coords[rs.index.coords.length - 1];
|
|
854
|
+
const [lng, lat] = w ? w.location : end ?? [0, 0];
|
|
855
|
+
return [lat, lng];
|
|
856
|
+
};
|
|
857
|
+
const rerouteRequest = (fix, legIndex) => {
|
|
858
|
+
const via = rs.response.waypoints.slice(legIndex + 1, -1).map((w) => [w.location[1], w.location[0]]);
|
|
859
|
+
const request = {
|
|
860
|
+
from: [fix.lat, fix.lng],
|
|
861
|
+
to: destinationLatLng(),
|
|
862
|
+
mode: rs.route.mode,
|
|
863
|
+
lang,
|
|
864
|
+
alternatives: false
|
|
865
|
+
};
|
|
866
|
+
if (via.length > 0) request.via = via;
|
|
867
|
+
return request;
|
|
868
|
+
};
|
|
869
|
+
async function runReroute(reason, fix, legIndex) {
|
|
870
|
+
if (!provider) return;
|
|
871
|
+
const token = ++rerouteToken;
|
|
872
|
+
inflight = true;
|
|
873
|
+
rerouteReason = reason;
|
|
874
|
+
lastRerouteAt = fix.timestamp;
|
|
875
|
+
setStatus("rerouting");
|
|
876
|
+
try {
|
|
877
|
+
const next = await provider.directions(rerouteRequest(fix, legIndex));
|
|
878
|
+
inflight = false;
|
|
879
|
+
if (token !== rerouteToken || status !== "rerouting") return;
|
|
880
|
+
rerouteAttempts = 0;
|
|
881
|
+
applyRoute(next, 0);
|
|
882
|
+
emit("reroute", { reason, response: next });
|
|
883
|
+
setStatus("navigating");
|
|
884
|
+
} catch (error) {
|
|
885
|
+
inflight = false;
|
|
886
|
+
if (token !== rerouteToken) return;
|
|
887
|
+
rerouteAttempts += 1;
|
|
888
|
+
if (status === "rerouting") setStatus("off_route");
|
|
889
|
+
emit("rerouteFailed", {
|
|
890
|
+
error,
|
|
891
|
+
attempts: rerouteAttempts,
|
|
892
|
+
final: rerouteAttempts >= rs.th.rerouteMaxFailures
|
|
893
|
+
});
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
const maybeAutoReroute = (fix, legIndex) => {
|
|
897
|
+
if (rerouteMode !== "auto" || !provider || inflight) return;
|
|
898
|
+
if (rerouteAttempts >= rs.th.rerouteMaxFailures) return;
|
|
899
|
+
if (lastRerouteAt !== null && fix.timestamp - lastRerouteAt < rs.th.rerouteCooldown_s * 1e3) {
|
|
900
|
+
return;
|
|
901
|
+
}
|
|
902
|
+
void runReroute("off_route", fix, legIndex);
|
|
903
|
+
};
|
|
904
|
+
const segmentBearing2 = (shapeIndex) => {
|
|
905
|
+
const a = rs.index.coords[shapeIndex];
|
|
906
|
+
const b = rs.index.coords[shapeIndex + 1];
|
|
907
|
+
return a && b ? bearingDeg(a, b) : 0;
|
|
908
|
+
};
|
|
909
|
+
function update(fix) {
|
|
910
|
+
if (status === "arrived" || status === "stopped") return;
|
|
911
|
+
const accuracy = fix.accuracy_m ?? DEFAULT_ACCURACY_M;
|
|
912
|
+
if (accuracy > rs.th.maxAccuracy_m) return;
|
|
913
|
+
const prev = last;
|
|
914
|
+
if (prev && fix.timestamp <= prev.fix.timestamp) return;
|
|
915
|
+
if (status === "idle") setStatus("navigating");
|
|
916
|
+
const dt_s = prev ? (fix.timestamp - prev.fix.timestamp) / 1e3 : 0;
|
|
917
|
+
const window_m = Math.min(WINDOW_MAX_M, WINDOW_BASE_M + WINDOW_SPEED_MPS * dt_s);
|
|
918
|
+
const here = [fix.lng, fix.lat];
|
|
919
|
+
const anchorShapeIndex = prev ? prev.shapeIndex : null;
|
|
920
|
+
const snap = snapToRoute(rs.index, here, {
|
|
921
|
+
fromShapeIndex: anchorShapeIndex,
|
|
922
|
+
window_m,
|
|
923
|
+
heading: fix.heading
|
|
924
|
+
});
|
|
925
|
+
if (!snap) return;
|
|
926
|
+
let along_m = snap.along_m;
|
|
927
|
+
let skippedViaStep = null;
|
|
928
|
+
const currentLeg = prev?.legIndex ?? 0;
|
|
929
|
+
const nextLegBegin = rs.index.legBegin_m[currentLeg + 1];
|
|
930
|
+
const nextVia = rs.response.waypoints[currentLeg + 1];
|
|
931
|
+
if (nextLegBegin !== void 0 && nextVia && currentLeg + 1 < rs.route.legs.length && along_m < nextLegBegin && haversineM(here, nextVia.snapped) <= rs.th.arrive_m) {
|
|
932
|
+
along_m = nextLegBegin;
|
|
933
|
+
skippedViaStep = rs.index.steps.find((s) => s.legIndex === currentLeg && s.step.kind === "arrive") ?? null;
|
|
934
|
+
}
|
|
935
|
+
const threshold = Math.max(rs.th.offRoute_m, 1.5 * accuracy);
|
|
936
|
+
const perpendicularOk = snap.distance_m <= threshold;
|
|
937
|
+
if (perpendicularOk && prev && along_m < maxAlong_m - rs.th.offRoute_m) backCount += 1;
|
|
938
|
+
else backCount = 0;
|
|
939
|
+
const onRoute = perpendicularOk && backCount < rs.th.offRouteFixes;
|
|
940
|
+
if (onRoute) {
|
|
941
|
+
offCount = 0;
|
|
942
|
+
offSince = null;
|
|
943
|
+
maxAlong_m = prev ? Math.max(maxAlong_m, along_m) : along_m;
|
|
944
|
+
if (status === "off_route" || status === "rerouting" && rerouteReason === "off_route") {
|
|
945
|
+
rerouteAttempts = 0;
|
|
946
|
+
setStatus("navigating");
|
|
947
|
+
}
|
|
948
|
+
} else {
|
|
949
|
+
offCount += 1;
|
|
950
|
+
offSince ?? (offSince = fix.timestamp);
|
|
951
|
+
if (status === "navigating" && offCount >= rs.th.offRouteFixes && fix.timestamp - offSince >= rs.th.offRouteSeconds * 1e3) {
|
|
952
|
+
setStatus("off_route");
|
|
953
|
+
emit("offRoute", { distance_m: snap.distance_m, fix });
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
const displayAlong_m = Math.max(along_m, maxAlong_m);
|
|
957
|
+
const at = progressAt(rs.index, displayAlong_m);
|
|
958
|
+
const rawFlat = rs.index.steps[at.stepIndex];
|
|
959
|
+
if (!rawFlat) return;
|
|
960
|
+
const rawNextFlat = rs.index.steps[at.stepIndex + 1];
|
|
961
|
+
const moving = (fix.speed_mps ?? 0) > MOVING_SPEED_MPS;
|
|
962
|
+
const bearing = moving && typeof fix.heading === "number" && Number.isFinite(fix.heading) ? fix.heading : segmentBearing2(snap.shapeIndex);
|
|
963
|
+
const end = rs.index.coords[rs.index.coords.length - 1];
|
|
964
|
+
const lastLeg = at.legIndex === rs.route.legs.length - 1;
|
|
965
|
+
const nearEnd = end !== void 0 && lastLeg && haversineM(here, end) <= rs.th.arrive_m && at.remaining_m <= ARRIVE_NEAR_REMAINING_M;
|
|
966
|
+
const arrivingNow = status === "navigating" && (at.remaining_m <= rs.th.arrive_m || nearEnd);
|
|
967
|
+
const rawStepChanged = prev === null || prev.stepIndex !== at.stepIndex;
|
|
968
|
+
const announceProgress = {
|
|
969
|
+
status,
|
|
970
|
+
route: rs.route,
|
|
971
|
+
routeIndex: rs.routeIndex,
|
|
972
|
+
legIndex: at.legIndex,
|
|
973
|
+
stepIndex: at.stepIndex,
|
|
974
|
+
step: rawFlat.step,
|
|
975
|
+
nextStep: rawNextFlat ? rawNextFlat.step : null,
|
|
976
|
+
snapped: snap.point,
|
|
977
|
+
bearing,
|
|
978
|
+
shapeIndex: snap.shapeIndex,
|
|
979
|
+
traveled_m: displayAlong_m,
|
|
980
|
+
remaining_m: at.remaining_m,
|
|
981
|
+
remaining_s: at.remaining_s,
|
|
982
|
+
distanceToStep_m: at.distanceToStep_m,
|
|
983
|
+
offRoute_m: snap.distance_m,
|
|
984
|
+
fix
|
|
985
|
+
};
|
|
986
|
+
const finalIndex = rs.index.steps.length - 1;
|
|
987
|
+
const finalFlat = rs.index.steps[finalIndex];
|
|
988
|
+
const effectiveAt = arrivingNow && finalFlat ? {
|
|
989
|
+
stepIndex: finalIndex,
|
|
990
|
+
legIndex: finalFlat.legIndex,
|
|
991
|
+
distanceToStep_m: 0,
|
|
992
|
+
remaining_m: 0,
|
|
993
|
+
remaining_s: 0
|
|
994
|
+
} : at;
|
|
995
|
+
const flat = arrivingNow && finalFlat ? finalFlat : rawFlat;
|
|
996
|
+
const nextFlat = arrivingNow ? void 0 : rawNextFlat;
|
|
997
|
+
const stepChanged = prev === null || prev.stepIndex !== effectiveAt.stepIndex;
|
|
998
|
+
const legChanged = prev !== null && effectiveAt.legIndex > prev.legIndex;
|
|
999
|
+
progress = {
|
|
1000
|
+
status,
|
|
1001
|
+
route: rs.route,
|
|
1002
|
+
routeIndex: rs.routeIndex,
|
|
1003
|
+
legIndex: effectiveAt.legIndex,
|
|
1004
|
+
stepIndex: effectiveAt.stepIndex,
|
|
1005
|
+
step: flat.step,
|
|
1006
|
+
nextStep: nextFlat ? nextFlat.step : null,
|
|
1007
|
+
snapped: snap.point,
|
|
1008
|
+
bearing,
|
|
1009
|
+
shapeIndex: snap.shapeIndex,
|
|
1010
|
+
traveled_m: arrivingNow ? rs.index.total_m : displayAlong_m,
|
|
1011
|
+
remaining_m: effectiveAt.remaining_m,
|
|
1012
|
+
remaining_s: effectiveAt.remaining_s,
|
|
1013
|
+
distanceToStep_m: effectiveAt.distanceToStep_m,
|
|
1014
|
+
offRoute_m: snap.distance_m,
|
|
1015
|
+
fix
|
|
1016
|
+
};
|
|
1017
|
+
last = {
|
|
1018
|
+
fix,
|
|
1019
|
+
// 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.
|
|
1020
|
+
shapeIndex: onRoute ? snap.shapeIndex : anchorShapeIndex ?? snap.shapeIndex,
|
|
1021
|
+
along_m: arrivingNow ? rs.index.total_m : displayAlong_m,
|
|
1022
|
+
stepIndex: effectiveAt.stepIndex,
|
|
1023
|
+
legIndex: effectiveAt.legIndex
|
|
1024
|
+
};
|
|
1025
|
+
if (status === "navigating") {
|
|
1026
|
+
if (stepChanged && prev !== null) {
|
|
1027
|
+
emit("step", { stepIndex: effectiveAt.stepIndex, step: flat.step });
|
|
1028
|
+
}
|
|
1029
|
+
if (legChanged) {
|
|
1030
|
+
const waypoint = rs.response.waypoints[effectiveAt.legIndex];
|
|
1031
|
+
if (waypoint) emit("waypoint", { legIndex: effectiveAt.legIndex, waypoint });
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
emit("progress", progress);
|
|
1035
|
+
if (status === "navigating") {
|
|
1036
|
+
if (skippedViaStep) {
|
|
1037
|
+
const key = `via:${currentLeg}:arrive`;
|
|
1038
|
+
const text = skippedViaStep.step.verbal_pre;
|
|
1039
|
+
if (text && !announced.has(key)) {
|
|
1040
|
+
announced.add(key);
|
|
1041
|
+
const viaStepIndex = rs.index.steps.indexOf(skippedViaStep);
|
|
1042
|
+
emit("announce", { text, kind: "arrive", stepIndex: viaStepIndex, priority: 3 });
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
for (const a of planAnnouncements(announceProgress, rs.th, lang, announced, rawStepChanged)) {
|
|
1046
|
+
emit("announce", a);
|
|
1047
|
+
}
|
|
1048
|
+
if (arrivingNow) {
|
|
1049
|
+
setStatus("arrived");
|
|
1050
|
+
progress = { ...progress, status };
|
|
1051
|
+
const waypoint = rs.response.waypoints[rs.response.waypoints.length - 1];
|
|
1052
|
+
if (waypoint) emit("arrive", { waypoint, fix });
|
|
1053
|
+
}
|
|
1054
|
+
} else if (status === "off_route") {
|
|
1055
|
+
maybeAutoReroute(fix, effectiveAt.legIndex);
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
return {
|
|
1059
|
+
get status() {
|
|
1060
|
+
return status;
|
|
1061
|
+
},
|
|
1062
|
+
get progress() {
|
|
1063
|
+
return progress;
|
|
1064
|
+
},
|
|
1065
|
+
update,
|
|
1066
|
+
setRoute(response, routeIndex = 0) {
|
|
1067
|
+
rerouteToken += 1;
|
|
1068
|
+
inflight = false;
|
|
1069
|
+
rerouteAttempts = 0;
|
|
1070
|
+
lastRerouteAt = null;
|
|
1071
|
+
applyRoute(response, routeIndex);
|
|
1072
|
+
if (status === "off_route" || status === "rerouting") setStatus("navigating");
|
|
1073
|
+
},
|
|
1074
|
+
async reroute() {
|
|
1075
|
+
if (!provider) throw new Error("createNavigator: kh\xF4ng c\xF3 provider \u0111\u1EC3 t\xEDnh l\u1EA1i");
|
|
1076
|
+
if (status === "arrived" || status === "stopped") return;
|
|
1077
|
+
if (!last) throw new Error("createNavigator: ch\u01B0a c\xF3 v\u1ECB tr\xED \u0111\u1EC3 t\xEDnh l\u1EA1i");
|
|
1078
|
+
await runReroute("manual", last.fix, last.legIndex);
|
|
1079
|
+
},
|
|
1080
|
+
stop() {
|
|
1081
|
+
rerouteToken += 1;
|
|
1082
|
+
inflight = false;
|
|
1083
|
+
setStatus("stopped");
|
|
1084
|
+
},
|
|
1085
|
+
on(event, handler) {
|
|
1086
|
+
listeners[event].add(handler);
|
|
1087
|
+
},
|
|
1088
|
+
off(event, handler) {
|
|
1089
|
+
listeners[event].delete(handler);
|
|
1090
|
+
}
|
|
1091
|
+
};
|
|
1092
|
+
}
|
|
1093
|
+
var EMPTY_ROUTE_FEATURES = {
|
|
1094
|
+
type: "FeatureCollection",
|
|
1095
|
+
features: []
|
|
1096
|
+
};
|
|
1097
|
+
function decodeRoutes(response) {
|
|
1098
|
+
return response.routes.map((route) => decodePolyline6(route.geometry));
|
|
1099
|
+
}
|
|
1100
|
+
var line = (kind, index, coordinates) => ({
|
|
1101
|
+
type: "Feature",
|
|
1102
|
+
geometry: { type: "LineString", coordinates },
|
|
1103
|
+
properties: { kind, index }
|
|
1104
|
+
});
|
|
1105
|
+
function routeFeatures(coords, opts) {
|
|
1106
|
+
const progress = opts.progress ?? null;
|
|
1107
|
+
const features = [];
|
|
1108
|
+
for (const [i, c] of coords.entries()) {
|
|
1109
|
+
if (i !== opts.active) {
|
|
1110
|
+
features.push(line("alt", i, [...c]));
|
|
1111
|
+
continue;
|
|
1112
|
+
}
|
|
1113
|
+
if (progress && progress.shapeIndex < c.length - 1) {
|
|
1114
|
+
features.push(
|
|
1115
|
+
line("traveled", i, [...c.slice(0, progress.shapeIndex + 1), progress.snapped]),
|
|
1116
|
+
line("active", i, [progress.snapped, ...c.slice(progress.shapeIndex + 1)])
|
|
1117
|
+
);
|
|
1118
|
+
} else {
|
|
1119
|
+
features.push(line("active", i, [...c]));
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
if (opts.puck && progress) {
|
|
1123
|
+
features.push({
|
|
1124
|
+
type: "Feature",
|
|
1125
|
+
geometry: { type: "Point", coordinates: progress.snapped },
|
|
1126
|
+
properties: { kind: "puck", bearing: progress.bearing ?? 0 }
|
|
1127
|
+
});
|
|
1128
|
+
}
|
|
1129
|
+
return { type: "FeatureCollection", features };
|
|
1130
|
+
}
|
|
429
1131
|
|
|
430
1132
|
// src/map.tsx
|
|
431
|
-
import { useContext, useEffect as useEffect2, useMemo, useRef } from "react";
|
|
1133
|
+
import { useContext as useContext2, useEffect as useEffect2, useMemo, useRef } from "react";
|
|
432
1134
|
import {
|
|
433
|
-
|
|
434
|
-
|
|
1135
|
+
AppState,
|
|
1136
|
+
StyleSheet as StyleSheet3,
|
|
1137
|
+
View as View2
|
|
435
1138
|
} from "react-native";
|
|
436
1139
|
|
|
437
1140
|
// src/attribution.tsx
|
|
@@ -469,6 +1172,576 @@ var styles = StyleSheet.create({
|
|
|
469
1172
|
import { createContext } from "react";
|
|
470
1173
|
var MapContext = createContext(null);
|
|
471
1174
|
|
|
1175
|
+
// src/navigation/map-binding.ts
|
|
1176
|
+
var FOLLOW_ZOOM = {
|
|
1177
|
+
walk: 17,
|
|
1178
|
+
motorbike: 16.5,
|
|
1179
|
+
car: 15.5
|
|
1180
|
+
};
|
|
1181
|
+
var FOLLOW_PITCH = 45;
|
|
1182
|
+
var SESSION_EVENTS = [
|
|
1183
|
+
"status",
|
|
1184
|
+
"progress",
|
|
1185
|
+
"step",
|
|
1186
|
+
"waypoint",
|
|
1187
|
+
"offRoute",
|
|
1188
|
+
"reroute",
|
|
1189
|
+
"rerouteFailed",
|
|
1190
|
+
"announce",
|
|
1191
|
+
"arrive",
|
|
1192
|
+
"route",
|
|
1193
|
+
"positionError",
|
|
1194
|
+
"voiceUnavailable",
|
|
1195
|
+
"backgroundUnavailable",
|
|
1196
|
+
"end"
|
|
1197
|
+
];
|
|
1198
|
+
function createMapBinding(deps) {
|
|
1199
|
+
const listeners = /* @__PURE__ */ new Map();
|
|
1200
|
+
const emit = (event, e) => {
|
|
1201
|
+
for (const fn of listeners.get(event) ?? []) fn(e);
|
|
1202
|
+
};
|
|
1203
|
+
let explicit = null;
|
|
1204
|
+
let fallback = null;
|
|
1205
|
+
let attached = null;
|
|
1206
|
+
let detachFns = [];
|
|
1207
|
+
let follow = {
|
|
1208
|
+
pitch: FOLLOW_PITCH
|
|
1209
|
+
};
|
|
1210
|
+
let following = true;
|
|
1211
|
+
let lastFixTs = null;
|
|
1212
|
+
const camera = (p) => {
|
|
1213
|
+
if (!follow || !following || deps.appState.currentState !== "active") return;
|
|
1214
|
+
const dt = lastFixTs === null ? 500 : p.fix.timestamp - lastFixTs;
|
|
1215
|
+
lastFixTs = p.fix.timestamp;
|
|
1216
|
+
deps.camera.current?.easeTo({
|
|
1217
|
+
center: p.snapped,
|
|
1218
|
+
bearing: p.bearing,
|
|
1219
|
+
zoom: follow.zoom ?? FOLLOW_ZOOM[p.route.mode],
|
|
1220
|
+
pitch: follow.pitch,
|
|
1221
|
+
duration: Math.max(0, Math.min(1e3, dt)),
|
|
1222
|
+
...follow.padding ? { padding: follow.padding } : {}
|
|
1223
|
+
});
|
|
1224
|
+
};
|
|
1225
|
+
const paint = (p) => {
|
|
1226
|
+
deps.store.setProgress({ shapeIndex: p.shapeIndex, snapped: p.snapped, bearing: p.bearing });
|
|
1227
|
+
camera(p);
|
|
1228
|
+
};
|
|
1229
|
+
const detach = () => {
|
|
1230
|
+
for (const fn of detachFns) fn();
|
|
1231
|
+
detachFns = [];
|
|
1232
|
+
attached = null;
|
|
1233
|
+
lastFixTs = null;
|
|
1234
|
+
deps.store.clear();
|
|
1235
|
+
};
|
|
1236
|
+
const attachTo = (session) => {
|
|
1237
|
+
if (attached === session) return;
|
|
1238
|
+
detach();
|
|
1239
|
+
attached = session;
|
|
1240
|
+
const on = (k, fn) => {
|
|
1241
|
+
session.on(k, fn);
|
|
1242
|
+
detachFns.push(() => session.off(k, fn));
|
|
1243
|
+
};
|
|
1244
|
+
for (const name of SESSION_EVENTS) {
|
|
1245
|
+
on(name, (e) => {
|
|
1246
|
+
if (name === "progress") paint(e);
|
|
1247
|
+
else if (name === "route") {
|
|
1248
|
+
const r = e;
|
|
1249
|
+
deps.store.show(r.response, { active: r.routeIndex });
|
|
1250
|
+
}
|
|
1251
|
+
emit(name, e);
|
|
1252
|
+
});
|
|
1253
|
+
}
|
|
1254
|
+
if (session.response) deps.store.show(session.response, { active: session.routeIndex });
|
|
1255
|
+
const p = session.state;
|
|
1256
|
+
if (p) paint(p);
|
|
1257
|
+
};
|
|
1258
|
+
const current = () => explicit ?? fallback;
|
|
1259
|
+
const resolve = () => {
|
|
1260
|
+
if (explicit) return explicit;
|
|
1261
|
+
if (!fallback) fallback = deps.createDefaultSession();
|
|
1262
|
+
if (attached !== fallback) attachTo(fallback);
|
|
1263
|
+
return fallback;
|
|
1264
|
+
};
|
|
1265
|
+
const appSub = deps.appState.addEventListener("change", (s) => {
|
|
1266
|
+
if (s !== "active") return;
|
|
1267
|
+
const p = attached?.state;
|
|
1268
|
+
if (p) camera(p);
|
|
1269
|
+
});
|
|
1270
|
+
const api = {
|
|
1271
|
+
get session() {
|
|
1272
|
+
return resolve();
|
|
1273
|
+
},
|
|
1274
|
+
get following() {
|
|
1275
|
+
return following;
|
|
1276
|
+
},
|
|
1277
|
+
recenter() {
|
|
1278
|
+
if (!follow) return;
|
|
1279
|
+
following = true;
|
|
1280
|
+
emit("followChange", true);
|
|
1281
|
+
const p = attached?.state;
|
|
1282
|
+
if (p) camera(p);
|
|
1283
|
+
},
|
|
1284
|
+
start: (o) => resolve().start(o),
|
|
1285
|
+
stop: () => current()?.stop() ?? Promise.resolve(),
|
|
1286
|
+
reroute: () => current()?.reroute() ?? Promise.reject(new Error("Phi\xEAn d\u1EABn \u0111\u01B0\u1EDDng ch\u01B0a start()")),
|
|
1287
|
+
get state() {
|
|
1288
|
+
return current()?.state ?? null;
|
|
1289
|
+
},
|
|
1290
|
+
get status() {
|
|
1291
|
+
return current()?.status ?? "idle";
|
|
1292
|
+
},
|
|
1293
|
+
on(event, handler) {
|
|
1294
|
+
let set = listeners.get(event);
|
|
1295
|
+
if (!set) {
|
|
1296
|
+
set = /* @__PURE__ */ new Set();
|
|
1297
|
+
listeners.set(event, set);
|
|
1298
|
+
}
|
|
1299
|
+
set.add(handler);
|
|
1300
|
+
},
|
|
1301
|
+
off(event, handler) {
|
|
1302
|
+
listeners.get(event)?.delete(handler);
|
|
1303
|
+
}
|
|
1304
|
+
};
|
|
1305
|
+
return {
|
|
1306
|
+
api,
|
|
1307
|
+
attach(session) {
|
|
1308
|
+
explicit = session;
|
|
1309
|
+
if (session) attachTo(session);
|
|
1310
|
+
else detach();
|
|
1311
|
+
},
|
|
1312
|
+
setFollow(opt) {
|
|
1313
|
+
if (opt === false) {
|
|
1314
|
+
follow = null;
|
|
1315
|
+
following = false;
|
|
1316
|
+
return;
|
|
1317
|
+
}
|
|
1318
|
+
const o = typeof opt === "object" ? opt : {};
|
|
1319
|
+
follow = {
|
|
1320
|
+
pitch: o.pitch ?? FOLLOW_PITCH,
|
|
1321
|
+
...o.zoom !== void 0 ? { zoom: o.zoom } : {},
|
|
1322
|
+
...o.padding ? { padding: o.padding } : {}
|
|
1323
|
+
};
|
|
1324
|
+
following = true;
|
|
1325
|
+
},
|
|
1326
|
+
userGesture() {
|
|
1327
|
+
if (!following) return;
|
|
1328
|
+
following = false;
|
|
1329
|
+
emit("followChange", false);
|
|
1330
|
+
},
|
|
1331
|
+
dispose() {
|
|
1332
|
+
detach();
|
|
1333
|
+
appSub.remove();
|
|
1334
|
+
listeners.clear();
|
|
1335
|
+
}
|
|
1336
|
+
};
|
|
1337
|
+
}
|
|
1338
|
+
|
|
1339
|
+
// src/navigation/route-layers.tsx
|
|
1340
|
+
import {
|
|
1341
|
+
GeoJSONSource,
|
|
1342
|
+
Images,
|
|
1343
|
+
Layer
|
|
1344
|
+
} from "@maplibre/maplibre-react-native";
|
|
1345
|
+
import { useSyncExternalStore } from "react";
|
|
1346
|
+
|
|
1347
|
+
// src/marker.tsx
|
|
1348
|
+
import { Marker as NativeMarker } from "@maplibre/maplibre-react-native";
|
|
1349
|
+
import { useContext } from "react";
|
|
1350
|
+
import { StyleSheet as StyleSheet2, View } from "react-native";
|
|
1351
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
1352
|
+
var DEFAULT_MARKER_COLOR = "#3FB1CE";
|
|
1353
|
+
function Marker({
|
|
1354
|
+
lng,
|
|
1355
|
+
lat,
|
|
1356
|
+
color = DEFAULT_MARKER_COLOR,
|
|
1357
|
+
anchor = "center",
|
|
1358
|
+
onPress,
|
|
1359
|
+
children,
|
|
1360
|
+
testID
|
|
1361
|
+
}) {
|
|
1362
|
+
if (!useContext(MapContext)) throw new Error("useMap ph\u1EA3i \u0111\u01B0\u1EE3c g\u1ECDi b\xEAn trong <MapsLibVNMap>");
|
|
1363
|
+
const pin = children ?? /* @__PURE__ */ jsx2(View, { style: [styles2.pin, { backgroundColor: color }], testID: "mapslibvn-marker-pin" });
|
|
1364
|
+
return /* @__PURE__ */ jsx2(
|
|
1365
|
+
NativeMarker,
|
|
1366
|
+
{
|
|
1367
|
+
lngLat: [lng, lat],
|
|
1368
|
+
anchor,
|
|
1369
|
+
...onPress ? { onPress: () => onPress() } : {},
|
|
1370
|
+
...testID ? { testID } : {},
|
|
1371
|
+
children: pin
|
|
1372
|
+
}
|
|
1373
|
+
);
|
|
1374
|
+
}
|
|
1375
|
+
var styles2 = StyleSheet2.create({
|
|
1376
|
+
pin: {
|
|
1377
|
+
width: 22,
|
|
1378
|
+
height: 22,
|
|
1379
|
+
borderRadius: 11,
|
|
1380
|
+
borderWidth: 3,
|
|
1381
|
+
borderColor: "#ffffff"
|
|
1382
|
+
}
|
|
1383
|
+
});
|
|
1384
|
+
|
|
1385
|
+
// src/navigation/puck-image.ts
|
|
1386
|
+
var PUCK_IMAGE_KEY = "mapslibvn-puck";
|
|
1387
|
+
var PUCK_PNG_DATA_URI = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEIAAABCCAYAAADjVADoAAADUUlEQVR42u2bv4rVQBTG9xH2EbbwAS6+gPcF7BdsgpWNsJXbBrEUAjaWwcZqLWy0UMhi48JiJStWjqCNFoqghdWREzJxcnLyZ86dyZ1kcuBAyN1NZr58c+Z3D7kHB2ussUaIAQBbzFUIgAIzdhE28D82MQuRG0LksYpwBO04it0Ncbqiww3xuQIAMj3ri6tvZRqRxSLCIQD81LO+9/htmUbgZ4cxCJHqGX/9/huuHT8tE4+NSKNyw4Mn72oh8DgaV5hu+PXnL1y/fVYLgcd4LgpXAICq98kXH2sRdOI5I9RSRUjMWd64+7wlBJ4jkSzaDc/OP7VE0ImfLdYV1A237r/uFAI/W6wrTDcgPHWJoJMAllqKCFtzVghPQ0IQwIJFNG6qxksLoIaSAFaxKDeYADWUBLDm7QrTDRSghpIBrGKuImwajQYGoIaSANY823m08cIB1FAygJXPTYRG46UPoIaSANa8GjfUDX0ANZQMYOWzdMMYgLIErHm4wmzDjQUoAWBloYvQaLzYAJQlYIXduDEbL7YAJQCsdBZusAUoAWCF6QrqBglACQArDVEItStACQBLhSZC4gqgBICVBOuGXQBKAFgqSDe4ACgBYCXBucEFQAkAS+1bhEbjxSVAWQLWfhs3ZuPFNUAJAKsIwg2uAUoAWPtxBXWDD4ASAFYxtQgbOgIfACUArGnbebTxIgWom6cv6zdm8NgRYOVTidB6/8kWoPBJMhMoz9k6iwGsaRo31A02AIUF7tHZe67INYou/o1N4WUAK5/cDWMBCre7PgE4QcZuxwxg+XUFbcONAag7D99w8FM/Of1Sesd7l+X/4jUEgJX5EqHReBkCKFy7jGV1QyXjnljluIzeRy/BvlrEAJafxg1tvHQBFBa7V5dfOAHwO8nJmMFVop/Q7zEYeG2uoHYAVurdDRSgcCAM4EAFXskO904ovOn70wfB3N+tK6gbTIDq2Qlyl8jL1RG6w3QAVupSCMUBFFZrps2e+azYXB3BMejdi+ET5erGCb0yPoUPn3+I1r/j5dqoIzgmHBsTiXM3uFz/jrtkRc84lXM3+Fj/PuuIE1cQN3hf/z7riNgVRuNl8vXvsY5sJRdJl/SyZ7XMl/3zhzXWWGONNSaKf5vNEuTO/lstAAAAAElFTkSuQmCC";
|
|
1388
|
+
|
|
1389
|
+
// src/navigation/route-layers.tsx
|
|
1390
|
+
import { Fragment, jsx as jsx3, jsxs } from "react/jsx-runtime";
|
|
1391
|
+
var ROUTE_SOURCE_ID = "mapslibvn-route";
|
|
1392
|
+
var ROUTE_LAYER_IDS = {
|
|
1393
|
+
alt: "mapslibvn-route-alt",
|
|
1394
|
+
casing: "mapslibvn-route-casing",
|
|
1395
|
+
line: "mapslibvn-route-line",
|
|
1396
|
+
traveled: "mapslibvn-route-traveled",
|
|
1397
|
+
puck: "mapslibvn-route-puck"
|
|
1398
|
+
};
|
|
1399
|
+
var ROUTE_COLOR = "#2458a6";
|
|
1400
|
+
var ALT_ROUTE_COLOR = "#9ca8ba";
|
|
1401
|
+
var DESTINATION_COLOR = "#d92d20";
|
|
1402
|
+
var kindIs = (kind) => ["==", ["get", "kind"], kind];
|
|
1403
|
+
var ROUND = {
|
|
1404
|
+
"line-join": "round",
|
|
1405
|
+
"line-cap": "round"
|
|
1406
|
+
};
|
|
1407
|
+
var PUCK_LAYOUT = {
|
|
1408
|
+
"icon-image": PUCK_IMAGE_KEY,
|
|
1409
|
+
"icon-rotate": ["get", "bearing"],
|
|
1410
|
+
"icon-rotation-alignment": "map",
|
|
1411
|
+
"icon-pitch-alignment": "map",
|
|
1412
|
+
"icon-allow-overlap": true,
|
|
1413
|
+
"icon-ignore-placement": true,
|
|
1414
|
+
"icon-size": 0.5
|
|
1415
|
+
};
|
|
1416
|
+
function RouteLayers({ store, routeStyle, beforeId, onRouteClick }) {
|
|
1417
|
+
const snap = useSyncExternalStore(store.subscribe, store.getSnapshot, store.getSnapshot);
|
|
1418
|
+
if (!snap.response) return null;
|
|
1419
|
+
const color = routeStyle?.color ?? ROUTE_COLOR;
|
|
1420
|
+
const altColor = routeStyle?.altColor ?? ALT_ROUTE_COLOR;
|
|
1421
|
+
const casingColor = routeStyle?.casingColor ?? "#ffffff";
|
|
1422
|
+
const traveledOpacity = routeStyle?.traveledOpacity ?? 0.35;
|
|
1423
|
+
const before = beforeId ? { beforeId } : {};
|
|
1424
|
+
const onPress = (e) => {
|
|
1425
|
+
const alt = e.nativeEvent.features.find((f) => f.properties?.kind === "alt");
|
|
1426
|
+
const index = alt?.properties?.index;
|
|
1427
|
+
if (typeof index === "number") onRouteClick?.(index);
|
|
1428
|
+
};
|
|
1429
|
+
const waypoints = snap.response.waypoints;
|
|
1430
|
+
const last = waypoints.length - 1;
|
|
1431
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1432
|
+
/* @__PURE__ */ jsx3(Images, { images: { [PUCK_IMAGE_KEY]: { source: { uri: PUCK_PNG_DATA_URI } } } }),
|
|
1433
|
+
/* @__PURE__ */ jsxs(
|
|
1434
|
+
GeoJSONSource,
|
|
1435
|
+
{
|
|
1436
|
+
id: ROUTE_SOURCE_ID,
|
|
1437
|
+
data: snap.features,
|
|
1438
|
+
onPress,
|
|
1439
|
+
children: [
|
|
1440
|
+
/* @__PURE__ */ jsx3(
|
|
1441
|
+
Layer,
|
|
1442
|
+
{
|
|
1443
|
+
type: "line",
|
|
1444
|
+
id: ROUTE_LAYER_IDS.alt,
|
|
1445
|
+
source: ROUTE_SOURCE_ID,
|
|
1446
|
+
filter: kindIs("alt"),
|
|
1447
|
+
layout: ROUND,
|
|
1448
|
+
paint: { "line-color": altColor, "line-width": 5 },
|
|
1449
|
+
...before
|
|
1450
|
+
}
|
|
1451
|
+
),
|
|
1452
|
+
/* @__PURE__ */ jsx3(
|
|
1453
|
+
Layer,
|
|
1454
|
+
{
|
|
1455
|
+
type: "line",
|
|
1456
|
+
id: ROUTE_LAYER_IDS.casing,
|
|
1457
|
+
source: ROUTE_SOURCE_ID,
|
|
1458
|
+
filter: kindIs("active"),
|
|
1459
|
+
layout: ROUND,
|
|
1460
|
+
paint: { "line-color": casingColor, "line-width": 9 },
|
|
1461
|
+
...before
|
|
1462
|
+
}
|
|
1463
|
+
),
|
|
1464
|
+
/* @__PURE__ */ jsx3(
|
|
1465
|
+
Layer,
|
|
1466
|
+
{
|
|
1467
|
+
type: "line",
|
|
1468
|
+
id: ROUTE_LAYER_IDS.line,
|
|
1469
|
+
source: ROUTE_SOURCE_ID,
|
|
1470
|
+
filter: kindIs("active"),
|
|
1471
|
+
layout: ROUND,
|
|
1472
|
+
paint: { "line-color": color, "line-width": 6 },
|
|
1473
|
+
...before
|
|
1474
|
+
}
|
|
1475
|
+
),
|
|
1476
|
+
/* @__PURE__ */ jsx3(
|
|
1477
|
+
Layer,
|
|
1478
|
+
{
|
|
1479
|
+
type: "line",
|
|
1480
|
+
id: ROUTE_LAYER_IDS.traveled,
|
|
1481
|
+
source: ROUTE_SOURCE_ID,
|
|
1482
|
+
filter: kindIs("traveled"),
|
|
1483
|
+
layout: ROUND,
|
|
1484
|
+
paint: { "line-color": color, "line-width": 6, "line-opacity": traveledOpacity },
|
|
1485
|
+
...before
|
|
1486
|
+
}
|
|
1487
|
+
),
|
|
1488
|
+
snap.puck ? /* @__PURE__ */ jsx3(
|
|
1489
|
+
Layer,
|
|
1490
|
+
{
|
|
1491
|
+
type: "symbol",
|
|
1492
|
+
id: ROUTE_LAYER_IDS.puck,
|
|
1493
|
+
source: ROUTE_SOURCE_ID,
|
|
1494
|
+
filter: kindIs("puck"),
|
|
1495
|
+
layout: PUCK_LAYOUT
|
|
1496
|
+
}
|
|
1497
|
+
) : null
|
|
1498
|
+
]
|
|
1499
|
+
}
|
|
1500
|
+
),
|
|
1501
|
+
waypoints.map(
|
|
1502
|
+
(w, i) => i === 0 ? null : /* @__PURE__ */ jsx3(
|
|
1503
|
+
Marker,
|
|
1504
|
+
{
|
|
1505
|
+
lng: w.snapped[0],
|
|
1506
|
+
lat: w.snapped[1],
|
|
1507
|
+
color: i === last ? DESTINATION_COLOR : ALT_ROUTE_COLOR,
|
|
1508
|
+
testID: "mapslibvn-route-marker"
|
|
1509
|
+
},
|
|
1510
|
+
`${i}-${w.snapped[0]}-${w.snapped[1]}`
|
|
1511
|
+
)
|
|
1512
|
+
)
|
|
1513
|
+
] });
|
|
1514
|
+
}
|
|
1515
|
+
|
|
1516
|
+
// src/navigation/routes-store.ts
|
|
1517
|
+
function createRoutesStore() {
|
|
1518
|
+
let coords = [];
|
|
1519
|
+
let snapshot = {
|
|
1520
|
+
response: null,
|
|
1521
|
+
active: 0,
|
|
1522
|
+
progress: null,
|
|
1523
|
+
puck: true,
|
|
1524
|
+
features: EMPTY_ROUTE_FEATURES
|
|
1525
|
+
};
|
|
1526
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
1527
|
+
const set = (patch) => {
|
|
1528
|
+
const next = { ...snapshot, ...patch };
|
|
1529
|
+
next.features = next.response ? routeFeatures(coords, { active: next.active, progress: next.progress, puck: next.puck }) : EMPTY_ROUTE_FEATURES;
|
|
1530
|
+
snapshot = next;
|
|
1531
|
+
for (const fn of listeners) fn();
|
|
1532
|
+
};
|
|
1533
|
+
return {
|
|
1534
|
+
getSnapshot: () => snapshot,
|
|
1535
|
+
subscribe(onChange) {
|
|
1536
|
+
listeners.add(onChange);
|
|
1537
|
+
return () => {
|
|
1538
|
+
listeners.delete(onChange);
|
|
1539
|
+
};
|
|
1540
|
+
},
|
|
1541
|
+
show(response, opts = {}) {
|
|
1542
|
+
coords = decodeRoutes(response);
|
|
1543
|
+
set({ response, active: opts.active ?? 0, progress: null });
|
|
1544
|
+
},
|
|
1545
|
+
setActive(index) {
|
|
1546
|
+
set({ active: index, progress: null });
|
|
1547
|
+
},
|
|
1548
|
+
setProgress(cut) {
|
|
1549
|
+
set({ progress: cut });
|
|
1550
|
+
},
|
|
1551
|
+
setPuck(on) {
|
|
1552
|
+
if (on !== snapshot.puck) set({ puck: on });
|
|
1553
|
+
},
|
|
1554
|
+
clear() {
|
|
1555
|
+
coords = [];
|
|
1556
|
+
set({ response: null, progress: null });
|
|
1557
|
+
}
|
|
1558
|
+
};
|
|
1559
|
+
}
|
|
1560
|
+
|
|
1561
|
+
// src/navigation/session.ts
|
|
1562
|
+
var MISSING_SOURCE_MESSAGE = "Phi\xEAn d\u1EABn \u0111\u01B0\u1EDDng thi\u1EBFu ngu\u1ED3n v\u1ECB tr\xED: truy\u1EC1n source (v\xED d\u1EE5 expoLocationSource() t\u1EEB @mapslibvn/react-native/expo)";
|
|
1563
|
+
var FORWARDED = [
|
|
1564
|
+
"progress",
|
|
1565
|
+
"step",
|
|
1566
|
+
"waypoint",
|
|
1567
|
+
"offRoute",
|
|
1568
|
+
"reroute",
|
|
1569
|
+
"rerouteFailed",
|
|
1570
|
+
"announce",
|
|
1571
|
+
"arrive"
|
|
1572
|
+
];
|
|
1573
|
+
function createNavigationSession(opts) {
|
|
1574
|
+
const listeners = /* @__PURE__ */ new Map();
|
|
1575
|
+
const emit = (event, e) => {
|
|
1576
|
+
for (const fn of listeners.get(event) ?? []) fn(e);
|
|
1577
|
+
};
|
|
1578
|
+
let nav = null;
|
|
1579
|
+
let unsubscribe = null;
|
|
1580
|
+
let running = false;
|
|
1581
|
+
let ended = false;
|
|
1582
|
+
let startToken = 0;
|
|
1583
|
+
let response = null;
|
|
1584
|
+
let routeIndex = 0;
|
|
1585
|
+
let voiceOn = false;
|
|
1586
|
+
let keepOn = false;
|
|
1587
|
+
let audioOn = false;
|
|
1588
|
+
const currentStatus = () => {
|
|
1589
|
+
if (!nav) return "idle";
|
|
1590
|
+
return nav.status === "idle" ? "navigating" : nav.status;
|
|
1591
|
+
};
|
|
1592
|
+
const releaseSource = () => {
|
|
1593
|
+
unsubscribe?.();
|
|
1594
|
+
unsubscribe = null;
|
|
1595
|
+
};
|
|
1596
|
+
const releaseDevice = async () => {
|
|
1597
|
+
if (keepOn) {
|
|
1598
|
+
keepOn = false;
|
|
1599
|
+
try {
|
|
1600
|
+
await opts.keepAwake?.deactivate();
|
|
1601
|
+
} catch {
|
|
1602
|
+
}
|
|
1603
|
+
}
|
|
1604
|
+
if (audioOn) {
|
|
1605
|
+
audioOn = false;
|
|
1606
|
+
try {
|
|
1607
|
+
await opts.audio?.deactivate();
|
|
1608
|
+
} catch {
|
|
1609
|
+
}
|
|
1610
|
+
}
|
|
1611
|
+
};
|
|
1612
|
+
async function stop() {
|
|
1613
|
+
if (!running) return;
|
|
1614
|
+
running = false;
|
|
1615
|
+
startToken += 1;
|
|
1616
|
+
const previous = currentStatus();
|
|
1617
|
+
releaseSource();
|
|
1618
|
+
if (voiceOn) opts.speech?.cancel();
|
|
1619
|
+
voiceOn = false;
|
|
1620
|
+
const engine = nav;
|
|
1621
|
+
nav = null;
|
|
1622
|
+
engine?.stop();
|
|
1623
|
+
await releaseDevice();
|
|
1624
|
+
emit("status", { status: "idle", previous });
|
|
1625
|
+
if (!ended) emit("end", { reason: "stopped" });
|
|
1626
|
+
ended = false;
|
|
1627
|
+
}
|
|
1628
|
+
async function start(o) {
|
|
1629
|
+
if (running) await stop();
|
|
1630
|
+
const source = opts.source;
|
|
1631
|
+
if (!source) throw new Error(MISSING_SOURCE_MESSAGE);
|
|
1632
|
+
running = true;
|
|
1633
|
+
ended = false;
|
|
1634
|
+
startToken += 1;
|
|
1635
|
+
const token = startToken;
|
|
1636
|
+
response = o.response;
|
|
1637
|
+
routeIndex = o.routeIndex ?? 0;
|
|
1638
|
+
const lang = o.lang ?? "vi";
|
|
1639
|
+
const navOptions = {
|
|
1640
|
+
response: o.response,
|
|
1641
|
+
routeIndex,
|
|
1642
|
+
provider: opts.provider,
|
|
1643
|
+
reroute: o.reroute ?? "auto",
|
|
1644
|
+
lang
|
|
1645
|
+
};
|
|
1646
|
+
if (o.thresholds) navOptions.thresholds = o.thresholds;
|
|
1647
|
+
const engine = createNavigator(navOptions);
|
|
1648
|
+
const mode = o.response.routes[routeIndex]?.mode ?? "motorbike";
|
|
1649
|
+
voiceOn = o.voice !== false && Boolean(opts.speech);
|
|
1650
|
+
if (voiceOn && opts.speech) {
|
|
1651
|
+
if (typeof o.voice === "object") opts.speech.setOptions?.(o.voice);
|
|
1652
|
+
if (opts.audio) {
|
|
1653
|
+
audioOn = true;
|
|
1654
|
+
try {
|
|
1655
|
+
await opts.audio.activate();
|
|
1656
|
+
} catch {
|
|
1657
|
+
}
|
|
1658
|
+
}
|
|
1659
|
+
let available = true;
|
|
1660
|
+
try {
|
|
1661
|
+
available = await opts.speech.available(lang);
|
|
1662
|
+
} catch {
|
|
1663
|
+
}
|
|
1664
|
+
if (token !== startToken) return;
|
|
1665
|
+
if (!available) emit("voiceUnavailable", void 0);
|
|
1666
|
+
}
|
|
1667
|
+
keepOn = (o.keepAwake ?? true) && Boolean(opts.keepAwake);
|
|
1668
|
+
if (keepOn) {
|
|
1669
|
+
try {
|
|
1670
|
+
await opts.keepAwake?.activate();
|
|
1671
|
+
} catch {
|
|
1672
|
+
}
|
|
1673
|
+
}
|
|
1674
|
+
if (token !== startToken) return;
|
|
1675
|
+
nav = engine;
|
|
1676
|
+
for (const name of FORWARDED) {
|
|
1677
|
+
engine.on(name, (e) => emit(name, e));
|
|
1678
|
+
}
|
|
1679
|
+
engine.on("status", (e) => {
|
|
1680
|
+
if (e.status === "stopped") return;
|
|
1681
|
+
if (e.status === "navigating" && e.previous === "idle") return;
|
|
1682
|
+
emit("status", e);
|
|
1683
|
+
});
|
|
1684
|
+
engine.on("announce", (a) => {
|
|
1685
|
+
if (voiceOn) opts.speech?.speak(a.text, a.priority, lang);
|
|
1686
|
+
});
|
|
1687
|
+
engine.on("reroute", (e) => {
|
|
1688
|
+
response = e.response;
|
|
1689
|
+
routeIndex = 0;
|
|
1690
|
+
emit("route", { response: e.response, routeIndex: 0 });
|
|
1691
|
+
});
|
|
1692
|
+
engine.on("arrive", () => {
|
|
1693
|
+
ended = true;
|
|
1694
|
+
releaseSource();
|
|
1695
|
+
void releaseDevice();
|
|
1696
|
+
emit("end", { reason: "arrived" });
|
|
1697
|
+
});
|
|
1698
|
+
emit("route", { response: o.response, routeIndex });
|
|
1699
|
+
emit("status", { status: "navigating", previous: "idle" });
|
|
1700
|
+
source.setMode?.(mode);
|
|
1701
|
+
source.onBackgroundUnavailable?.((e) => emit("backgroundUnavailable", e));
|
|
1702
|
+
unsubscribe = source.subscribe(
|
|
1703
|
+
(fix) => engine.update(fix),
|
|
1704
|
+
(error) => emit("positionError", error)
|
|
1705
|
+
);
|
|
1706
|
+
}
|
|
1707
|
+
return {
|
|
1708
|
+
start,
|
|
1709
|
+
stop,
|
|
1710
|
+
reroute() {
|
|
1711
|
+
return nav ? nav.reroute() : Promise.reject(new Error("Phi\xEAn d\u1EABn \u0111\u01B0\u1EDDng ch\u01B0a start()"));
|
|
1712
|
+
},
|
|
1713
|
+
setRoute(next, index = 0) {
|
|
1714
|
+
response = next;
|
|
1715
|
+
routeIndex = index;
|
|
1716
|
+
nav?.setRoute(next, index);
|
|
1717
|
+
emit("route", { response: next, routeIndex: index });
|
|
1718
|
+
},
|
|
1719
|
+
get status() {
|
|
1720
|
+
return currentStatus();
|
|
1721
|
+
},
|
|
1722
|
+
get state() {
|
|
1723
|
+
return nav?.progress ?? null;
|
|
1724
|
+
},
|
|
1725
|
+
get response() {
|
|
1726
|
+
return response;
|
|
1727
|
+
},
|
|
1728
|
+
get routeIndex() {
|
|
1729
|
+
return routeIndex;
|
|
1730
|
+
},
|
|
1731
|
+
on(event, handler) {
|
|
1732
|
+
let set = listeners.get(event);
|
|
1733
|
+
if (!set) {
|
|
1734
|
+
set = /* @__PURE__ */ new Set();
|
|
1735
|
+
listeners.set(event, set);
|
|
1736
|
+
}
|
|
1737
|
+
set.add(handler);
|
|
1738
|
+
},
|
|
1739
|
+
off(event, handler) {
|
|
1740
|
+
listeners.get(event)?.delete(handler);
|
|
1741
|
+
}
|
|
1742
|
+
};
|
|
1743
|
+
}
|
|
1744
|
+
|
|
472
1745
|
// src/to-poi-feature.ts
|
|
473
1746
|
function toPoiFeature(feature) {
|
|
474
1747
|
if (!feature || feature.geometry.type !== "Point") return null;
|
|
@@ -538,7 +1811,7 @@ function useResolvedStyle(places, options, doFetch = globalThis.fetch) {
|
|
|
538
1811
|
}
|
|
539
1812
|
|
|
540
1813
|
// src/map.tsx
|
|
541
|
-
import { jsx as
|
|
1814
|
+
import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
542
1815
|
var DEFAULT_CENTER = [106.7, 10.776];
|
|
543
1816
|
var DEFAULT_ZOOM = 12;
|
|
544
1817
|
function MapsLibVNMap({
|
|
@@ -553,6 +1826,13 @@ function MapsLibVNMap({
|
|
|
553
1826
|
compactAttribution = false,
|
|
554
1827
|
bundleId,
|
|
555
1828
|
containerStyle,
|
|
1829
|
+
navigation,
|
|
1830
|
+
sessionOptions,
|
|
1831
|
+
follow = true,
|
|
1832
|
+
puck = true,
|
|
1833
|
+
routeStyle,
|
|
1834
|
+
routeBeforeLayerId,
|
|
1835
|
+
onRouteClick,
|
|
556
1836
|
onLoad,
|
|
557
1837
|
onPoiClick,
|
|
558
1838
|
onError,
|
|
@@ -571,8 +1851,31 @@ function MapsLibVNMap({
|
|
|
571
1851
|
);
|
|
572
1852
|
const native = useRef(null);
|
|
573
1853
|
const camera = useRef(null);
|
|
574
|
-
const handlers = useRef({ onLoad, onPoiClick, onError });
|
|
575
|
-
handlers.current = { onLoad, onPoiClick, onError };
|
|
1854
|
+
const handlers = useRef({ onLoad, onPoiClick, onError, onRouteClick });
|
|
1855
|
+
handlers.current = { onLoad, onPoiClick, onError, onRouteClick };
|
|
1856
|
+
const sessionOptionsRef = useRef(sessionOptions);
|
|
1857
|
+
sessionOptionsRef.current = sessionOptions;
|
|
1858
|
+
const store = useMemo(() => createRoutesStore(), []);
|
|
1859
|
+
const binding = useMemo(
|
|
1860
|
+
() => createMapBinding({
|
|
1861
|
+
camera,
|
|
1862
|
+
store,
|
|
1863
|
+
appState: AppState,
|
|
1864
|
+
createDefaultSession: () => createNavigationSession({ provider: places, ...sessionOptionsRef.current })
|
|
1865
|
+
}),
|
|
1866
|
+
[places, store]
|
|
1867
|
+
);
|
|
1868
|
+
useEffect2(() => () => binding.dispose(), [binding]);
|
|
1869
|
+
useEffect2(() => {
|
|
1870
|
+
binding.attach(navigation ?? null);
|
|
1871
|
+
}, [binding, navigation]);
|
|
1872
|
+
const followKey = JSON.stringify(follow);
|
|
1873
|
+
useEffect2(() => {
|
|
1874
|
+
binding.setFollow(JSON.parse(followKey));
|
|
1875
|
+
}, [binding, followKey]);
|
|
1876
|
+
useEffect2(() => {
|
|
1877
|
+
store.setPuck(puck);
|
|
1878
|
+
}, [store, puck]);
|
|
576
1879
|
const handle = useMemo(
|
|
577
1880
|
() => ({
|
|
578
1881
|
native,
|
|
@@ -590,9 +1893,15 @@ function MapsLibVNMap({
|
|
|
590
1893
|
const b = await native.current?.getBounds();
|
|
591
1894
|
if (!b) throw new Error("B\u1EA3n \u0111\u1ED3 ch\u01B0a s\u1EB5n s\xE0ng");
|
|
592
1895
|
return b;
|
|
593
|
-
}
|
|
1896
|
+
},
|
|
1897
|
+
routes: {
|
|
1898
|
+
show: (response, opts) => store.show(response, opts ?? {}),
|
|
1899
|
+
setActive: (index) => store.setActive(index),
|
|
1900
|
+
clear: () => store.clear()
|
|
1901
|
+
},
|
|
1902
|
+
navigation: binding.api
|
|
594
1903
|
}),
|
|
595
|
-
[places]
|
|
1904
|
+
[places, store, binding]
|
|
596
1905
|
);
|
|
597
1906
|
const resolved = useResolvedStyle(places, { style, lang, poiLayer });
|
|
598
1907
|
useEffect2(() => {
|
|
@@ -608,17 +1917,22 @@ function MapsLibVNMap({
|
|
|
608
1917
|
const poi = toPoiFeature(features?.[0]);
|
|
609
1918
|
if (poi) handlers.current.onPoiClick?.(poi);
|
|
610
1919
|
};
|
|
611
|
-
|
|
612
|
-
|
|
1920
|
+
const onRegionWillChange = (e) => {
|
|
1921
|
+
if (e.nativeEvent.userInteraction) binding.userGesture();
|
|
1922
|
+
};
|
|
1923
|
+
const beforeId = routeBeforeLayerId === void 0 ? isTheme(style) ? FIRST_SYMBOL_LAYER_ID[style] : null : routeBeforeLayerId;
|
|
1924
|
+
return /* @__PURE__ */ jsxs2(View2, { style: [styles3.container, containerStyle], ...testID ? { testID } : {}, children: [
|
|
1925
|
+
resolved.status === "ready" ? /* @__PURE__ */ jsxs2(
|
|
613
1926
|
NativeMap,
|
|
614
1927
|
{
|
|
615
1928
|
ref: native,
|
|
616
|
-
style:
|
|
1929
|
+
style: styles3.map,
|
|
617
1930
|
mapStyle: resolved.mapStyle,
|
|
618
1931
|
attribution: true,
|
|
619
1932
|
attributionPosition: { bottom: 8, right: 8 },
|
|
620
1933
|
logo: false,
|
|
621
1934
|
onPress,
|
|
1935
|
+
onRegionWillChange,
|
|
622
1936
|
onDidFinishLoadingStyle: () => {
|
|
623
1937
|
if (loadedFor.current === mapKey) return;
|
|
624
1938
|
loadedFor.current = mapKey;
|
|
@@ -626,13 +1940,24 @@ function MapsLibVNMap({
|
|
|
626
1940
|
},
|
|
627
1941
|
onDidFailLoadingMap: () => handlers.current.onError?.(new Error("Kh\xF4ng t\u1EA3i \u0111\u01B0\u1EE3c b\u1EA3n \u0111\u1ED3")),
|
|
628
1942
|
children: [
|
|
629
|
-
/* @__PURE__ */
|
|
630
|
-
/* @__PURE__ */
|
|
1943
|
+
/* @__PURE__ */ jsx4(Camera, { ref: camera, initialViewState: { center, zoom } }),
|
|
1944
|
+
/* @__PURE__ */ jsxs2(MapContext.Provider, { value: handle, children: [
|
|
1945
|
+
/* @__PURE__ */ jsx4(
|
|
1946
|
+
RouteLayers,
|
|
1947
|
+
{
|
|
1948
|
+
store,
|
|
1949
|
+
routeStyle,
|
|
1950
|
+
beforeId,
|
|
1951
|
+
onRouteClick: (index) => handlers.current.onRouteClick?.(index)
|
|
1952
|
+
}
|
|
1953
|
+
),
|
|
1954
|
+
children
|
|
1955
|
+
] })
|
|
631
1956
|
]
|
|
632
1957
|
},
|
|
633
1958
|
mapKey
|
|
634
1959
|
) : null,
|
|
635
|
-
/* @__PURE__ */
|
|
1960
|
+
/* @__PURE__ */ jsx4(
|
|
636
1961
|
Attribution,
|
|
637
1962
|
{
|
|
638
1963
|
compact: compactAttribution,
|
|
@@ -644,56 +1969,19 @@ function MapsLibVNMap({
|
|
|
644
1969
|
] });
|
|
645
1970
|
}
|
|
646
1971
|
function useMap() {
|
|
647
|
-
const map =
|
|
1972
|
+
const map = useContext2(MapContext);
|
|
648
1973
|
if (!map) throw new Error("useMap ph\u1EA3i \u0111\u01B0\u1EE3c g\u1ECDi b\xEAn trong <MapsLibVNMap>");
|
|
649
1974
|
return map;
|
|
650
1975
|
}
|
|
651
|
-
var
|
|
1976
|
+
var styles3 = StyleSheet3.create({
|
|
652
1977
|
container: { flex: 1, position: "relative" },
|
|
653
1978
|
map: { flex: 1 }
|
|
654
1979
|
});
|
|
655
1980
|
|
|
656
|
-
// src/marker.tsx
|
|
657
|
-
import { Marker as NativeMarker } from "@maplibre/maplibre-react-native";
|
|
658
|
-
import { StyleSheet as StyleSheet3, View as View2 } from "react-native";
|
|
659
|
-
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
660
|
-
var DEFAULT_MARKER_COLOR = "#3FB1CE";
|
|
661
|
-
function Marker({
|
|
662
|
-
lng,
|
|
663
|
-
lat,
|
|
664
|
-
color = DEFAULT_MARKER_COLOR,
|
|
665
|
-
anchor = "center",
|
|
666
|
-
onPress,
|
|
667
|
-
children,
|
|
668
|
-
testID
|
|
669
|
-
}) {
|
|
670
|
-
useMap();
|
|
671
|
-
const pin = children ?? /* @__PURE__ */ jsx3(View2, { style: [styles3.pin, { backgroundColor: color }], testID: "mapslibvn-marker-pin" });
|
|
672
|
-
return /* @__PURE__ */ jsx3(
|
|
673
|
-
NativeMarker,
|
|
674
|
-
{
|
|
675
|
-
lngLat: [lng, lat],
|
|
676
|
-
anchor,
|
|
677
|
-
...onPress ? { onPress: () => onPress() } : {},
|
|
678
|
-
...testID ? { testID } : {},
|
|
679
|
-
children: pin
|
|
680
|
-
}
|
|
681
|
-
);
|
|
682
|
-
}
|
|
683
|
-
var styles3 = StyleSheet3.create({
|
|
684
|
-
pin: {
|
|
685
|
-
width: 22,
|
|
686
|
-
height: 22,
|
|
687
|
-
borderRadius: 11,
|
|
688
|
-
borderWidth: 3,
|
|
689
|
-
borderColor: "#ffffff"
|
|
690
|
-
}
|
|
691
|
-
});
|
|
692
|
-
|
|
693
1981
|
// src/use-places.ts
|
|
694
|
-
import { useContext as
|
|
1982
|
+
import { useContext as useContext3, useEffect as useEffect3, useState as useState2 } from "react";
|
|
695
1983
|
function usePlaces(query, options = {}) {
|
|
696
|
-
const mapClient =
|
|
1984
|
+
const mapClient = useContext3(MapContext)?.places ?? null;
|
|
697
1985
|
const client = options.client ?? mapClient;
|
|
698
1986
|
const [items, setItems] = useState2([]);
|
|
699
1987
|
const [loading, setLoading] = useState2(false);
|
|
@@ -730,13 +2018,125 @@ function usePlaces(query, options = {}) {
|
|
|
730
2018
|
}, [client, query, nearKey, options.limit, options.debounceMs]);
|
|
731
2019
|
return { items, loading, error };
|
|
732
2020
|
}
|
|
2021
|
+
|
|
2022
|
+
// src/use-navigation.ts
|
|
2023
|
+
import { useCallback, useContext as useContext4, useSyncExternalStore as useSyncExternalStore2 } from "react";
|
|
2024
|
+
var noop = () => {
|
|
2025
|
+
};
|
|
2026
|
+
function useNavigation(session) {
|
|
2027
|
+
const map = useContext4(MapContext);
|
|
2028
|
+
const binding = session ? null : map?.navigation ?? null;
|
|
2029
|
+
const target = session ?? binding;
|
|
2030
|
+
if (!target) {
|
|
2031
|
+
throw new Error("useNavigation ph\u1EA3i \u0111\u01B0\u1EE3c g\u1ECDi b\xEAn trong <MapsLibVNMap> ho\u1EB7c truy\u1EC1n session");
|
|
2032
|
+
}
|
|
2033
|
+
const subscribeStatus = useCallback(
|
|
2034
|
+
(onChange) => {
|
|
2035
|
+
target.on("status", onChange);
|
|
2036
|
+
return () => target.off("status", onChange);
|
|
2037
|
+
},
|
|
2038
|
+
[target]
|
|
2039
|
+
);
|
|
2040
|
+
const subscribeProgress = useCallback(
|
|
2041
|
+
(onChange) => {
|
|
2042
|
+
target.on("progress", onChange);
|
|
2043
|
+
return () => target.off("progress", onChange);
|
|
2044
|
+
},
|
|
2045
|
+
[target]
|
|
2046
|
+
);
|
|
2047
|
+
const subscribeFollow = useCallback(
|
|
2048
|
+
(onChange) => {
|
|
2049
|
+
if (!binding) return noop;
|
|
2050
|
+
binding.on("followChange", onChange);
|
|
2051
|
+
return () => binding.off("followChange", onChange);
|
|
2052
|
+
},
|
|
2053
|
+
[binding]
|
|
2054
|
+
);
|
|
2055
|
+
const status = useSyncExternalStore2(
|
|
2056
|
+
subscribeStatus,
|
|
2057
|
+
() => target.status,
|
|
2058
|
+
() => target.status
|
|
2059
|
+
);
|
|
2060
|
+
const progress = useSyncExternalStore2(
|
|
2061
|
+
subscribeProgress,
|
|
2062
|
+
() => target.state,
|
|
2063
|
+
() => target.state
|
|
2064
|
+
);
|
|
2065
|
+
const following = useSyncExternalStore2(
|
|
2066
|
+
subscribeFollow,
|
|
2067
|
+
() => binding?.following ?? false,
|
|
2068
|
+
() => binding?.following ?? false
|
|
2069
|
+
);
|
|
2070
|
+
return {
|
|
2071
|
+
status,
|
|
2072
|
+
progress,
|
|
2073
|
+
following,
|
|
2074
|
+
start: (opts) => target.start(opts),
|
|
2075
|
+
stop: () => target.stop(),
|
|
2076
|
+
reroute: () => target.reroute(),
|
|
2077
|
+
recenter: () => binding?.recenter()
|
|
2078
|
+
};
|
|
2079
|
+
}
|
|
2080
|
+
|
|
2081
|
+
// src/navigation/playback-source.ts
|
|
2082
|
+
function playbackSource(fixes, options = {}) {
|
|
2083
|
+
const rate = options.rate ?? 1;
|
|
2084
|
+
return {
|
|
2085
|
+
subscribe(onFix) {
|
|
2086
|
+
let stopped = false;
|
|
2087
|
+
let timer = null;
|
|
2088
|
+
let i = 0;
|
|
2089
|
+
const emitNext = () => {
|
|
2090
|
+
if (stopped) return;
|
|
2091
|
+
const fix = fixes[i];
|
|
2092
|
+
if (!fix) return;
|
|
2093
|
+
onFix(fix);
|
|
2094
|
+
i += 1;
|
|
2095
|
+
const next = fixes[i];
|
|
2096
|
+
if (!next) return;
|
|
2097
|
+
timer = setTimeout(emitNext, Math.max(0, (next.timestamp - fix.timestamp) / rate));
|
|
2098
|
+
};
|
|
2099
|
+
timer = setTimeout(
|
|
2100
|
+
rate <= 0 ? () => {
|
|
2101
|
+
for (const f of fixes) {
|
|
2102
|
+
if (stopped) break;
|
|
2103
|
+
onFix(f);
|
|
2104
|
+
}
|
|
2105
|
+
} : emitNext,
|
|
2106
|
+
0
|
|
2107
|
+
);
|
|
2108
|
+
return () => {
|
|
2109
|
+
stopped = true;
|
|
2110
|
+
if (timer !== null) clearTimeout(timer);
|
|
2111
|
+
};
|
|
2112
|
+
}
|
|
2113
|
+
};
|
|
2114
|
+
}
|
|
733
2115
|
export {
|
|
2116
|
+
ALT_ROUTE_COLOR,
|
|
734
2117
|
COMPACT_ATTRIBUTION,
|
|
735
2118
|
DEFAULT_CENTER,
|
|
736
2119
|
DEFAULT_MARKER_COLOR,
|
|
737
2120
|
DEFAULT_ZOOM,
|
|
2121
|
+
DESTINATION_COLOR,
|
|
2122
|
+
FOLLOW_PITCH,
|
|
2123
|
+
FOLLOW_ZOOM,
|
|
2124
|
+
MISSING_SOURCE_MESSAGE,
|
|
738
2125
|
MapsLibVNMap,
|
|
739
2126
|
Marker,
|
|
2127
|
+
NAVIGATION_THRESHOLDS,
|
|
2128
|
+
ROUTE_COLOR,
|
|
2129
|
+
ROUTE_LAYER_IDS,
|
|
2130
|
+
ROUTE_SOURCE_ID,
|
|
2131
|
+
createClient,
|
|
2132
|
+
createNavigationSession,
|
|
2133
|
+
createNavigator,
|
|
2134
|
+
decodePolyline6,
|
|
2135
|
+
formatDistance,
|
|
2136
|
+
formatDistanceShort,
|
|
2137
|
+
playbackSource,
|
|
2138
|
+
simulateFixes,
|
|
740
2139
|
useMap,
|
|
2140
|
+
useNavigation,
|
|
741
2141
|
usePlaces
|
|
742
2142
|
};
|