feat(maps): draw every travel day's route at once, with the trip total

Extract the day route builder out of useRouteCalculation into a pure module and
aggregate it over the whole trip: one map with every day's route, each day in its
own colour, the mode per stage and the trip's total distance. Straight lines first,
roads as they answer; a refused leg keeps its line and adds no distance.

Give the floating map controls one shared drop shadow while adding the toggle, so
the new pill and the base-layer switcher stand off the tiles the way the locate
button always has. Document the control in wiki/Map-Features.md.

Addresses #1736
This commit is contained in:
Konstantinos Thermos
2026-09-10 15:57:48 +03:00
parent 8acfaea3c4
commit bf1900c3a0
43 changed files with 1095 additions and 192 deletions
+3
View File
@@ -22,5 +22,8 @@ const map: TranslationStrings = {
'map.location.denied': 'تم حظر الوصول إلى الموقع. تحقق من إعدادات الجهاز؛ فالتطبيق المثبت لديه إذن موقع خاص به منفصل عن المتصفح.',
'map.location.unavailable': 'تعذر تحديد موقعك.',
'map.location.timeout': 'استغرق تحديد موقعك وقتًا طويلًا. حاول مرة أخرى تحت سماء مكشوفة.',
'map.overview.show': 'عرض الرحلة كاملة',
'map.overview.hide': 'إخفاء الرحلة كاملة',
'map.overview.total': 'المسافة الإجمالية',
};
export default map;
+3
View File
@@ -22,5 +22,8 @@ const map: TranslationStrings = {
'map.location.denied': 'O acesso à localização está bloqueado. Verifique as configurações do aparelho; um app instalado tem permissão de localização própria, separada do navegador.',
'map.location.unavailable': 'Não foi possível determinar sua localização.',
'map.location.timeout': 'A localização demorou demais. Tente de novo com uma visão mais aberta do céu.',
'map.overview.show': 'Mostrar a viagem inteira',
'map.overview.hide': 'Ocultar a viagem inteira',
'map.overview.total': 'Distância total',
};
export default map;
+3
View File
@@ -22,5 +22,8 @@ const map: TranslationStrings = {
'map.location.denied': 'L’accés a la ubicació està bloquejat. Revisa la configuració del dispositiu; una app instal·lada té el seu propi permís d’ubicació, separat del navegador.',
'map.location.unavailable': 'No s’ha pogut determinar la teva ubicació.',
'map.location.timeout': 'La localització ha trigat massa. Torna-ho a provar amb una vista més clara del cel.',
'map.overview.show': 'Mostra tot el viatge',
'map.overview.hide': 'Amaga tot el viatge',
'map.overview.total': 'Distància total',
};
export default map;
+3
View File
@@ -22,5 +22,8 @@ const map: TranslationStrings = {
'map.location.denied': 'Přístup k poloze je zablokován. Zkontrolujte nastavení zařízení; nainstalovaná aplikace má vlastní oprávnění k poloze, nezávislé na prohlížeči.',
'map.location.unavailable': 'Vaši polohu se nepodařilo zjistit.',
'map.location.timeout': 'Zjišťování polohy trvalo příliš dlouho. Zkuste to znovu s volným výhledem na oblohu.',
'map.overview.show': 'Zobrazit celou cestu',
'map.overview.hide': 'Skrýt celou cestu',
'map.overview.total': 'Celková vzdálenost',
};
export default map;
+3
View File
@@ -22,5 +22,8 @@ const map: TranslationStrings = {
'map.location.denied': 'Der Standortzugriff ist blockiert. Prüfe die Geräteeinstellungen; eine installierte App hat eine eigene Standortberechtigung, unabhängig vom Browser.',
'map.location.unavailable': 'Dein Standort konnte nicht ermittelt werden.',
'map.location.timeout': 'Die Standortbestimmung hat zu lange gedauert. Versuche es mit freier Sicht zum Himmel erneut.',
'map.overview.show': 'Ganze Reise anzeigen',
'map.overview.hide': 'Ganze Reise ausblenden',
'map.overview.total': 'Gesamtstrecke',
};
export default map;
+3
View File
@@ -22,5 +22,8 @@ const map: TranslationStrings = {
'map.location.denied': 'Location access is blocked. Check your device settings; an installed app has its own location permission, separate from the browser.',
'map.location.unavailable': 'Your location could not be determined.',
'map.location.timeout': 'Locating you took too long. Try again with a clearer view of the sky.',
'map.overview.show': 'Show whole trip',
'map.overview.hide': 'Hide whole trip',
'map.overview.total': 'Total distance',
};
export default map;
+3
View File
@@ -22,5 +22,8 @@ const map: TranslationStrings = {
'map.location.denied': 'El acceso a la ubicación está bloqueado. Revisa los ajustes del dispositivo; una app instalada tiene su propio permiso de ubicación, independiente del navegador.',
'map.location.unavailable': 'No se pudo determinar tu ubicación.',
'map.location.timeout': 'La localización tardó demasiado. Inténtalo de nuevo con una vista más despejada del cielo.',
'map.overview.show': 'Mostrar todo el viaje',
'map.overview.hide': 'Ocultar todo el viaje',
'map.overview.total': 'Distancia total',
};
export default map;
+3
View File
@@ -22,5 +22,8 @@ const map: TranslationStrings = {
'map.location.denied': 'L’accès à la position est bloqué. Vérifiez les réglages de l’appareil. Une app installée possède sa propre autorisation de localisation, distincte du navigateur.',
'map.location.unavailable': 'Votre position n’a pas pu être déterminée.',
'map.location.timeout': 'La localisation a pris trop de temps. Réessayez avec une vue plus dégagée du ciel.',
'map.overview.show': 'Afficher tout le voyage',
'map.overview.hide': 'Masquer tout le voyage',
'map.overview.total': 'Distance totale',
};
export default map;
+3
View File
@@ -22,5 +22,8 @@ const map: TranslationStrings = {
'map.location.denied': 'Η πρόσβαση στην τοποθεσία είναι αποκλεισμένη. Ελέγξτε τις ρυθμίσεις της συσκευής. Μια εγκατεστημένη εφαρμογή έχει δική της άδεια τοποθεσίας, ξεχωριστή από το πρόγραμμα περιήγησης.',
'map.location.unavailable': 'Δεν ήταν δυνατός ο προσδιορισμός της τοποθεσίας σας.',
'map.location.timeout': 'Ο εντοπισμός της θέσης σας άργησε πολύ. Δοκιμάστε ξανά με καθαρή θέα στον ουρανό.',
'map.overview.show': 'Εμφάνιση όλου του ταξιδιού',
'map.overview.hide': 'Απόκρυψη όλου του ταξιδιού',
'map.overview.total': 'Συνολική απόσταση',
};
export default map;
+3
View File
@@ -22,5 +22,8 @@ const map: TranslationStrings = {
'map.location.denied': 'A helyhozzáférés le van tiltva. Ellenőrizd a készülék beállításait; a telepített alkalmazásnak saját helyengedélye van, a böngészőtől függetlenül.',
'map.location.unavailable': 'A tartózkodási helyed nem határozható meg.',
'map.location.timeout': 'A helymeghatározás túl sokáig tartott. Próbáld újra szabad ég alatt.',
'map.overview.show': 'Teljes utazás megjelenítése',
'map.overview.hide': 'Teljes utazás elrejtése',
'map.overview.total': 'Teljes távolság',
};
export default map;
+3
View File
@@ -22,5 +22,8 @@ const map: TranslationStrings = {
'map.location.denied': 'Akses lokasi diblokir. Periksa pengaturan perangkat; aplikasi yang terpasang punya izin lokasi sendiri, terpisah dari browser.',
'map.location.unavailable': 'Lokasi Anda tidak dapat ditentukan.',
'map.location.timeout': 'Penentuan lokasi terlalu lama. Coba lagi di tempat dengan pandangan langit yang lebih terbuka.',
'map.overview.show': 'Tampilkan seluruh perjalanan',
'map.overview.hide': 'Sembunyikan seluruh perjalanan',
'map.overview.total': 'Jarak total',
};
export default map;
+3
View File
@@ -22,5 +22,8 @@ const map: TranslationStrings = {
'map.location.denied': 'L’accesso alla posizione è bloccato. Controlla le impostazioni del dispositivo; un’app installata ha un proprio permesso di localizzazione, separato dal browser.',
'map.location.unavailable': 'Impossibile determinare la tua posizione.',
'map.location.timeout': 'La localizzazione ha richiesto troppo tempo. Riprova con una visuale più libera del cielo.',
'map.overview.show': 'Mostra tutto il viaggio',
'map.overview.hide': 'Nascondi tutto il viaggio',
'map.overview.total': 'Distanza totale',
};
export default map;
+3
View File
@@ -22,5 +22,8 @@ const map: TranslationStrings = {
'map.location.denied': '位置情報へのアクセスがブロックされています。端末の設定を確認してください。インストールしたアプリにはブラウザとは別の位置情報の許可があります。',
'map.location.unavailable': '現在地を特定できませんでした。',
'map.location.timeout': '位置情報の取得に時間がかかりすぎました。空がよく見える場所でもう一度お試しください。',
'map.overview.show': '旅程全体を表示',
'map.overview.hide': '旅程全体を非表示',
'map.overview.total': '合計距離',
};
export default map;
+3
View File
@@ -22,5 +22,8 @@ const map: TranslationStrings = {
'map.location.denied': '위치 접근이 차단되어 있습니다. 기기 설정을 확인하세요. 설치된 앱은 브라우저와 별도의 위치 권한을 사용합니다.',
'map.location.unavailable': '현재 위치를 확인할 수 없습니다.',
'map.location.timeout': '위치를 확인하는 데 시간이 너무 오래 걸렸습니다. 하늘이 잘 보이는 곳에서 다시 시도하세요.',
'map.overview.show': '전체 여행 보기',
'map.overview.hide': '전체 여행 숨기기',
'map.overview.total': '총 거리',
};
export default map;
+3
View File
@@ -22,5 +22,8 @@ const map: TranslationStrings = {
'map.location.denied': 'Locatietoegang is geblokkeerd. Controleer de apparaatinstellingen; een geïnstalleerde app heeft een eigen locatiemachtiging, los van de browser.',
'map.location.unavailable': 'Je locatie kon niet worden bepaald.',
'map.location.timeout': 'Het bepalen van je locatie duurde te lang. Probeer het opnieuw met vrij zicht op de lucht.',
'map.overview.show': 'Hele reis tonen',
'map.overview.hide': 'Hele reis verbergen',
'map.overview.total': 'Totale afstand',
};
export default map;
+3
View File
@@ -22,5 +22,8 @@ const map: TranslationStrings = {
'map.location.denied': 'Dostęp do lokalizacji jest zablokowany. Sprawdź ustawienia urządzenia; zainstalowana aplikacja ma własne uprawnienie do lokalizacji, niezależne od przeglądarki.',
'map.location.unavailable': 'Nie udało się ustalić Twojej lokalizacji.',
'map.location.timeout': 'Ustalanie lokalizacji trwało zbyt długo. Spróbuj ponownie przy lepszej widoczności nieba.',
'map.overview.show': 'Pokaż całą podróż',
'map.overview.hide': 'Ukryj całą podróż',
'map.overview.total': 'Łączny dystans',
};
export default map;
+3
View File
@@ -22,5 +22,8 @@ const map: TranslationStrings = {
'map.location.denied': 'Доступ к геопозиции заблокирован. Проверьте настройки устройства; у установленного приложения есть собственное разрешение на геопозицию, отдельное от браузера.',
'map.location.unavailable': 'Не удалось определить ваше местоположение.',
'map.location.timeout': 'Определение местоположения заняло слишком много времени. Попробуйте ещё раз под открытым небом.',
'map.overview.show': 'Показать всю поездку',
'map.overview.hide': 'Скрыть всю поездку',
'map.overview.total': 'Общее расстояние',
};
export default map;
+3
View File
@@ -22,5 +22,8 @@ const map: TranslationStrings = {
'map.location.denied': 'Platsåtkomst är blockerad. Kontrollera enhetens inställningar; en installerad app har ett eget platstillstånd, skilt från webbläsaren.',
'map.location.unavailable': 'Din plats kunde inte fastställas.',
'map.location.timeout': 'Platsbestämningen tog för lång tid. Försök igen med friare sikt mot himlen.',
'map.overview.show': 'Visa hela resan',
'map.overview.hide': 'Dölj hela resan',
'map.overview.total': 'Total sträcka',
};
export default map;
+3
View File
@@ -22,5 +22,8 @@ const map: TranslationStrings = {
'map.location.denied': 'Konum erişimi engellendi. Cihaz ayarlarını kontrol edin; yüklü bir uygulamanın tarayıcıdan ayrı kendi konum izni vardır.',
'map.location.unavailable': 'Konumunuz belirlenemedi.',
'map.location.timeout': 'Konum belirleme çok uzun sürdü. Gökyüzünü daha iyi gören bir yerde tekrar deneyin.',
'map.overview.show': 'Tüm geziyi göster',
'map.overview.hide': 'Tüm geziyi gizle',
'map.overview.total': 'Toplam mesafe',
};
export default map;
+3
View File
@@ -22,5 +22,8 @@ const map: TranslationStrings = {
'map.location.denied': 'Доступ до геолокації заблоковано. Перевірте налаштування пристрою; встановлений застосунок має власний дозвіл на геолокацію, окремий від браузера.',
'map.location.unavailable': 'Не вдалося визначити ваше місцезнаходження.',
'map.location.timeout': 'Визначення місцезнаходження тривало надто довго. Спробуйте ще раз просто неба.',
'map.overview.show': 'Показати всю подорож',
'map.overview.hide': 'Сховати всю подорож',
'map.overview.total': 'Загальна відстань',
};
export default map;
+3
View File
@@ -22,5 +22,8 @@ const map: TranslationStrings = {
'map.location.denied': 'Quyền truy cập vị trí đang bị chặn. Hãy kiểm tra cài đặt thiết bị; ứng dụng đã cài đặt có quyền vị trí riêng, tách biệt với trình duyệt.',
'map.location.unavailable': 'Không thể xác định vị trí của bạn.',
'map.location.timeout': 'Việc xác định vị trí mất quá nhiều thời gian. Hãy thử lại ở nơi nhìn thấy bầu trời thoáng hơn.',
'map.overview.show': 'Hiện toàn bộ chuyến đi',
'map.overview.hide': 'Ẩn toàn bộ chuyến đi',
'map.overview.total': 'Tổng quãng đường',
};
export default map;
+3
View File
@@ -22,5 +22,8 @@ const map: TranslationStrings = {
'map.location.denied': '定位權限已被封鎖。請檢查裝置設定,已安裝的應用程式擁有與瀏覽器分開的定位權限。',
'map.location.unavailable': '無法判斷你的位置。',
'map.location.timeout': '定位花費的時間過長。請在能看到天空的開闊位置重試。',
'map.overview.show': '顯示整趟行程',
'map.overview.hide': '隱藏整趟行程',
'map.overview.total': '總距離',
};
export default map;
+3
View File
@@ -22,5 +22,8 @@ const map: TranslationStrings = {
'map.location.denied': '定位权限已被阻止。请检查设备设置,已安装的应用拥有与浏览器分开的定位权限。',
'map.location.unavailable': '无法确定你的位置。',
'map.location.timeout': '定位耗时过长。请在能看到天空的开阔位置重试。',
'map.overview.show': '显示整个行程',
'map.overview.hide': '隐藏整个行程',
'map.overview.total': '总距离',
};
export default map;