Humanize turn-by-turn distances; drop arrival step distance
All checks were successful
CI / test-and-build (push) Successful in 1m2s
All checks were successful
CI / test-and-build (push) Successful in 1m2s
This commit is contained in:
parent
fb6ad3eb0c
commit
1d4ae6c76d
@ -119,6 +119,66 @@
|
|||||||
return min ? `${h} h ${min} min` : `${h} h`;
|
return min ? `${h} h ${min} min` : `${h} h`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---- Humanized (spoken-style) distance phrasing for the turn-by-turn list ----
|
||||||
|
// e.g. "500 ft", "half a mile", "a quarter mile", "1 and a half miles", "2 km".
|
||||||
|
function fracWords(f: number): string {
|
||||||
|
if (Math.abs(f - 0.25) < 0.001) return 'a quarter';
|
||||||
|
if (Math.abs(f - 0.5) < 0.001) return 'a half';
|
||||||
|
if (Math.abs(f - 0.75) < 0.001) return 'three-quarters';
|
||||||
|
return `${f.toFixed(2)}`;
|
||||||
|
}
|
||||||
|
function friendlyMiles(mi: number): string {
|
||||||
|
if (mi >= 10) return `${Math.round(mi)} miles`;
|
||||||
|
const q = Math.round((mi + Number.EPSILON) * 4) / 4; // nearest quarter mile
|
||||||
|
const whole = Math.floor(q);
|
||||||
|
const frac = q - whole;
|
||||||
|
const unit = whole === 1 ? 'mile' : 'miles';
|
||||||
|
if (whole === 0) {
|
||||||
|
if (Math.abs(frac - 0.25) < 0.001) return 'a quarter mile';
|
||||||
|
if (Math.abs(frac - 0.5) < 0.001) return 'half a mile';
|
||||||
|
if (Math.abs(frac - 0.75) < 0.001) return 'three-quarters of a mile';
|
||||||
|
return `${q.toFixed(2)} miles`;
|
||||||
|
}
|
||||||
|
if (frac === 0) return whole === 1 ? '1 mile' : `${whole} miles`;
|
||||||
|
return `${whole} and ${fracWords(frac)} miles`;
|
||||||
|
}
|
||||||
|
function friendlyKms(km: number): string {
|
||||||
|
if (km >= 10) return `${Math.round(km)} km`;
|
||||||
|
const q = Math.round((km + Number.EPSILON) * 4) / 4;
|
||||||
|
const whole = Math.floor(q);
|
||||||
|
const frac = q - whole;
|
||||||
|
if (whole === 0) {
|
||||||
|
if (Math.abs(frac - 0.25) < 0.001) return 'a quarter km';
|
||||||
|
if (Math.abs(frac - 0.5) < 0.001) return 'half a km';
|
||||||
|
if (Math.abs(frac - 0.75) < 0.001) return 'three-quarters of a km';
|
||||||
|
return `${q.toFixed(2)} km`;
|
||||||
|
}
|
||||||
|
if (frac === 0) return whole === 1 ? '1 km' : `${whole} km`;
|
||||||
|
return `${whole} and ${fracWords(frac)} km`;
|
||||||
|
}
|
||||||
|
function humanizeDistance(m: number): string {
|
||||||
|
if ($prefs.system === 'imperial') {
|
||||||
|
const ft = m * 3.28084;
|
||||||
|
const mi = m / 1609.344;
|
||||||
|
if (mi < 0.12) {
|
||||||
|
let f = Math.round(ft / 50) * 50; // '500 ft'
|
||||||
|
if (f < 75) f = Math.round(ft / 10) * 10; // below ~75ft be more precise
|
||||||
|
return `${Math.max(20, f)} ft`;
|
||||||
|
}
|
||||||
|
return friendlyMiles(mi);
|
||||||
|
}
|
||||||
|
if (m < 200) {
|
||||||
|
let d = Math.round(m / 25) * 25;
|
||||||
|
if (d < 50) d = Math.round(m);
|
||||||
|
return `${Math.max(10, d)} m`;
|
||||||
|
}
|
||||||
|
return friendlyKms(m / 1000);
|
||||||
|
}
|
||||||
|
// Detects the terminal step so we can drop its (always 0) distance.
|
||||||
|
function isArrival(t: TurnStep): boolean {
|
||||||
|
return t.instruction === 'Arrive at destination';
|
||||||
|
}
|
||||||
|
|
||||||
// --- destination (set via the context dialog or search/picked below) ---
|
// --- destination (set via the context dialog or search/picked below) ---
|
||||||
function setDestination(lat: number, lon: number, label?: string) {
|
function setDestination(lat: number, lon: number, label?: string) {
|
||||||
onDestinationChange({ lat, lon, label });
|
onDestinationChange({ lat, lon, label });
|
||||||
@ -360,7 +420,11 @@
|
|||||||
<div class="turns-head"><span>Turn-by-turn</span></div>
|
<div class="turns-head"><span>Turn-by-turn</span></div>
|
||||||
<ol class="turnlist">
|
<ol class="turnlist">
|
||||||
{#each turns.list as t, i (i + '-' + t.instruction)}
|
{#each turns.list as t, i (i + '-' + t.instruction)}
|
||||||
<li><span class="tdist">{formatDistance(t.distanceM)}</span><span class="tins">{t.instruction}</span></li>
|
{#if isArrival(t)}
|
||||||
|
<li><span class="tarrival">{t.instruction}</span></li>
|
||||||
|
{:else}
|
||||||
|
<li><span class="tdist">{humanizeDistance(t.distanceM)}</span><span class="tins">{t.instruction}</span></li>
|
||||||
|
{/if}
|
||||||
{/each}
|
{/each}
|
||||||
</ol>
|
</ol>
|
||||||
<div class="turns-total">{turns.list.length} steps · {formatDistance(turns.total)} total</div>
|
<div class="turns-total">{turns.list.length} steps · {formatDistance(turns.total)} total</div>
|
||||||
@ -444,6 +508,7 @@
|
|||||||
.turnlist { list-style: none; margin: 0; padding: 0; display: grid; gap: 4px; }
|
.turnlist { list-style: none; margin: 0; padding: 0; display: grid; gap: 4px; }
|
||||||
.turnlist li { display: flex; gap: 8px; align-items: baseline; font-size: 0.74rem; }
|
.turnlist li { display: flex; gap: 8px; align-items: baseline; font-size: 0.74rem; }
|
||||||
.tdist { color: #0ea5e9; font-weight: 600; min-width: 52px; }
|
.tdist { color: #0ea5e9; font-weight: 600; min-width: 52px; }
|
||||||
|
.tarrival { font-weight: 700; color: #16a34a; }
|
||||||
.tins { color: #0f172a; }
|
.tins { color: #0f172a; }
|
||||||
.turns-total { color: #94a3b8; font-size: 0.68rem; margin-top: 4px; }
|
.turns-total { color: #94a3b8; font-size: 0.68rem; margin-top: 4px; }
|
||||||
</style>
|
</style>
|
||||||
Loading…
x
Reference in New Issue
Block a user