Files
randy a5adb39770
CI / check (push) Successful in 18s
CI / deploy (push) Successful in 13s
Add monoline icons to the topbar coordinates/sun readout
WAYPOINT_ICON (coords) and PENDING_ICON (countdown) reused as-is; two new
hand-drawn SUNRISE_ICON/SUNSET_ICON since NPS's symbol set has no match —
same currentColor stroke style as the rest of icons.js, so they render
monochrome in the dark topbar rather than colored emoji.

sun.js's formatSunInfo now returns {sunrise, sunset, countdown} instead of
one joined string, so map.js can place its own icon in front of each
piece instead of the module owning any markup.
2026-09-10 22:54:18 +02:00

72 lines
2.9 KiB
JavaScript

import { getTimes } from "/vendor/suncalc/suncalc.js";
// A Locus Pro-style toolbar readout: DMS coordinates (plain decimal degrees
// read less like a "GPS unit" and are harder to scan at a glance) plus
// sunrise/sunset and a countdown to whichever comes next — both computed
// for the map's current center, the same point the coordinates already
// describe, not a GNSS fix (this app's coordinate readout was never tied
// to having one).
function toDMS(value, positiveLabel, negativeLabel) {
const label = value >= 0 ? positiveLabel : negativeLabel;
const abs = Math.abs(value);
const degrees = Math.floor(abs);
const minutesFull = (abs - degrees) * 60;
let minutes = Math.floor(minutesFull);
let seconds = Math.round((minutesFull - minutes) * 60);
// A seconds value that rounds up to 60 has to carry into minutes (and
// from there, in the rare case, into degrees) rather than print "60".
if (seconds === 60) {
seconds = 0;
minutes += 1;
}
const carriedDegrees = minutes === 60 ? degrees + 1 : degrees;
const carriedMinutes = minutes === 60 ? 0 : minutes;
return `${carriedDegrees}°${String(carriedMinutes).padStart(2, "0")}'${String(seconds).padStart(2, "0")}"${label}`;
}
export function formatCoords(lat, lng) {
return `${toDMS(lat, "N", "S")} ${toDMS(lng, "E", "W")}`;
}
function formatTime(date) {
return `${String(date.getHours()).padStart(2, "0")}:${String(date.getMinutes()).padStart(2, "0")}`;
}
function formatCountdown(ms) {
const totalMinutes = Math.max(0, Math.round(ms / 60000));
const hours = Math.floor(totalMinutes / 60);
const minutes = totalMinutes % 60;
return hours > 0 ? `${hours}h ${minutes}m` : `${minutes}m`;
}
const DAY_MS = 24 * 60 * 60 * 1000;
// null when there's no sunrise/sunset to show at all — getTimes returns
// null (not Invalid Date) for both on a polar day/night, per its own
// comment in suncalc.js — the caller just hides the readout in that case
// rather than showing something misleading. Returns the three pieces
// separately (not one joined string) so the caller can put its own icon in
// front of each rather than this module owning any markup.
export function formatSunInfo(lat, lng, now = new Date()) {
const today = getTimes(now, lat, lng);
if (today.sunrise === null || today.sunset === null) return null;
let next;
if (now < today.sunrise) {
next = { label: "sunrise", at: today.sunrise };
} else if (now < today.sunset) {
next = { label: "sunset", at: today.sunset };
} else {
// Already past today's sunset — the next event is tomorrow's sunrise.
const tomorrow = getTimes(new Date(now.getTime() + DAY_MS), lat, lng);
next = tomorrow.sunrise === null ? null : { label: "sunrise", at: tomorrow.sunrise };
}
return {
sunrise: formatTime(today.sunrise),
sunset: formatTime(today.sunset),
countdown: next ? `${formatCountdown(next.at - now)} to ${next.label}` : null,
};
}