mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-18 21:16:47 +00:00
fix(roadtrip): a late check-out morning says so, a road driven twice takes its via
The check-out row showed the room's last hour and, beside it, the time the plan leaves the hotel, which can be hours later when a place further on is pinned. The row now says "Leaves after check-out" when it does, on the desktop and on the phone. A click on a road the day drives twice, out of the hotel in the morning and again between two of its own places, went to the first pass, the drive out of the hotel, and was refused. The second pass now takes the via, and where yesterday's drive into the hotel and today's leg share a road, today's leg does. A road only the hotel's drives use still takes none. The wiki says both.
This commit is contained in:
@@ -290,6 +290,7 @@ const roadtrip: TranslationStrings = {
|
||||
'roadtrip.bookend.from': 'من {name}',
|
||||
'roadtrip.bookend.back': 'العودة إلى {name}',
|
||||
'roadtrip.bookend.checkIn': 'تسجيل الوصول · {name}',
|
||||
'roadtrip.bookend.afterCheckOut': 'الانطلاق بعد موعد تسجيل المغادرة',
|
||||
'roadtrip.bookend.noVia': 'لا يمكن وضع نقطة عبور على الطريق من مكان إقامتك أو إليه. أضف توقفاً هناك بدلاً من ذلك.',
|
||||
};
|
||||
export default roadtrip;
|
||||
|
||||
@@ -296,6 +296,7 @@ const roadtrip: TranslationStrings = {
|
||||
'roadtrip.bookend.from': 'Saindo de {name}',
|
||||
'roadtrip.bookend.back': 'Volta para {name}',
|
||||
'roadtrip.bookend.checkIn': 'Check-in · {name}',
|
||||
'roadtrip.bookend.afterCheckOut': 'Saída depois do check-out',
|
||||
'roadtrip.bookend.noVia':
|
||||
'Não dá para colocar um ponto de passagem no trajeto de ida ou volta da sua hospedagem. Adicione uma parada ali.',
|
||||
};
|
||||
|
||||
@@ -299,6 +299,7 @@ const roadtrip: TranslationStrings = {
|
||||
'roadtrip.bookend.from': 'Des de {name}',
|
||||
'roadtrip.bookend.back': 'Tornada a {name}',
|
||||
'roadtrip.bookend.checkIn': 'Entrada · {name}',
|
||||
'roadtrip.bookend.afterCheckOut': 'Marxa després de l’hora de sortida',
|
||||
'roadtrip.bookend.noVia':
|
||||
'No es pot posar cap punt de pas al trajecte fins al teu allotjament o des d’ell. Afegeix-hi una parada.',
|
||||
};
|
||||
|
||||
@@ -293,6 +293,7 @@ const roadtrip: TranslationStrings = {
|
||||
'roadtrip.bookend.from': 'Z ubytování {name}',
|
||||
'roadtrip.bookend.back': 'Zpět do ubytování {name}',
|
||||
'roadtrip.bookend.checkIn': 'Check-in · {name}',
|
||||
'roadtrip.bookend.afterCheckOut': 'Odjezd až po check-outu',
|
||||
'roadtrip.bookend.noVia':
|
||||
'Na trasu do ubytování ani z něj nelze přidat průjezdní bod. Přidejte tam místo toho zastávku.',
|
||||
};
|
||||
|
||||
@@ -293,6 +293,7 @@ const roadtrip: TranslationStrings = {
|
||||
'roadtrip.bookend.from': 'Ab {name}',
|
||||
'roadtrip.bookend.back': 'Zurück zu {name}',
|
||||
'roadtrip.bookend.checkIn': 'Check-in · {name}',
|
||||
'roadtrip.bookend.afterCheckOut': 'Abfahrt nach dem Check-out',
|
||||
'roadtrip.bookend.noVia':
|
||||
'Auf der Fahrt zur oder von der Unterkunft geht kein Zwischenpunkt. Füge dort stattdessen einen Stopp hinzu.',
|
||||
};
|
||||
|
||||
@@ -290,6 +290,7 @@ const roadtrip: TranslationStrings = {
|
||||
'roadtrip.bookend.from': 'From {name}',
|
||||
'roadtrip.bookend.back': 'Back to {name}',
|
||||
'roadtrip.bookend.checkIn': 'Check-in · {name}',
|
||||
'roadtrip.bookend.afterCheckOut': 'Leaves after check-out',
|
||||
'roadtrip.bookend.noVia': 'No via point on the drive to or from your stay. Add a stop there instead.',
|
||||
};
|
||||
export default roadtrip;
|
||||
|
||||
@@ -297,6 +297,7 @@ const roadtrip: TranslationStrings = {
|
||||
'roadtrip.bookend.from': 'Desde {name}',
|
||||
'roadtrip.bookend.back': 'Vuelta a {name}',
|
||||
'roadtrip.bookend.checkIn': 'Entrada · {name}',
|
||||
'roadtrip.bookend.afterCheckOut': 'Parte después de la hora de salida',
|
||||
'roadtrip.bookend.noVia':
|
||||
'No se puede poner un punto de paso en el trayecto hacia tu alojamiento o desde él. Añade una parada allí.',
|
||||
};
|
||||
|
||||
@@ -299,6 +299,7 @@ const roadtrip: TranslationStrings = {
|
||||
'roadtrip.bookend.from': 'Depuis {name}',
|
||||
'roadtrip.bookend.back': 'Retour à {name}',
|
||||
'roadtrip.bookend.checkIn': 'Arrivée · {name}',
|
||||
'roadtrip.bookend.afterCheckOut': 'Part après l’heure limite de départ',
|
||||
'roadtrip.bookend.noVia':
|
||||
'Pas de point de passage sur le trajet vers ou depuis votre hébergement. Ajoutez plutôt un arrêt à cet endroit.',
|
||||
};
|
||||
|
||||
@@ -301,6 +301,7 @@ const roadtrip: TranslationStrings = {
|
||||
'roadtrip.bookend.from': 'Από {name}',
|
||||
'roadtrip.bookend.back': 'Επιστροφή: {name}',
|
||||
'roadtrip.bookend.checkIn': 'Check-in · {name}',
|
||||
'roadtrip.bookend.afterCheckOut': 'Αναχώρηση μετά το check-out',
|
||||
'roadtrip.bookend.noVia':
|
||||
'Δεν μπαίνει ενδιάμεσο σημείο στη διαδρομή προς ή από το κατάλυμα. Προσθέστε εκεί μια στάση.',
|
||||
};
|
||||
|
||||
@@ -295,6 +295,7 @@ const roadtrip: TranslationStrings = {
|
||||
'roadtrip.bookend.from': 'Indulás: {name}',
|
||||
'roadtrip.bookend.back': 'Vissza: {name}',
|
||||
'roadtrip.bookend.checkIn': 'Bejelentkezés · {name}',
|
||||
'roadtrip.bookend.afterCheckOut': 'Indulás kijelentkezés után',
|
||||
'roadtrip.bookend.noVia':
|
||||
'A szálláshoz vezető vagy onnan induló útra nem kerülhet köztes pont. Adj hozzá inkább egy megállót ott.',
|
||||
};
|
||||
|
||||
@@ -298,6 +298,7 @@ const roadtrip: TranslationStrings = {
|
||||
'roadtrip.bookend.from': 'Dari {name}',
|
||||
'roadtrip.bookend.back': 'Kembali ke {name}',
|
||||
'roadtrip.bookend.checkIn': 'Check-in · {name}',
|
||||
'roadtrip.bookend.afterCheckOut': 'Berangkat setelah check-out',
|
||||
'roadtrip.bookend.noVia':
|
||||
'Titik lintas tidak bisa ditaruh di perjalanan ke atau dari penginapan Anda. Tambahkan perhentian di sana.',
|
||||
};
|
||||
|
||||
@@ -298,6 +298,7 @@ const roadtrip: TranslationStrings = {
|
||||
'roadtrip.bookend.from': 'Da {name}',
|
||||
'roadtrip.bookend.back': 'Ritorno a {name}',
|
||||
'roadtrip.bookend.checkIn': 'Check-in · {name}',
|
||||
'roadtrip.bookend.afterCheckOut': 'Partenza dopo il check-out',
|
||||
'roadtrip.bookend.noVia':
|
||||
'Nessun punto di passaggio sul tragitto verso o dal tuo alloggio. Aggiungi invece una sosta in quel punto.',
|
||||
};
|
||||
|
||||
@@ -292,6 +292,7 @@ const roadtrip: TranslationStrings = {
|
||||
'roadtrip.bookend.from': '{name}から',
|
||||
'roadtrip.bookend.back': '{name}へ戻る',
|
||||
'roadtrip.bookend.checkIn': 'チェックイン · {name}',
|
||||
'roadtrip.bookend.afterCheckOut': 'チェックアウト後に出発',
|
||||
'roadtrip.bookend.noVia': '宿泊先との間の区間には経由地を置けません。代わりにそこへ立ち寄り先を追加してください。',
|
||||
};
|
||||
export default roadtrip;
|
||||
|
||||
@@ -289,6 +289,7 @@ const roadtrip: TranslationStrings = {
|
||||
'roadtrip.bookend.from': '{name}에서 출발',
|
||||
'roadtrip.bookend.back': '{name}(으)로 돌아가기',
|
||||
'roadtrip.bookend.checkIn': '체크인 · {name}',
|
||||
'roadtrip.bookend.afterCheckOut': '체크아웃 후 출발',
|
||||
'roadtrip.bookend.noVia': '숙소를 오가는 구간에는 경유지를 둘 수 없습니다. 대신 그곳에 정차 지점을 추가하세요.',
|
||||
};
|
||||
export default roadtrip;
|
||||
|
||||
@@ -297,6 +297,7 @@ const roadtrip: TranslationStrings = {
|
||||
'roadtrip.bookend.from': 'Vanaf {name}',
|
||||
'roadtrip.bookend.back': 'Terug naar {name}',
|
||||
'roadtrip.bookend.checkIn': 'Inchecken · {name}',
|
||||
'roadtrip.bookend.afterCheckOut': 'Vertrek na het uitchecken',
|
||||
'roadtrip.bookend.noVia':
|
||||
'Geen tussenpunt op de rit naar of van je verblijf. Voeg daar in plaats daarvan een stop toe.',
|
||||
};
|
||||
|
||||
@@ -294,6 +294,7 @@ const roadtrip: TranslationStrings = {
|
||||
'roadtrip.bookend.from': 'Wyjazd: {name}',
|
||||
'roadtrip.bookend.back': 'Powrót: {name}',
|
||||
'roadtrip.bookend.checkIn': 'Zameldowanie · {name}',
|
||||
'roadtrip.bookend.afterCheckOut': 'Wyjazd po godzinie wymeldowania',
|
||||
'roadtrip.bookend.noVia':
|
||||
'Na trasie do noclegu ani z niego nie można dodać punktu pośredniego. Dodaj tam zamiast tego postój.',
|
||||
};
|
||||
|
||||
@@ -298,6 +298,7 @@ const roadtrip: TranslationStrings = {
|
||||
'roadtrip.bookend.from': 'Отправление: {name}',
|
||||
'roadtrip.bookend.back': 'Возвращение: {name}',
|
||||
'roadtrip.bookend.checkIn': 'Заезд · {name}',
|
||||
'roadtrip.bookend.afterCheckOut': 'Отъезд позже расчётного часа',
|
||||
'roadtrip.bookend.noVia':
|
||||
'На маршрут до места проживания или от него нельзя поставить промежуточную точку. Добавьте там остановку.',
|
||||
};
|
||||
|
||||
@@ -293,6 +293,7 @@ const roadtrip: TranslationStrings = {
|
||||
'roadtrip.bookend.from': 'Från {name}',
|
||||
'roadtrip.bookend.back': 'Tillbaka till {name}',
|
||||
'roadtrip.bookend.checkIn': 'Incheckning · {name}',
|
||||
'roadtrip.bookend.afterCheckOut': 'Avresa efter utcheckning',
|
||||
'roadtrip.bookend.noVia':
|
||||
'Ingen mellanpunkt på resan till eller från ditt boende. Lägg till ett stopp där i stället.',
|
||||
};
|
||||
|
||||
@@ -296,6 +296,7 @@ const roadtrip: TranslationStrings = {
|
||||
'roadtrip.bookend.from': 'Başlangıç: {name}',
|
||||
'roadtrip.bookend.back': 'Dönüş: {name}',
|
||||
'roadtrip.bookend.checkIn': 'Giriş · {name}',
|
||||
'roadtrip.bookend.afterCheckOut': 'Çıkış saatinden sonra hareket',
|
||||
'roadtrip.bookend.noVia':
|
||||
'Konaklama yerinize giden ya da oradan çıkan yola ara nokta konamaz. Bunun yerine oraya bir mola ekleyin.',
|
||||
};
|
||||
|
||||
@@ -295,6 +295,7 @@ const roadtrip: TranslationStrings = {
|
||||
'roadtrip.bookend.from': 'Відправлення: {name}',
|
||||
'roadtrip.bookend.back': 'Повернення: {name}',
|
||||
'roadtrip.bookend.checkIn': 'Заїзд · {name}',
|
||||
'roadtrip.bookend.afterCheckOut': 'Від’їзд пізніше розрахункової години',
|
||||
'roadtrip.bookend.noVia':
|
||||
'На маршрут до місця проживання чи від нього не можна поставити проміжну точку. Додайте там зупинку.',
|
||||
};
|
||||
|
||||
@@ -294,6 +294,7 @@ const roadtrip: TranslationStrings = {
|
||||
'roadtrip.bookend.from': 'Từ {name}',
|
||||
'roadtrip.bookend.back': 'Về lại {name}',
|
||||
'roadtrip.bookend.checkIn': 'Nhận phòng · {name}',
|
||||
'roadtrip.bookend.afterCheckOut': 'Khởi hành sau giờ trả phòng',
|
||||
'roadtrip.bookend.noVia':
|
||||
'Không thể đặt điểm trung gian trên chặng đi hoặc về nơi lưu trú. Hãy thêm một điểm dừng ở đó.',
|
||||
};
|
||||
|
||||
@@ -278,6 +278,7 @@ const roadtrip: TranslationStrings = {
|
||||
'roadtrip.bookend.from': '從 {name} 出發',
|
||||
'roadtrip.bookend.back': '返回 {name}',
|
||||
'roadtrip.bookend.checkIn': '入住 · {name}',
|
||||
'roadtrip.bookend.afterCheckOut': '退房後才出發',
|
||||
'roadtrip.bookend.noVia': '往返住宿地點的路段無法加入途經點。請改在那裡新增一個停靠點。',
|
||||
};
|
||||
export default roadtrip;
|
||||
|
||||
@@ -279,6 +279,7 @@ const roadtrip: TranslationStrings = {
|
||||
'roadtrip.bookend.from': '从 {name} 出发',
|
||||
'roadtrip.bookend.back': '返回 {name}',
|
||||
'roadtrip.bookend.checkIn': '入住 · {name}',
|
||||
'roadtrip.bookend.afterCheckOut': '退房后才出发',
|
||||
'roadtrip.bookend.noVia': '往返住处的路段不能添加途经点。请改为在那里添加一个停靠点。',
|
||||
};
|
||||
export default roadtrip;
|
||||
|
||||
@@ -89,6 +89,45 @@ describe('projectOntoRoute', () => {
|
||||
expect(projectOntoRoute(BERLIN, [])).toBeNull();
|
||||
expect(projectOntoRoute(BERLIN, [BERLIN])).toBeNull();
|
||||
});
|
||||
|
||||
describe('within a stretch of the drive', () => {
|
||||
// Out along a straight road and back over it: every point of the road is on the
|
||||
// line twice, once on the way out and once on the way back.
|
||||
const out = { lat: 52, lng: 13 };
|
||||
const tip = { lat: 52, lng: 14 };
|
||||
const outAndBack = [out, tip, out];
|
||||
const onRoad = { lat: 52, lng: 13.3 };
|
||||
|
||||
it('answers with the first pass for the whole line', () => {
|
||||
const whole = projectOntoRoute(onRoad, outAndBack)!;
|
||||
const oneWay = haversineKm(out, tip);
|
||||
expect(whole.offRouteKm).toBeLessThan(0.01);
|
||||
expect(whole.alongKm).toBeLessThan(oneWay / 2);
|
||||
});
|
||||
|
||||
it('finds the second pass when asked for the way back', () => {
|
||||
const oneWay = haversineKm(out, tip);
|
||||
const back = projectOntoRoute(onRoad, outAndBack, { fromKm: oneWay, toKm: 2 * oneWay })!;
|
||||
expect(back.offRouteKm).toBeLessThan(0.01);
|
||||
expect(back.alongKm).toBeGreaterThan(oneWay);
|
||||
expect(back.alongKm).toBeCloseTo(2 * oneWay - haversineKm(out, onRoad), 1);
|
||||
});
|
||||
|
||||
it('measures to the edge of the stretch when the point lies beyond it', () => {
|
||||
const straight = [
|
||||
{ lat: 52, lng: 13 },
|
||||
{ lat: 52, lng: 15 },
|
||||
];
|
||||
const length = haversineKm(straight[0]!, straight[1]!);
|
||||
const hit = projectOntoRoute({ lat: 52, lng: 13.1 }, straight, { fromKm: length / 2, toKm: length })!;
|
||||
expect(hit.alongKm).toBeCloseTo(length / 2, 3);
|
||||
expect(hit.offRouteKm).toBeGreaterThan(55);
|
||||
});
|
||||
|
||||
it('answers nothing for a stretch past the end of the line', () => {
|
||||
expect(projectOntoRoute(onRoad, outAndBack, { fromKm: 10_000, toKm: 20_000 })).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('corridorTiles', () => {
|
||||
|
||||
@@ -59,22 +59,50 @@ export interface CorridorHit {
|
||||
alongKm: number;
|
||||
}
|
||||
|
||||
export function projectOntoRoute(p: LatLng, line: LatLng[]): CorridorHit | null {
|
||||
/**
|
||||
* Where `p` meets the line: how far off it is and how far into the drive the closest
|
||||
* point comes.
|
||||
*
|
||||
* `within` keeps the answer to one stretch of the drive, in km from its start. A road
|
||||
* driven twice in a day, out of a hotel and back past it later, is the same line twice
|
||||
* over, and the answer for the whole line is always the first pass; a caller that needs
|
||||
* the second one asks for the stretch it lies in. No part of that stretch on the line
|
||||
* means no answer.
|
||||
*/
|
||||
export function projectOntoRoute(
|
||||
p: LatLng,
|
||||
line: LatLng[],
|
||||
within?: { fromKm: number; toKm: number },
|
||||
): CorridorHit | null {
|
||||
if (line.length < 2) return null;
|
||||
let best = Number.POSITIVE_INFINITY;
|
||||
let bestAlong = 0;
|
||||
let travelled = 0;
|
||||
let measured = false;
|
||||
for (let i = 0; i < line.length - 1; i++) {
|
||||
const a = line[i]!;
|
||||
const b = line[i + 1]!;
|
||||
const segment = haversineKm(a, b);
|
||||
const { distanceKm: d, t } = projectOnSegment(p, a, b);
|
||||
const start = travelled;
|
||||
travelled += segment;
|
||||
if (within && (travelled < within.fromKm || start > within.toKm)) continue;
|
||||
measured = true;
|
||||
let { distanceKm: d, t } = projectOnSegment(p, a, b);
|
||||
if (within && segment > 0) {
|
||||
// A segment the stretch starts or ends inside counts only up to that edge.
|
||||
const lo = Math.max(0, (within.fromKm - start) / segment);
|
||||
const hi = Math.min(1, (within.toKm - start) / segment);
|
||||
if (t < lo || t > hi) {
|
||||
t = t < lo ? lo : hi;
|
||||
d = haversineKm(p, { lat: a.lat + t * (b.lat - a.lat), lng: a.lng + t * (b.lng - a.lng) });
|
||||
}
|
||||
}
|
||||
if (d < best) {
|
||||
best = d;
|
||||
bestAlong = travelled + t * segment;
|
||||
bestAlong = start + t * segment;
|
||||
}
|
||||
travelled += segment;
|
||||
}
|
||||
if (within && !measured) return null;
|
||||
return { offRouteKm: best, alongKm: bestAlong };
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user