Commit Graph
12 Commits
Author SHA1 Message Date
randy d08ed41185 Replace offline basemap pipeline with Planetiler, unify online/offline styling
CI / check (push) Successful in 18s
CI / deploy (push) Successful in 2m43s
Offline tiles were built from Protomaps' own basemap schema, needing a
completely separate hand-tuned theme (protomaps-themes-base + OFFLINE_THEME)
from the online Liberty style's ONLINE_STYLE_PATCH — the two could never be
made to match exactly no matter how much either side got tuned (today's
session found and fixed several mismatches: building opacity, park
boundaries, road casings, road widths).

Offline.pmtiles is now built with Planetiler's default OpenMapTiles profile
against Geofabrik per-country OSM extracts (osmium-merged when more than one
country is selected), the same vector schema Liberty already expects.
offlineStyle() now clones the same Liberty JSON (vendored as a local
snapshot for reliability with no internet) and applies the identical
ONLINE_STYLE_PATCH, just repointed at local PMTiles/glyphs/sprite — one
palette instead of two, correct on both sides by construction.

Also vendors Liberty's icon sprite for the first time (offline had none
before), adds Java/osmium-tool/planetiler.jar to the Docker image, and
removes the now-unused protomaps-themes-base dependency and the
theme-comparison debug page that found today's bugs.
2026-09-11 20:52:11 +02:00
randy cd56ebe0a1 Add in-app regeneration of offline.pmtiles and terrain.pmtiles
CI / check (push) Successful in 20s
CI / deploy (push) Successful in 1m52s
A logged-in user can now trigger, from a new "Offline data" sidebar
panel, what was previously a fully manual go-pmtiles workflow
(docs/development.adoc): regenerate either file for the current map
viewport, no area-picker (same "visible area is the area" convention
the Overpass POI plugin already uses).

- lib/maptool/pmtiles_builder.rb: two independent background jobs
  (Thread + Mutex-guarded status Hash, same shape as app.rb's own GNSS
  poller), each writing to a .tmp path and atomically renaming into
  place so a build never serves a partially-written file mid-run.
  offline.pmtiles is a single `pmtiles extract` against Protomaps'
  public daily basemap build (byte-range extraction, not a full
  ~120GB download). terrain.pmtiles has no equivalent public
  Terrarium-encoded archive to extract from, so it fetches individual
  Terrarium tiles itself, packs them into an intermediate MBTiles
  (new sqlite3 gem), then the same `pmtiles convert`.
- 4 new app.rb routes (POST .../build, GET .../build-status) plus
  size/mtime added to the existing availability endpoints.
- public/js/offline_data.js: new sidebar panel, polling build-status
  the same self-rescheduling-setTimeout shape livetrack.js already
  uses for its own refresh loop.
- Both network-heavy pieces (the Protomaps build index, per-tile
  fetches) are injectable, so test/test_pmtiles_builder.rb exercises
  the actual build pipeline against a fake `pmtiles` binary without
  hitting a real network, matching this suite's existing
  keep-it-offline convention for external services.
2026-09-11 01:25:08 +02:00
randy fba8ca10a2 Add contour lines to the Overlays panel via maplibre-contour
CI / check (push) Successful in 18s
CI / deploy (push) Successful in 12s
5th Overlays item, generated client-side from the same online Terrarium
DEM tiles Terrain shading already fetches (no offline fallback — the
plugin's DemSource has no public hook to route through the pmtiles://
protocol the offline basemap uses, so this quietly renders nothing
without a network, same as Radar/the Wikipedia layer).

A plain vector source/layer under MapLibre's own tile lifecycle, no
manual refetch-on-move code needed. Thresholds are the plugin's own
generic starting point, not yet tuned against real terrain. Labels
skipped for now (font-name matching across the online/offline styles'
own glyph sets adds real risk for a nice-to-have, not the actual ask).
2026-09-11 00:12:32 +02:00
randy b4a1c6c0cc Add a search box, matching NPS's Park Tiles viewer
CI / check (push) Successful in 19s
CI / deploy (push) Successful in 13s
Top-left map control (topmost, above Home/zoom) with a text input:
"lat, lng" is parsed and jumped to directly (same plain-text shape the
topbar's coordinate readout already copies to the clipboard), anything
else is geocoded via Nominatim — OSM's own geocoder, free, no API key,
matching the online basemap's own OSM-derived style. Online only, same
story as this app's other non-basemap online features (Radar, the
Wikipedia layer) — no offline geocoding index to fall back to.
2026-09-10 23:59:35 +02:00
randy c9561a5489 Add an offline terrain shadow overlay to the Overlays panel
Shades the visible map by whether each point is currently blocked from
the sun by surrounding terrain (shademap.app-style), computed fully
offline via the same Terrarium DEM tiles the Terrain shading layer
already renders, plus this session's sun-position math and Radar's
checkbox+expandable UI shape.

dem.js pulls elevation-at-a-point out on its own since TODO.adoc's
slope readout and solar calculator will need the same lookup.
2026-09-10 21:43:51 +02:00
randy dce5fd87da Add a Locus Pro-style toolbar readout: DMS coords + sunrise/sunset
The topbar's coordinate display was plain decimal degrees ("48.14611,
17.17722") — switched to DMS (48°08'46"N 17°10'38"E), the more
GPS-unit-like format Locus Pro itself uses, and made it click-to-copy
(plain decimal, since that's what pastes cleanly into another map/GPS
app — not the DMS text actually shown).

Added sunrise/sunset + a countdown to whichever's next, for the same
map-center point the coordinates already describe. Vendored SunCalc
(BSD-2-Clause, github.com/mourner/suncalc) rather than hand-rolling
the astronomical math — small, dependency-free, and correctness here
has real pitfalls (equation of time, refraction, polar day/night) a
first-pass reimplementation would risk getting subtly wrong.

Also added a test asserting every public/js/*.js module has a
versioned importmap entry in layout.erb — caught that overlays.js
(from the previous commit) was missing one, which would have quietly
skipped ASSET_VERSION's cache-busting for that file after every
deploy.
2026-09-10 21:08:02 +02:00
randy 72ffd0ef18 Add an import plugin system, with the Overpass POI query as its first plugin
CI / check (push) Successful in 40s
CI / deploy (push) Successful in 13s
A plugin is a directory under plugins/ with a plugin.json manifest
({"label": "...", "command": "./run.sh"}) plus a script that reads
--south/--west/--north/--east and prints a GeoJSON FeatureCollection
to stdout (lib/maptool/plugins.rb) — that's the entire interface a
plugin needs to know about this app. The sidebar's new import icon
(logged in only, GET /api/plugins + POST /api/plugins/:name/run) lists
whatever's found and runs one for the current map viewport, saving its
output as a new .geojson file the same way an upload would be.

The point: a scraper for a site whose ToS forbids that (Park4Night,
iOverlander, Freecampsites) doesn't need to live in this public repo
to work here — its own directory just goes under plugins/ locally and
is never git add'ed, no special-casing needed anywhere.

plugins/overpass-pois wraps the existing rake overpass:pois query as
the first, in-repo, reference example of the contract.

Also fills in four modules (auth.js, locate.js, measure.js, notes.js)
missing from layout.erb's cache-busting importmap alongside adding
plugins.js's own entry — a pre-existing gap that meant they could keep
serving a stale cached copy across a deploy despite the importmap
existing specifically to prevent that.
2026-09-09 16:23:31 +02:00
randy 5caf865fc8 Cache radar tile images with a Service Worker
CI / check (push) Successful in 40s
CI / deploy (push) Successful in 12s
public/sw.js: network-falling-back-to-cache for RainViewer radar tile
requests specifically (nothing else — the app itself is served by the
same local server that's already the offline resource in the van, so
there's no app-shell to precache). Registered from layout.erb, a no-op
where it can't register (insecure context) rather than an error.

Verified: registers and controls the page, tiles get cached on a
normal fetch, cache.match() correctly retrieves them by the same
request. CDP's network-blocking simulation doesn't interact cleanly
with Service Worker fetch interception (a known tooling quirk, not
specific to this), so the actual offline fallback path is the
standard try/catch pattern, verified by code review rather than a
simulated-offline screenshot.
2026-09-09 09:51:29 +02:00
randy 105651f005 Add activity type and a click-to-open track profile panel
CI / check (push) Successful in 41s
CI / deploy (push) Successful in 15s
Track properties gain activity_type (FIT: sport enum from the
session/sport message, Garmin's own table; GPX: <trk><type> passed
through as-is) alongside the existing distance/elevation/duration.

Clicking a track line now opens a bottom panel with a chart per
metric the track actually has (elevation always; speed/cadence/heart
rate only when present — FIT's record fields or GPX's device
extensions). TrackStats.build_profile computes cumulative distance
the same way for both formats so they're directly comparable. One
pointermove listener drives a synchronized crosshair across every
chart, a single tooltip listing all series, and a map marker at the
hovered point's position — hovering the chart shows where on the
track that moment was.
2026-09-07 13:38:49 +02:00
randy 7083cec36c Cache-bust app JS/CSS on each deploy
CI / check (push) Successful in 41s
CI / deploy (push) Successful in 18s
Browsers (and a caching proxy in front of production) were holding
onto /js and /css for hours, so a deploy could go unnoticed by an
already-open tab. A boot-time version query string on the entry
script/stylesheet, plus an import map remapping map.js's internal
module imports to the same versioned URLs, fixes that without editing
every JS file's import statements.
2026-09-06 21:41:57 +02:00
randy 5afb130c45 Switch to vector maps (MapLibre GL JS + PMTiles) and add RainViewer radar
Replace Leaflet/raster tiles with MapLibre GL JS: an online OpenFreeMap
style and an offline style built from a local PMTiles archive via
pmtiles.js and protomaps-themes-base (no glyphs/labels offline by
design, see docs/architecture.adoc). Frontend libraries are vendored
locally rather than loaded from a CDN so the app keeps working with no
internet access.

Also add an animated RainViewer precipitation radar overlay (toggle,
play/pause, scrub slider), matching the default time window of the
weather-radar-card Home Assistant integration.
2026-08-25 01:18:36 +02:00
randy 7329497e95 Initial scaffold: Sinatra map app with Tauri desktop shell
Sinatra backend serving a basic Leaflet map, wrapped in a Tauri
desktop shell that starts the same server in a native window.
2026-08-24 22:57:03 +02:00