fix(tours): open a saved ski tour as saved and keep what its router planned

A saved ski tour now opens with the line, heights, time, build and
planned legs it was saved with, read from the place this device keeps,
and nothing is asked of the piste router until a point, an option or the
discipline changes. Opened offline, while TREK Places is down or with
piste routing switched off, it no longer turns every leg straight, and a
rename saves the planned tour as it was. A kept line whose length is not
the tour's is another save's, and the tour is planned afresh.

- the save names the legs the router planned (ski_planned_legs, checked
  against the tour's own legs); get_tour_plan reports each leg routed or
  drawn from them instead of from the build alone, a tour that names none
  counting as planned only with the router's time
- copies keep ski_options in every path: in the trip, taken over, kept
  in a collection and made from one (collection_tours gets the column in
  the same unshipped migration)
- the route summary shows the data build, the km of runs, the height
  skied down on them and the lift rides, and the credit the server sends
  in place of a fixed one; a tour opened as saved says so
- answers refused while piste routing is off, or planned on an older kept
  copy, are not remembered, so they are asked again later
- terrain heights between known ones are filled in one walk along the line
- i18n: five summary keys in all 27 locales
This commit is contained in:
Maurice
2026-10-20 21:07:06 +02:00
parent 1d72fbd25f
commit ae699187f7
63 changed files with 1002 additions and 98 deletions
+5
View File
@@ -496,6 +496,11 @@ const tours: TranslationStrings = {
'tours.ski.summary.guessed': 'تعتمد بعض الأجزاء على تخمين من بيانات الخريطة، مثل وصلة أو اتجاه غير مرسوم.',
'tours.ski.summary.stale': 'خُطط على نسخة أقدم من بيانات المنحدرات، لأن النسخة الحالية غير متاحة.',
'tours.ski.summary.source': 'بيانات المنحدرات © مساهمو OpenStreetMap',
'tours.ski.summary.runs': 'المنحدرات',
'tours.ski.summary.descent': 'النزول على المنحدرات',
'tours.ski.summary.lifts': 'رحلات المصاعد',
'tours.ski.summary.build': 'بيانات الخريطة بتاريخ {build}',
'tours.ski.summary.saved': 'معروض كما حُفظ. حرّك نقطة أو غيّر خيارًا لإعادة التخطيط.',
'tours.ski.chooseTechnique': 'اختر الأسلوب، أو غير معروف.',
'tours.ski.infoLabel': 'ما يفعله TREK بجولات ومسارات التزلج',
'tours.ski.info':
+5
View File
@@ -487,6 +487,11 @@ const tours: TranslationStrings = {
'tours.ski.summary.stale':
'Cari nüsxəyə çatmaq mümkün olmadığı üçün pist məlumatlarının köhnə nüsxəsi ilə planlaşdırılıb.',
'tours.ski.summary.source': 'Pist məlumatları © OpenStreetMap iştirakçıları',
'tours.ski.summary.runs': 'Pistlər',
'tours.ski.summary.descent': 'Pistlərdə eniş',
'tours.ski.summary.lifts': 'Lift gedişləri',
'tours.ski.summary.build': 'Xəritə məlumatları: {build}',
'tours.ski.summary.saved': 'Saxlandığı kimi göstərilir. Yenidən planlamaq üçün nöqtəni tərpət və ya seçimi dəyiş.',
'tours.ski.chooseTechnique': 'Texnikanı və ya Naməlum seç.',
'tours.ski.infoLabel': 'TREK xizək turları və izləri ilə nə edir',
'tours.ski.info':
+5
View File
@@ -494,6 +494,11 @@ const tours: TranslationStrings = {
'tours.ski.summary.stale':
'Planejado com uma cópia mais antiga dos dados de pistas, pois a atual não está acessível.',
'tours.ski.summary.source': 'Dados de pistas © colaboradores do OpenStreetMap',
'tours.ski.summary.runs': 'Pistas',
'tours.ski.summary.descent': 'Descida nas pistas',
'tours.ski.summary.lifts': 'Viagens de teleférico',
'tours.ski.summary.build': 'Dados do mapa de {build}',
'tours.ski.summary.saved': 'Exibido como foi salvo. Mova um ponto ou mude uma opção para planejar de novo.',
'tours.ski.chooseTechnique': 'Escolha a técnica, ou Desconhecida.',
'tours.ski.infoLabel': 'O que o TREK faz com passeios e trilhas de esqui',
'tours.ski.info':
+6
View File
@@ -495,6 +495,12 @@ const tours: TranslationStrings = {
'tours.ski.summary.stale':
'Planificat amb una còpia anterior de les dades de pistes, perquè l’actual no és accessible.',
'tours.ski.summary.source': 'Dades de pistes © col·laboradors d’OpenStreetMap',
'tours.ski.summary.runs': 'Pistes',
'tours.ski.summary.descent': 'Baixada per pistes',
'tours.ski.summary.lifts': 'Viatges en remuntador',
'tours.ski.summary.build': 'Dades del mapa de {build}',
'tours.ski.summary.saved':
'Es mostra tal com es va desar. Mou un punt o canvia una opció per tornar-ho a planificar.',
'tours.ski.chooseTechnique': 'Tria la tècnica, o Desconeguda.',
'tours.ski.infoLabel': 'Què fa TREK amb les rutes i traces d’esquí',
'tours.ski.info':
+5
View File
@@ -486,6 +486,11 @@ const tours: TranslationStrings = {
'Některé části stojí na odhadu z mapových dat, například na nezmapované spojce nebo směru.',
'tours.ski.summary.stale': 'Naplánováno na starší kopii dat sjezdovek, protože aktuální není dostupná.',
'tours.ski.summary.source': 'Data sjezdovek © přispěvatelé OpenStreetMap',
'tours.ski.summary.runs': 'Sjezdovky',
'tours.ski.summary.descent': 'Sjezd po sjezdovkách',
'tours.ski.summary.lifts': 'Jízdy vlekem',
'tours.ski.summary.build': 'Mapová data z {build}',
'tours.ski.summary.saved': 'Zobrazeno, jak bylo uloženo. Posuňte bod nebo změňte volbu a naplánujte znovu.',
'tours.ski.chooseTechnique': 'Vyberte techniku, nebo Neznámá.',
'tours.ski.infoLabel': 'Co TREK dělá s lyžařskými túrami a trasami',
'tours.ski.info':
+6
View File
@@ -496,6 +496,12 @@ const tours: TranslationStrings = {
'Einige Stücke beruhen auf einer Vermutung aus den Kartendaten, etwa einer Verbindung oder Richtung, die nicht kartiert ist.',
'tours.ski.summary.stale': 'Auf einer älteren Kopie der Pistendaten geplant, weil die aktuelle nicht erreichbar ist.',
'tours.ski.summary.source': 'Pistendaten © OpenStreetMap-Mitwirkende',
'tours.ski.summary.runs': 'Pisten',
'tours.ski.summary.descent': 'Abfahrt auf Pisten',
'tours.ski.summary.lifts': 'Liftfahrten',
'tours.ski.summary.build': 'Kartendaten vom {build}',
'tours.ski.summary.saved':
'So angezeigt, wie gespeichert. Verschiebe einen Punkt oder ändere eine Option, um neu zu planen.',
'tours.ski.chooseTechnique': 'Wähle die Technik oder Unbekannt.',
'tours.ski.infoLabel': 'Was TREK mit Wintertouren und Skistrecken macht',
'tours.ski.info':
+5
View File
@@ -485,6 +485,11 @@ const tours: TranslationStrings = {
'Some stretches rest on a guess of the map data, such as a link or a direction that is not mapped.',
'tours.ski.summary.stale': 'Planned on an older copy of the piste data, as the current one cannot be reached.',
'tours.ski.summary.source': 'Piste data © OpenStreetMap contributors',
'tours.ski.summary.runs': 'Runs',
'tours.ski.summary.descent': 'Down on runs',
'tours.ski.summary.lifts': 'Lift rides',
'tours.ski.summary.build': 'Map data as of {build}',
'tours.ski.summary.saved': 'Shown as saved. Move a point or change an option to plan it again.',
'tours.ski.chooseTechnique': 'Choose the technique, or Unknown.',
'tours.ski.infoLabel': 'What TREK does with ski tours and tracks',
'tours.ski.info':
+6
View File
@@ -494,6 +494,12 @@ const tours: TranslationStrings = {
'tours.ski.summary.stale':
'Planificado con una copia anterior de los datos de pistas, porque la actual no está accesible.',
'tours.ski.summary.source': 'Datos de pistas © colaboradores de OpenStreetMap',
'tours.ski.summary.runs': 'Pistas',
'tours.ski.summary.descent': 'Descenso en pistas',
'tours.ski.summary.lifts': 'Viajes en remonte',
'tours.ski.summary.build': 'Datos del mapa de {build}',
'tours.ski.summary.saved':
'Se muestra tal como se guardó. Mueve un punto o cambia una opción para planificar de nuevo.',
'tours.ski.chooseTechnique': 'Elige la técnica, o Desconocida.',
'tours.ski.infoLabel': 'Qué hace TREK con rutas y trazas de esquí',
'tours.ski.info':
+5
View File
@@ -487,6 +487,11 @@ const tours: TranslationStrings = {
'Mõned lõigud põhinevad kaardiandmete oletusel, näiteks kaardistamata ühendusel või suunal.',
'tours.ski.summary.stale': 'Planeeritud nõlvaandmete vanemal koopial, sest praegune pole kättesaadav.',
'tours.ski.summary.source': 'Nõlvaandmed © OpenStreetMapi kaastöötajad',
'tours.ski.summary.runs': 'Nõlvad',
'tours.ski.summary.descent': 'Laskumine nõlvadel',
'tours.ski.summary.lifts': 'Tõstukisõidud',
'tours.ski.summary.build': 'Kaardiandmed seisuga {build}',
'tours.ski.summary.saved': 'Näidatud nagu salvestatud. Uuesti planeerimiseks liiguta punkti või muuda valikut.',
'tours.ski.chooseTechnique': 'Vali tehnika või Teadmata.',
'tours.ski.infoLabel': 'Mida TREK suusamatkade ja -radadega teeb',
'tours.ski.info':
+5
View File
@@ -501,6 +501,11 @@ const tours: TranslationStrings = {
'tours.ski.summary.stale':
'Planifié sur une copie plus ancienne des données de pistes, la version actuelle étant injoignable.',
'tours.ski.summary.source': 'Données de pistes © contributeurs OpenStreetMap',
'tours.ski.summary.runs': 'Pistes',
'tours.ski.summary.descent': 'Descente sur pistes',
'tours.ski.summary.lifts': 'Montées en remontée',
'tours.ski.summary.build': 'Données de carte du {build}',
'tours.ski.summary.saved': 'Affiché tel qu’enregistré. Déplacez un point ou changez une option pour replanifier.',
'tours.ski.chooseTechnique': 'Choisissez la technique, ou Inconnue.',
'tours.ski.infoLabel': 'Ce que TREK fait des sorties et traces de ski',
'tours.ski.info':
+6
View File
@@ -502,6 +502,12 @@ const tours: TranslationStrings = {
'tours.ski.summary.stale':
'Σχεδιάστηκε σε παλαιότερο αντίγραφο των δεδομένων πιστών, επειδή το τρέχον δεν είναι διαθέσιμο.',
'tours.ski.summary.source': 'Δεδομένα πιστών © συντελεστές του OpenStreetMap',
'tours.ski.summary.runs': 'Πίστες',
'tours.ski.summary.descent': 'Κατάβαση σε πίστες',
'tours.ski.summary.lifts': 'Διαδρομές με αναβατήρα',
'tours.ski.summary.build': 'Δεδομένα χάρτη της {build}',
'tours.ski.summary.saved':
'Εμφανίζεται όπως αποθηκεύτηκε. Μετακινήστε ένα σημείο ή αλλάξτε μια επιλογή για νέο σχεδιασμό.',
'tours.ski.chooseTechnique': 'Επιλέξτε τεχνική ή Άγνωστη.',
'tours.ski.infoLabel': 'Τι κάνει το TREK με διαδρομές και ίχνη σκι',
'tours.ski.info':
+6
View File
@@ -492,6 +492,12 @@ const tours: TranslationStrings = {
'Egyes részek a térképadatok becslésén alapulnak, például nem térképezett összeköttetésen vagy irányon.',
'tours.ski.summary.stale': 'A pályaadatok egy régebbi másolatán tervezve, mert a jelenlegi nem érhető el.',
'tours.ski.summary.source': 'Pályaadatok © OpenStreetMap közreműködők',
'tours.ski.summary.runs': 'Pályák',
'tours.ski.summary.descent': 'Lesiklás pályán',
'tours.ski.summary.lifts': 'Felvonós utak',
'tours.ski.summary.build': 'Térképadatok: {build}',
'tours.ski.summary.saved':
'A mentett állapot látszik. Mozgass el egy pontot vagy módosíts egy beállítást az újratervezéshez.',
'tours.ski.chooseTechnique': 'Válaszd ki a technikát, vagy az Ismeretlent.',
'tours.ski.infoLabel': 'Mit kezd a TREK a sí- és téli túrákkal és a sínyomvonalakkal',
'tours.ski.info':
+6
View File
@@ -490,6 +490,12 @@ const tours: TranslationStrings = {
'tours.ski.summary.stale':
'Direncanakan pada salinan data lintasan yang lebih lama, karena yang terbaru tidak dapat dijangkau.',
'tours.ski.summary.source': 'Data lintasan © kontributor OpenStreetMap',
'tours.ski.summary.runs': 'Lintasan',
'tours.ski.summary.descent': 'Turun di lintasan',
'tours.ski.summary.lifts': 'Naik lift',
'tours.ski.summary.build': 'Data peta per {build}',
'tours.ski.summary.saved':
'Ditampilkan seperti saat disimpan. Pindahkan titik atau ubah opsi untuk merencanakan ulang.',
'tours.ski.chooseTechnique': 'Pilih teknik, atau Tidak diketahui.',
'tours.ski.infoLabel': 'Apa yang TREK lakukan dengan tur dan jalur ski',
'tours.ski.info':
+5
View File
@@ -496,6 +496,11 @@ const tours: TranslationStrings = {
'tours.ski.summary.stale':
'Pianificato su una copia più vecchia dei dati delle piste, perché quella attuale non è raggiungibile.',
'tours.ski.summary.source': 'Dati delle piste © contributori di OpenStreetMap',
'tours.ski.summary.runs': 'Piste',
'tours.ski.summary.descent': 'Discesa sulle piste',
'tours.ski.summary.lifts': 'Corse in impianto',
'tours.ski.summary.build': 'Dati della mappa del {build}',
'tours.ski.summary.saved': 'Mostrato come salvato. Sposta un punto o cambia un’opzione per ripianificare.',
'tours.ski.chooseTechnique': 'Scegli la tecnica, oppure Sconosciuta.',
'tours.ski.infoLabel': 'Cosa fa TREK con tour e tracce di sci',
'tours.ski.info':
+5
View File
@@ -475,6 +475,11 @@ const tours: TranslationStrings = {
'tours.ski.summary.guessed': '一部の区間は、地図にない接続や方向など、地図データからの推定に基づいています。',
'tours.ski.summary.stale': '最新のゲレンデデータにアクセスできないため、古いコピーで計画しました。',
'tours.ski.summary.source': 'ゲレンデデータ © OpenStreetMap 貢献者',
'tours.ski.summary.runs': 'ゲレンデ',
'tours.ski.summary.descent': 'ゲレンデでの下降',
'tours.ski.summary.lifts': 'リフト乗車',
'tours.ski.summary.build': '地図データ: {build}',
'tours.ski.summary.saved': '保存した状態で表示しています。地点を動かすかオプションを変えると再計画します。',
'tours.ski.chooseTechnique': '走法を選ぶか、不明を選んでください。',
'tours.ski.infoLabel': 'TREK がスキーツアーとトラックで行うこと',
'tours.ski.info':
+5
View File
@@ -474,6 +474,11 @@ const tours: TranslationStrings = {
'tours.ski.summary.guessed': '일부 구간은 지도에 없는 연결이나 방향처럼 지도 데이터의 추정에 기반합니다.',
'tours.ski.summary.stale': '현재 슬로프 데이터에 연결할 수 없어 이전 사본으로 계획했습니다.',
'tours.ski.summary.source': '슬로프 데이터 © OpenStreetMap 기여자',
'tours.ski.summary.runs': '슬로프',
'tours.ski.summary.descent': '슬로프 하강',
'tours.ski.summary.lifts': '리프트 탑승',
'tours.ski.summary.build': '지도 데이터 기준: {build}',
'tours.ski.summary.saved': '저장된 그대로 표시합니다. 지점을 옮기거나 옵션을 바꾸면 다시 계획합니다.',
'tours.ski.chooseTechnique': '주법을 선택하거나 알 수 없음을 선택하세요.',
'tours.ski.infoLabel': 'TREK이 스키 투어와 트랙으로 하는 일',
'tours.ski.info':
+5
View File
@@ -494,6 +494,11 @@ const tours: TranslationStrings = {
'Sommige stukken berusten op een gok uit de kaartdata, zoals een verbinding of richting die niet in kaart is gebracht.',
'tours.ski.summary.stale': 'Gepland op een oudere kopie van de pistedata, omdat de huidige niet bereikbaar is.',
'tours.ski.summary.source': 'Pistedata © OpenStreetMap-bijdragers',
'tours.ski.summary.runs': 'Pistes',
'tours.ski.summary.descent': 'Afdaling op pistes',
'tours.ski.summary.lifts': 'Liftritten',
'tours.ski.summary.build': 'Kaartgegevens van {build}',
'tours.ski.summary.saved': 'Getoond zoals opgeslagen. Verplaats een punt of wijzig een optie om opnieuw te plannen.',
'tours.ski.chooseTechnique': 'Kies de techniek, of Onbekend.',
'tours.ski.infoLabel': 'Wat TREK met skitochten en skisporen doet',
'tours.ski.info':
+5
View File
@@ -502,6 +502,11 @@ const tours: TranslationStrings = {
'Niektóre fragmenty opierają się na domysłach z danych mapy, np. niezmapowanym połączeniu lub kierunku.',
'tours.ski.summary.stale': 'Zaplanowano na starszej kopii danych tras, bo aktualna jest nieosiągalna.',
'tours.ski.summary.source': 'Dane tras © współtwórcy OpenStreetMap',
'tours.ski.summary.runs': 'Trasy',
'tours.ski.summary.descent': 'Zjazd po trasach',
'tours.ski.summary.lifts': 'Przejazdy wyciągiem',
'tours.ski.summary.build': 'Dane mapy z {build}',
'tours.ski.summary.saved': 'Pokazano tak, jak zapisano. Przesuń punkt lub zmień opcję, aby zaplanować ponownie.',
'tours.ski.chooseTechnique': 'Wybierz technikę lub Nieznana.',
'tours.ski.infoLabel': 'Co TREK robi z wycieczkami i śladami narciarskimi',
'tours.ski.info':
+6
View File
@@ -505,6 +505,12 @@ const tours: TranslationStrings = {
'Некоторые отрезки опираются на догадку по картографическим данным, например на неотмеченную связку или направление.',
'tours.ski.summary.stale': 'Спланировано по более старой копии данных трасс, так как текущая недоступна.',
'tours.ski.summary.source': 'Данные трасс © участники OpenStreetMap',
'tours.ski.summary.runs': 'Трассы',
'tours.ski.summary.descent': 'Спуск по трассам',
'tours.ski.summary.lifts': 'Подъёмы',
'tours.ski.summary.build': 'Данные карты от {build}',
'tours.ski.summary.saved':
'Показано, как сохранено. Сдвиньте точку или измените параметр, чтобы спланировать заново.',
'tours.ski.chooseTechnique': 'Выберите технику или «Неизвестно».',
'tours.ski.infoLabel': 'Что TREK делает с лыжными маршрутами и треками',
'tours.ski.info':
+5
View File
@@ -487,6 +487,11 @@ const tours: TranslationStrings = {
'Niektoré časti stoja na odhade z mapových dát, napríklad na nezmapovanej spojke alebo smere.',
'tours.ski.summary.stale': 'Naplánované na staršej kópii dát zjazdoviek, lebo aktuálna nie je dostupná.',
'tours.ski.summary.source': 'Dáta zjazdoviek © prispievatelia OpenStreetMap',
'tours.ski.summary.runs': 'Zjazdovky',
'tours.ski.summary.descent': 'Zjazd po zjazdovkách',
'tours.ski.summary.lifts': 'Jazdy vlekom',
'tours.ski.summary.build': 'Mapové dáta z {build}',
'tours.ski.summary.saved': 'Zobrazené tak, ako bolo uložené. Posuňte bod alebo zmeňte voľbu a naplánujte znova.',
'tours.ski.chooseTechnique': 'Vyberte techniku, alebo Neznáma.',
'tours.ski.infoLabel': 'Čo TREK robí s lyžiarskymi túrami a trasami',
'tours.ski.info':
+5
View File
@@ -484,6 +484,11 @@ const tours: TranslationStrings = {
'Vissa delar bygger på en gissning från kartdatan, som en länk eller riktning som inte är kartlagd.',
'tours.ski.summary.stale': 'Planerad på en äldre kopia av pistdatan, eftersom den aktuella inte går att nå.',
'tours.ski.summary.source': 'Pistdata © OpenStreetMap-bidragsgivare',
'tours.ski.summary.runs': 'Pister',
'tours.ski.summary.descent': 'Nedfärd i pister',
'tours.ski.summary.lifts': 'Liftturer',
'tours.ski.summary.build': 'Kartdata från {build}',
'tours.ski.summary.saved': 'Visas som det sparades. Flytta en punkt eller ändra ett val för att planera om.',
'tours.ski.chooseTechnique': 'Välj teknik, eller Okänd.',
'tours.ski.infoLabel': 'Vad TREK gör med skidturer och skidspår',
'tours.ski.info':
+5
View File
@@ -465,6 +465,11 @@ const tours: TranslationStrings = {
'tours.ski.summary.guessed': 'บางส่วนอาศัยการคาดเดาจากข้อมูลแผนที่ เช่น ทางเชื่อมหรือทิศทางที่ไม่มีในแผนที่',
'tours.ski.summary.stale': 'วางแผนบนสำเนาข้อมูลลานสกีที่เก่ากว่า เพราะเข้าถึงฉบับปัจจุบันไม่ได้',
'tours.ski.summary.source': 'ข้อมูลลานสกี © ผู้ร่วมพัฒนา OpenStreetMap',
'tours.ski.summary.runs': 'ลานสกี',
'tours.ski.summary.descent': 'ระยะลงบนลานสกี',
'tours.ski.summary.lifts': 'จำนวนรอบลิฟต์',
'tours.ski.summary.build': 'ข้อมูลแผนที่ ณ {build}',
'tours.ski.summary.saved': 'แสดงตามที่บันทึกไว้ ย้ายจุดหรือเปลี่ยนตัวเลือกเพื่อวางแผนใหม่',
'tours.ski.chooseTechnique': 'เลือกเทคนิค หรือไม่ทราบ',
'tours.ski.infoLabel': 'TREK ทำอะไรกับทริปและเส้นทางสกี',
'tours.ski.info':
+6
View File
@@ -486,6 +486,12 @@ const tours: TranslationStrings = {
'Bazı kısımlar harita verilerinden bir tahmine dayanıyor, örneğin haritada olmayan bir bağlantı veya yön.',
'tours.ski.summary.stale': 'Güncel kopyaya ulaşılamadığı için pist verilerinin eski bir kopyasıyla planlandı.',
'tours.ski.summary.source': 'Pist verileri © OpenStreetMap katkıcıları',
'tours.ski.summary.runs': 'Pistler',
'tours.ski.summary.descent': 'Pistlerde iniş',
'tours.ski.summary.lifts': 'Teleferik binişleri',
'tours.ski.summary.build': 'Harita verisi: {build}',
'tours.ski.summary.saved':
'Kaydedildiği gibi gösteriliyor. Yeniden planlamak için bir noktayı taşı ya da bir seçeneği değiştir.',
'tours.ski.chooseTechnique': 'Tekniği ya da Bilinmiyor seçeneğini seç.',
'tours.ski.infoLabel': 'TREK kayak turları ve izleriyle ne yapar',
'tours.ski.info':
+5
View File
@@ -500,6 +500,11 @@ const tours: TranslationStrings = {
'Деякі відрізки спираються на здогад за картографічними даними, наприклад на невідмічену зв’язку чи напрямок.',
'tours.ski.summary.stale': 'Сплановано за старішою копією даних трас, бо поточна недоступна.',
'tours.ski.summary.source': 'Дані трас © учасники OpenStreetMap',
'tours.ski.summary.runs': 'Траси',
'tours.ski.summary.descent': 'Спуск трасами',
'tours.ski.summary.lifts': 'Підйоми',
'tours.ski.summary.build': 'Дані мапи від {build}',
'tours.ski.summary.saved': 'Показано, як збережено. Перемістіть точку або змініть параметр, щоб спланувати знову.',
'tours.ski.chooseTechnique': 'Виберіть техніку або «Невідомо».',
'tours.ski.infoLabel': 'Що TREK робить з лижними маршрутами і треками',
'tours.ski.info':
+5
View File
@@ -485,6 +485,11 @@ const tours: TranslationStrings = {
'tours.ski.summary.stale':
'Được lên kế hoạch trên bản sao cũ hơn của dữ liệu đường trượt, vì không truy cập được bản hiện tại.',
'tours.ski.summary.source': 'Dữ liệu đường trượt © cộng tác viên OpenStreetMap',
'tours.ski.summary.runs': 'Đường trượt',
'tours.ski.summary.descent': 'Độ xuống trên đường trượt',
'tours.ski.summary.lifts': 'Lượt cáp treo',
'tours.ski.summary.build': 'Dữ liệu bản đồ ngày {build}',
'tours.ski.summary.saved': 'Hiển thị như đã lưu. Di chuyển một điểm hoặc đổi một tùy chọn để lập lại kế hoạch.',
'tours.ski.chooseTechnique': 'Chọn kỹ thuật, hoặc Không rõ.',
'tours.ski.infoLabel': 'TREK làm gì với chuyến và đường trượt tuyết',
'tours.ski.info':
+5
View File
@@ -449,6 +449,11 @@ const tours: TranslationStrings = {
'tours.ski.summary.guessed': '有些路段基於地圖資料的推測,例如未標註的連接或方向。',
'tours.ski.summary.stale': '由於無法存取目前版本,使用較早的雪道資料副本規劃。',
'tours.ski.summary.source': '雪道資料 © OpenStreetMap 貢獻者',
'tours.ski.summary.runs': '雪道',
'tours.ski.summary.descent': '雪道下降',
'tours.ski.summary.lifts': '纜車次數',
'tours.ski.summary.build': '地圖資料:{build}',
'tours.ski.summary.saved': '依儲存時顯示。移動一個點或變更選項即可重新規劃。',
'tours.ski.chooseTechnique': '請選擇技術,或選擇未知。',
'tours.ski.infoLabel': 'TREK 如何處理滑雪路線與軌跡',
'tours.ski.info':
+5
View File
@@ -450,6 +450,11 @@ const tours: TranslationStrings = {
'tours.ski.summary.guessed': '有些路段基于地图数据的推测,例如未标注的连接或方向。',
'tours.ski.summary.stale': '由于无法访问当前版本,使用较早的雪道数据副本规划。',
'tours.ski.summary.source': '雪道数据 © OpenStreetMap 贡献者',
'tours.ski.summary.runs': '雪道',
'tours.ski.summary.descent': '雪道下降',
'tours.ski.summary.lifts': '缆车次数',
'tours.ski.summary.build': '地图数据:{build}',
'tours.ski.summary.saved': '按保存时显示。移动一个点或更改选项即可重新规划。',
'tours.ski.chooseTechnique': '请选择技术,或选择未知。',
'tours.ski.infoLabel': 'TREK 如何处理滑雪路线与轨迹',
'tours.ski.info':
+43 -16
View File
@@ -22,6 +22,23 @@ const A = { lat: 45.98, lng: 7.75 };
const B = { lat: 45.93, lng: 7.71 };
const SHA = 'a'.repeat(64);
/** A ski tour as the list carries it. */
const LISTED = {
place_id: 4,
tour_type: 'ski',
distance: 8,
elevation_gain: 100,
elevation_loss: 900,
duration: 3600,
difficulty: null,
wanderer_ref: null,
match_confidence: 1,
max_hiking_difficulty: null,
name: 'Valley run',
planned: true,
caution: false,
};
describe('piste router options (SKI-ROUTE)', () => {
it('SKI-ROUTE-SCHEMA-001: an empty object is a complete answer, use_lifts left to the discipline', () => {
expect(DEFAULT_SKI_ROUTE_OPTIONS).toEqual({
@@ -111,27 +128,37 @@ describe('piste router options (SKI-ROUTE)', () => {
);
});
it('SKI-ROUTE-SCHEMA-005: a listed tour may carry its options and build, or neither', () => {
const listed = {
place_id: 4,
tour_type: 'ski',
distance: 8,
elevation_gain: 100,
elevation_loss: 900,
duration: 3600,
difficulty: null,
wanderer_ref: null,
match_confidence: 1,
max_hiking_difficulty: null,
it('SKI-ROUTE-SCHEMA-012: a saved ski tour names the legs the router planned, each a leg of its own, once', () => {
const planned = {
name: 'Valley run',
planned: true,
caution: false,
tour_type: 'ski' as const,
ski_discipline: 'alpine' as const,
route_geometry: [
[A.lat, A.lng, 2500],
[B.lat, B.lng, 1600],
],
waypoints: [
{ lat: A.lat, lng: A.lng, role: 'start', sequence: 0 },
{ lat: B.lat, lng: B.lng, role: 'via', sequence: 1 },
{ lat: A.lat, lng: A.lng, role: 'end', sequence: 2 },
],
};
expect(tourListItemSchema.parse({ ...listed, ski_options: {}, ski_route_build: '2026-11-02' })).toMatchObject({
expect(tourCreateRequestSchema.parse({ ...planned, ski_planned_legs: [1, 0] }).ski_planned_legs).toEqual([1, 0]);
expect(tourCreateRequestSchema.parse({ ...planned, ski_planned_legs: [] }).ski_planned_legs).toEqual([]);
for (const legs of [[2], [0, 0], [-1], [0.5]]) {
expect(tourCreateRequestSchema.safeParse({ ...planned, ski_planned_legs: legs }).success).toBe(false);
}
const hike = { ...planned, tour_type: 'hike' as const, ski_discipline: null };
expect(() => tourCreateRequestSchema.parse({ ...hike, ski_planned_legs: [0] })).toThrow(/ski_planned_legs belongs/);
expect(tourListItemSchema.parse({ ...LISTED, ski_planned_legs: [0] }).ski_planned_legs).toEqual([0]);
});
it('SKI-ROUTE-SCHEMA-005: a listed tour may carry its options and build, or neither', () => {
expect(tourListItemSchema.parse({ ...LISTED, ski_options: {}, ski_route_build: '2026-11-02' })).toMatchObject({
ski_options: DEFAULT_SKI_ROUTE_OPTIONS,
ski_route_build: '2026-11-02',
});
expect(tourListItemSchema.parse(listed).ski_options).toBeUndefined();
expect(tourListItemSchema.parse(LISTED).ski_options).toBeUndefined();
});
});
+6
View File
@@ -171,5 +171,11 @@ export function skiOptionsForDiscipline(discipline: SkiDiscipline, options: SkiR
return result as SkiRouteOptions;
}
/**
* The legs of a ski tour the piste router planned, each named by the index of
* the control point it starts at; the others were drawn straight or by hand.
*/
export const skiPlannedLegsSchema = z.array(z.number().int().nonnegative()).max(5000);
/** The name of a piste data build a ski tour's legs were planned on, as the Places host names it. */
export const skiRouteBuildSchema = z.string().trim().min(1).max(40).regex(/^[0-9A-Za-z._-]+$/);
+13 -1
View File
@@ -10,6 +10,7 @@ import { runPaceSecondsPerKmSchema } from './run-pace';
import {
skiDisciplineSchema,
skiIssues,
skiPlannedLegsSchema,
skiRouteBuildSchema,
skiRouteOptionsSchema,
skiTechniqueSchema,
@@ -227,6 +228,8 @@ export const tourSchema = z.object({
ski_options: skiRouteOptionsSchema.nullable().optional(),
/** The piste data build a ski tour's legs were planned on; null where the router planned none. */
ski_route_build: z.string().nullable().optional(),
/** The ski tour's legs the piste router planned, by the index of the point each starts at; null if unknown. */
ski_planned_legs: skiPlannedLegsSchema.nullable().optional(),
/** A paddle tour's boat and water; null for every other type. Absent in a list cached before they existed. */
boat_type: paddleBoatTypeSchema.nullable().optional(),
water_context: paddleWaterContextSchema.nullable().optional(),
@@ -531,6 +534,8 @@ export const tourCreateRequestSchema = z
ski_options: skiRouteOptionsSchema.nullable().optional(),
/** A ski tour's only: the piste data build its legs were planned on, so newer data can be offered later. */
ski_route_build: skiRouteBuildSchema.nullable().optional(),
/** A ski tour's only: the legs the piste router planned, by the index of the point each starts at. */
ski_planned_legs: skiPlannedLegsSchema.nullable().optional(),
boat_type: paddleBoatTypeSchema.nullable().optional(),
water_context: paddleWaterContextSchema.nullable().optional(),
paddle_speed_kmh: paddleSpeedKmhSchema.nullable().optional(),
@@ -657,6 +662,7 @@ function tourTypeIssues(value: {
ski_technique?: SkiTechnique | null;
ski_options?: object | null;
ski_route_build?: string | null;
ski_planned_legs?: readonly number[] | null;
boat_type?: PaddleBoatType | null;
water_context?: PaddleWaterContext | null;
paddle_speed_kmh?: number | null;
@@ -698,10 +704,16 @@ function tourTypeIssues(value: {
issues.push(...runIssues(value));
issues.push(...skiIssues(type === 'ski', value));
if (type !== 'ski') {
for (const field of ['ski_options', 'ski_route_build'] as const) {
for (const field of ['ski_options', 'ski_route_build', 'ski_planned_legs'] as const) {
if (value[field] != null) issues.push({ path: [field], message: `${field} belongs to a ski tour` });
}
}
// Each planned leg is one of this tour's legs, named once.
const legCount = Math.max(0, (value.waypoints?.length ?? 0) - 1);
(value.ski_planned_legs ?? []).forEach((leg, index, all) => {
if (leg >= legCount || all.indexOf(leg) !== index)
issues.push({ path: ['ski_planned_legs', index], message: 'not a leg of this tour, or named twice' });
});
// No router plans a ride, so no time is worked out for it; a planned total the rider enters is theirs.
// A ski tour's time is the piste router's, sent only when it planned every leg.
if (type === 'horse' && value.duration_seconds != null)