Commit graph

1621 commits

Author SHA1 Message Date
FluxCD
4b10d63e67 chore: update prop image to git.mcintire.me/graham/prop:main-1777230205-f9e3dd5 [skip ci] 2026-04-26 19:07:55 +00:00
f9e3dd50e4
fix(rover-locations): use circleMarker; default marker icons aren't bundled 2026-04-26 14:03:10 -05:00
FluxCD
df475a8199 chore: update prop image to git.mcintire.me/graham/prop:main-1777229724-f63e679 [skip ci] 2026-04-26 18:59:35 +00:00
f63e679e34
feat(rover-locations): inline satellite map per location
Click a row's info area to expand a Leaflet mini-map centered on that
pin, defaulting to ESRI World_Imagery satellite (zoom up to 21) with
OSM/Topo toggles in a top-right layer control.
2026-04-26 13:55:08 -05:00
FluxCD
18e6088aa8 chore: update prop image to git.mcintire.me/graham/prop:main-1777227978-488b968 [skip ci] 2026-04-26 18:30:28 +00:00
488b968cdc
feat(rover): "only use known ideal locations" toggle
Adds a checkbox in the rover sidebar that constrains the suggested
candidate spots to lat/lon points tagged as :ideal in /rover-locations.
When enabled, Compute.run replaces grid cells with synthetic cells at
each ideal location's exact coordinates, inheriting the propagation
score from the nearest grid cell so atmospheric forecast still factors
in. Path-clearance, terrain, building, and canopy penalties run
unchanged on those snapped candidates.
2026-04-26 13:25:29 -05:00
FluxCD
5094ea0837 chore: update prop image to git.mcintire.me/graham/prop:main-1777227067-ca142d5 [skip ci] 2026-04-26 18:15:24 +00:00
ca142d5091
feat(rover-locations): show 10-char Maidenhead grid (computed, never stored) 2026-04-26 13:10:49 -05:00
140351c0f6
feat(rover-locations): add /rover-locations page with CRUD
New globally-shared list of rover-friendly (or off-limits) parking
spots. Anyone can view; logged-in users can add new locations and
edit/delete their own. Each location stores lat/lon, free-text notes,
and a status enum (:ideal | :off_limits).

  * Microwaveprop.Rover.Location schema + migration
  * Rover.list_locations/0, create_location/2, update_location/3,
    delete_location/2 (mutations require User; ownership-scoped)
  * /rover-locations LiveView in the public live_session — Add button
    is disabled for anonymous visitors, Edit/Delete buttons render
    only on the user's own entries
  * Tests for context + LiveView covering anonymous read, owner-only
    edit/delete, and form submission
2026-04-26 13:08:27 -05:00
FluxCD
ac98d35330 chore: update prop image to git.mcintire.me/graham/prop:main-1777226288-7105ac6 [skip ci] 2026-04-26 18:02:20 +00:00
7105ac61df
feat(rover): add canopy clutter penalty + Canopy.BulkFetch downloader
Rover ranking now applies a per-cell tree-canopy clutter penalty
(1 dB / 8 m, capped at 4 dB) so cells in dense forests get down-ranked
even when their per-station path clearance survives. PathTerrain
already accounts for trees ON the link path; this surfaces "I'm in a
forest, every direction is foliage."

Canopy.BulkFetch downloads Lang et al. 2020 10 m global canopy-height
3-degree COGs and slices each into 1° × 1° uint8 .canopy tiles via
gdal_translate, the runtime image now ships gdal-bin. Run on prod via:

  bin/microwaveprop rpc 'Microwaveprop.Canopy.BulkFetch.run(32.8, -97.0, 200)'

Tile cache lives under /data/canopy/. Already-sliced tiles are skipped.
2026-04-26 12:57:50 -05:00
FluxCD
1a85a5f540 chore: update prop image to git.mcintire.me/graham/prop:main-1777225924-4f647e7 [skip ci] 2026-04-26 17:55:18 +00:00
4f647e7894
feat(canopy): add tree-canopy height layer for path obstruction analysis
Adds Microwaveprop.Canopy module that reads 1° × 1° uint8 per-degree
canopy-height tiles (mirrors SRTM .hgt naming/layout, 30 m resolution).
Returns 0 m for areas with no tile on disk so the lookup is safe to
thread through the existing path-clearance pipeline before any data
lands.

Wires canopy into:
  - PathTerrain.obstacle_top: per-sample blocker = ground +
    max(building_m, canopy_m), so the rover ranks paths through forests
    as obstructed even when no buildings sit on the line
  - CandidateDetail profile: each sample now carries canopy_m alongside
    building_m
  - Rover-detail SVG: green "trees" polygon stacked on terrain, under
    the red building polygon
  - Path calculator elevation chart: green Tree Canopy dataset between
    terrain and buildings

Data prep (download Potapov 2019 / Lang 2022 GeoTIFF, slice to per-degree
uint8 tiles, drop in /data/canopy/) is a separate one-shot operation —
without tiles, lookups return 0 and the existing behavior is unchanged.
2026-04-26 12:51:48 -05:00
FluxCD
87d5175c6f chore: update prop image to git.mcintire.me/graham/prop:main-1777224612-aaf3115 [skip ci] 2026-04-26 17:34:04 +00:00
aaf3115fd6
feat(path): show buildings stacked on terrain in path elevation profile
Path calculator now queries Microsoft Building Footprints for the
tallest structure within 80m of each profile sample and renders a red
"Buildings" dataset on top of the terrain layer in the elevation chart.

Also normalize on-disk grid HRRR cells to include the legacy
:min_refractivity_gradient / :surface_refractivity / :ducting_detected
keys (mapped from :native_min_gradient and :duct_count). Path-calculator
crashed in prod with KeyError when a path's HRRR points came from the
new on-disk grid format instead of the DB profile shape.
2026-04-26 12:29:53 -05:00
FluxCD
031db7befc chore: update prop image to git.mcintire.me/graham/prop:main-1777223156-82b515e [skip ci] 2026-04-26 17:09:51 +00:00
82b515e88d
feat(rover): include step number/total in Calculate progress label 2026-04-26 12:05:32 -05:00
9f8c4d3b36
feat(rover): show current pipeline step next to Calculate spinner
Compute.run now accepts a `progress` callback and reports human-readable
labels before each pipeline step (loading grid, looking up elevation,
checking road access, scoring cells, etc.). RoverLive feeds it via
send/2 to itself and renders the latest label as small text to the left
of the Calculate button while it's running.
2026-04-26 12:02:40 -05:00
FluxCD
88d785be50 chore: update prop image to git.mcintire.me/graham/prop:main-1777222350-5e1155f [skip ci] 2026-04-26 16:55:48 +00:00
FluxCD
5d9e9e1084 chore: update prop image to git.mcintire.me/graham/prop:main-1777222118-5a4129f [skip ci] 2026-04-26 16:52:29 +00:00
5e1155f9da
feat(rover): add ESRI satellite imagery as base-layer option 2026-04-26 11:52:12 -05:00
5a4129ffca
fix(rover): show building height tooltip in feet 2026-04-26 11:48:15 -05:00
b97af8a5ce
feat(rover): show buildings stacked on terrain in path profile
Each profile sample now carries the tallest building height within 80m
of the path point. The candidate-detail SVG renders these as a red
polygon stacked on top of the terrain so blockage from buildings is
visible alongside ridges. max_obstacle_m now reflects building tops too,
so the clearance label downgrades when buildings sit on the link.
2026-04-26 11:47:24 -05:00
FluxCD
3b8d87c356 chore: update prop image to git.mcintire.me/graham/prop:main-1777220031-2f060b4 [skip ci] 2026-04-26 16:17:21 +00:00
2f060b4371
feat(rover): penalize cells surrounded by tall buildings (clutter)
Adds a per-cell building-clutter penalty so the algorithm down-ranks
spots in built environments where buildings on multiple sides
scatter/block signal regardless of which station you're aiming at.

Penalty = max_height_within_75m / 5 dB, capped at 6 dB. Path-clearance
already accounts for buildings ON the link path; this is the
"surrounded by stuff" signal.
2026-04-26 11:13:31 -05:00
FluxCD
6710140b72 chore: update prop image to git.mcintire.me/graham/prop:main-1777218902-a290c99 [skip ci] 2026-04-26 15:58:15 +00:00
a290c99184
fix(buildings): use explicit step in descending range in encode_quadkey
zoom..1 without a step raises a runtime warning in Elixir 1.19 and was
causing the BulkFetch Task to crash immediately. Changed to zoom..1//-1.
2026-04-26 10:54:44 -05:00
FluxCD
b17ff838ca chore: update prop image to git.mcintire.me/graham/prop:main-1777218305-0f15e99 [skip ci] 2026-04-26 15:49:03 +00:00
0f15e99fb9
feat(buildings): integrate MS footprints into path clearance + map render
Phase 2/3/5 of the building-blockage support:

* Parser streams csv.gz tiles into compact records (centroid, radius,
  height) — keeps RAM ~32 B per polygon, drops -1.0-height entries.
* Index buckets records into 0.01° (~1 km) grid cells in a public ETS
  table for concurrent reads; max_height_near/3 and records_near/3
  scan only the 9 nearest buckets.
* Loader lazily parses any cached quadkey before a Calculate so the
  scorer is terrain-only when tiles aren't on disk yet.
* Rover.PathTerrain now treats each path-sample obstacle as
  terrain_elev + max_local_building_height, so blocked LOS through
  recent construction actually reduces clearance.
* Selecting a candidate pushes a candidate_buildings event with the
  buildings near each rover→station path; the hook renders them as
  height-coloured circles (yellow <10 m, orange 10-30 m, red >=30 m)
  with a tooltip showing height in meters.
2026-04-26 10:44:46 -05:00
FluxCD
a150e02cd0 chore: update prop image to git.mcintire.me/graham/prop:main-1777217856-021608d [skip ci] 2026-04-26 15:41:01 +00:00
021608dc90
feat(buildings): bulk-prefetch tiles via BulkFetch.run/4 (works in prod)
Mix tasks don't run in production, so the orchestration lives in
Microwaveprop.Buildings.BulkFetch — callable from an attached iex on
the prod pod via:

    iex> Microwaveprop.Buildings.BulkFetch.run(32.8, -97.0, 500)

Default 500 mi radius around DFW yields ~440 candidate quadkeys at
zoom 9; only those present in the MS dataset index (typically a few
hundred over CONUS) get downloaded. Concurrency 4, per-task timeout
3 min, results logged with downloaded/cached/failed/missing counts.
The mix.task is kept as a dev-only thin wrapper.
2026-04-26 10:37:13 -05:00
5a97776fd5
feat(buildings): MS Global ML Building Footprints quadkey + tile fetcher
Phase 1 of building-blockage support: implements quadkey math (Bing/MS
tile encoding), looks up the MS dataset-links index for UnitedStates,
and downloads csv.gz quadkey tiles to /data/buildings (overridable via
:buildings_cache_dir). Index is cached 24h; tiles are written once and
reused. No path-analysis wiring yet — that's the next slice.
2026-04-26 10:33:25 -05:00
FluxCD
ff7f369e57 chore: update prop image to git.mcintire.me/graham/prop:main-1777215438-2f46d49 [skip ci] 2026-04-26 15:00:50 +00:00
2f46d49016
feat(rover): clear selected candidate + path lines on recalculate 2026-04-26 09:56:55 -05:00
556967db5a
feat(grid): show full Maidenhead label at subsquare zoom (em13sf, not sf) 2026-04-26 09:54:08 -05:00
FluxCD
b2aec7e8c8 chore: update prop image to git.mcintire.me/graham/prop:main-1777213888-3b1a13b [skip ci] 2026-04-26 14:34:35 +00:00
3b1a13b0f7
feat(rover): make candidate strip scroll horizontally + wheel-to-h-scroll 2026-04-26 09:31:10 -05:00
12e0106b7b
feat(rover): drop drive-time from candidate cards and detail header 2026-04-26 09:29:27 -05:00
535fda7cce
feat(rover): default Maidenhead grid overlay to on 2026-04-26 09:27:33 -05:00
7f77939fa4
feat(rover): add Maidenhead grid-square overlay (toggle in layers control) 2026-04-26 09:26:59 -05:00
af2ab3c969
fix(rover): bound Overpass with hard 8s deadline + 6h ETS cache by bbox
Prod logs showed road_proximity took 186 s on a single Calculate.
Req's receive_timeout only catches TCP idle, not slow streaming, so a
trickling Overpass response could not be cancelled. Wrap the call in a
Task.yield/shutdown deadline and cache successful results by 0.05°-rounded
bbox so adjacent Calculates skip Overpass entirely.
2026-04-26 09:25:08 -05:00
FluxCD
6a79458ea9 chore: update prop image to git.mcintire.me/graham/prop:main-1777212762-7d80bab [skip ci] 2026-04-26 14:16:11 +00:00
7d80babb18
feat(rover): snap top candidates to SRTM hilltops + add hillshade overlay 2026-04-26 09:12:24 -05:00
FluxCD
3c877b8b8e chore: update prop image to git.mcintire.me/graham/prop:main-1777212183-2f35005 [skip ci] 2026-04-26 14:10:09 +00:00
2f3500515a
feat(rover): overlay LOS line + label profile rover→station endpoints 2026-04-26 09:02:07 -05:00
FluxCD
ea502ee9e7 chore: update prop image to git.mcintire.me/graham/prop:main-1777158633-0d2cfb6 [skip ci] 2026-04-25 23:18:38 +00:00
0d2cfb68f1
feat(rover): show aim heading + path lines for each fixed station
- Detail panel now reads as an aiming card: "Aim 220° (SW) · clearance +12 ft"
  with the precise bearing in degrees, plus rover/obstacle elev on a second line
- Elevation profile labels swapped from min/max-of-profile to start (rover) and
  end (station) elevations, since their positions in the chart now match
- Map draws a dashed indigo polyline from the candidate to each selected
  station when a candidate is opened; cleared when the panel closes
2026-04-25 18:10:15 -05:00
4bfcff4e32
perf(rover): tighten Overpass timeout and log per-step compute timing
Calculate occasionally hung in production for ~30s. Adds per-phase
timing logs to Compute.run so the slow step is visible in pod logs,
and trims the Overpass road-proximity request to a 10s server-side
timeout / 12s receive timeout (no Req retries) so a slow road API
fails soft to no road penalty instead of stalling Calculate.
2026-04-25 18:05:46 -05:00
10cbd8dd48
fix(remote_ip): log IPv6 in canonical hex via :inet.ntoa
IPv6 segments were being printed in decimal (e.g. 10772:1985:1024:45::1
for 2a14:7c1:400:2d::1), which made request_id logs unreadable.
2026-04-25 18:05:46 -05:00
FluxCD
5c178ef4b7 chore: update prop image to git.mcintire.me/graham/prop:main-1777157172-eac817c [skip ci] 2026-04-25 22:56:35 +00:00