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.
72 lines
2.9 KiB
JavaScript
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,
|
|
};
|
|
}
|