Parse ADIF tagged-data format with fuzzy frequency-to-band matching
(nearest microwave band for any freq >= 900 MHz). Supports STATION_CALLSIGN,
OPERATOR, CALL, GRIDSQUARE, MY_GRIDSQUARE, FREQ, BAND, QSO_DATE, TIME_ON.
Same dedup and preview/commit flow as CSV upload.
Also fix select dropdown text alignment in daisyUI.
- Rename all .js files to .ts in assets/js/
- Add interfaces for hook state, data structures, and event payloads
- Add type annotations to function parameters and return types
- Create type declarations for vendor deps (Leaflet, Chart.js, Phoenix, topbar)
- Update tsconfig.json for strict TypeScript checking
- Update esbuild entry point to app.ts
Backfill page now sums child partition stats for partitioned tables
like hrrr_profiles (relkind 'p') which were excluded by the relkind
'r' filter. Contacts map defers Leaflet init to requestAnimationFrame
so the flex container has dimensions before map initialization.
Replace the JS-built Leaflet control panel with a server-side
sidebar (desktop) and mobile floating controls. Band checkboxes,
callsign filter, and nav links now match the main propagation map
layout. Filter state managed by LiveView, pushed to JS via events.
- Callsign input filters contacts where either station matches
(case-insensitive substring match, debounced 300ms)
- Band checkboxes now show per-band count of visible contacts
- All/None buttons for quick band selection
- Header count updates dynamically with filters
- Complete rewrite: rebuilds map on any filter change instead of
toggling individual layers (simpler, handles cross-filter correctly)
When clicking a grid point, fetches the latest NEXRAD composite
reflectivity and identifies rain cells within 300 km that could
enable rain scatter contacts. Shows:
- Scatter classification (excellent/good/marginal/none)
- Top 3 cells with dBZ, distance, bearing, and relative signal
- Colored circle markers on the map at rain cell locations
- Markers sized by reflectivity, colored by intensity
Uses simplified bistatic radar equation accounting for reflectivity,
frequency-dependent scattering (Rayleigh/Mie), and R^4 path loss.
NEXRAD cells sampled every ~5 km within bounding box for efficiency.
Terrain elevations now include the earth bulge correction, so the
profile visually humps up in the middle relative to the straight
LOS beam — showing how the curved earth rises into the signal path.
Uses actual k-factor from HRRR refractivity when available, otherwise
standard 4/3. For a 300 km path at k=4/3, the midpoint bulge is
~2,650 m (8,700 ft).
When clicking a grid point with ducting, the panel now shows each
duct layer with base-top height in feet, thickness in meters, and
minimum trapped frequency. Data flows from Duct.analyze through
the scoring factors as a ducts array.
- Update all 9 hardcoded weights to match recalibrated values
- Add missing PWAT factor (11.3% weight) to FACTOR_META and FACTOR_ORDER
- Reorder factors by weight (highest first)
- Fix pressure explanation (low pressure is better, not rising)
- Add PWAT factor explanation (beneficial vs harmful by band)
- Show duct info panel when native data detects ducting layers
(count, thickness, minimum trapped frequency)
- Add CommaNumber JS hook for live comma formatting while typing
frequency MHz in the beacon form
- Strip commas server-side before changeset validation
- Order beacon list by most recently added (desc inserted_at)
- Round lat/lon to 6 decimal places in changeset and display
- Round height_ft to integer in changeset and display
- Display coords at 6 decimal places on show page
- Beacon map defaults to just the marker at zoom 11. A daisyUI toggle
switch shows/hides the estimated-coverage cell layer; the coverage
info line and tier legend are hidden unless the toggle is on.
- Beacon detail list replaced with a compact dl grid (2-4 columns).
Lat/lon rounded to 5 decimals, power/height/beamwidth formatted via
Beacon.format_mw/1 so no more "2.5e3" scientific notation or "280.0".
format_mw/1 hoisted onto the schema so index and show share it.
- About page no longer uses prose classes (this project doesn't load
@tailwindcss/typography). Headings, lists, and paragraphs now use
explicit utility classes so they render as intended.
- /map now centers on the visitor's Cloudflare geolocation when the
cf-iplatitude/cf-iplongitude headers are present, falling back to DFW.
Initial HRRR score bounds track the chosen center.
- Added /about link to the /map side navigation.
- Renamed "Propagation Map" label to "Propagation Prediction Map" in
the /map page title, sidebars, and the /weather back-link.
- Populated /about with mission, approach, stack, roadmap, and a live
stat grid fed from DB counts (estimates via pg_class.reltuples for
the multi-million-row tables).
- Anonymous users can submit beacons (held pending admin approval)
- Added notes field to beacons (textarea on form, shown on detail page)
- Added Beacons link to /map sidebar nav (mobile + desktop),
removed stale Rover Planner link
- Moved /backfill to /admin/backfill under the admin live_session
- Beacon detail map zooms in two extra levels after fitBounds
Replace the idealised concentric-circle range rings with a realistic
per-HRRR-grid-cell reception map. For every 0.125° grid point within
the band's exceptional range, the estimator computes great-circle
distance, FSPL + atmospheric loss, and applies a cell-specific score
adjustment of (50 - score) * 0.3 dB — score 100 gives a 15 dB ducting
boost, score 0 a 15 dB penalty. Cells below the -145 dBm detection
floor are dropped.
The beacon map hook now renders each surviving cell as a 0.125°
filled rectangle with a tooltip showing tier, Rx dBm, distance, and
HRRR score, so the coverage footprint bulges where ducting conditions
are good and cuts off where they aren't, instead of being a perfect
circle. Falls back to score 50 for cells that don't have HRRR data
yet, so the plot is always useful.
Also fixes the prior all-grey-tiles regression by restoring an
explicit setView() at map creation and adding a post-mount
invalidateSize() for when the map is placed inside a flex container.
- Add on_the_air boolean to beacons (default true); surfaced as a
checkbox on the form, a badge on the index, and in the detail list.
- Render a Leaflet map on the beacon show page with a marker at the
beacon's lat/lon tooltipped with callsign + frequency. Marker is
green when on air, gray when off.
- Compute and draw a reception-range estimate as concentric signal-
strength rings. New Microwaveprop.Beacons.RangeEstimate solves a
link budget (FSPL + O2/H2O absorption from BandConfig) at five RX
thresholds (-100 to -145 dBm), then scales by 0.5 + score/100 from
the latest Propagation.point_detail at the beacon's grid square, so
current HRRR conditions shift the rings in or out.
- Re-enable the hourly PropagationGridWorker cron and freshness
monitor in dev.exs so dev actually has HRRR-backed scores to feed
the new estimator.
- Move controls (band selector, grid toggle, nav links) to a docked
right sidebar on desktop, keep floating overlay on mobile
- Point detail panel floats over the map bottom-left instead of inside
the narrow sidebar
- Add collapse/expand toggle for the sidebar
- Add auth links (login/logout/settings) to sidebar
- Wrap public live routes in live_session with UserAuth on_mount
- Use daisyUI menu lists for sidebar navigation
Extract shared Maidenhead grid overlay into maidenhead_grid.js so both
the main map and rover planner use the same implementation. Wire up the
toggle_grid event and checkbox on the rover page matching the main map.
Subtle hint appears in top-right corner (desktop only) when the
detail panel is open. Fades in when panel shows, fades out on ESC.
Hidden on mobile where the X button is more appropriate.
Extract GridSquare class and updateGridOverlay from propagation_map_hook
into shared maidenhead_grid.js module. Rover map now shows the same
zoom-adaptive Maidenhead grid overlay as the main /map page — red
rectangles with labels that switch between field (2-char) and square
(4-char) based on zoom level. Updates on pan/zoom.
Toggle button hides/shows the grid overlay.
- Toggle button for Maidenhead grid overlay (on by default)
- Coverage candidates now at 0.25° resolution (~28 km) instead of
2°×1° Maidenhead grids, matching HRRR propagation grid density
- Coverage rectangles render as 0.25° cells for finer detail
- Grid overlay is separate from coverage coloring
Rewrites rover planner to automatically color the map after stations
are entered. No manual stop selection needed — the system evaluates
every Maidenhead grid in range and scores them by:
- SRTM terrain analysis to each station (diffraction loss, LOS verdict)
- HRRR propagation forecast (ducting, refractivity, atmospheric conditions)
- Distance to stations vs band range limits
- Propagation boost: good conditions (score 80+) make blocked paths
viable through ducting, matching the app's core beyond-LOS prediction
Coverage score formula: boosted path quality 30% + propagation 30%
+ distance 20% + station count 20%. Colored grid squares on the map
show best (green) to worst (red) operating positions.
Sidebar shows top 10 grids ranked with: workable station count,
propagation score, best operating hour, 18-hour forecast sparkline.
Click a grid for per-station detail: distance, terrain verdict,
diffraction loss.
URL stores stations + band for sharing: /rover?stations=W5ISP,K5TR&band=10000
Switch from getCurrentPosition (which looped via push_patch) to
watchPosition with a 60-second throttle. Break the server-side
request_gps loop by skipping re-request when coords already exist.
Interactive map-based tool for planning rover operating positions.
Rovers enter stationary stations they want to work, then click the
map to evaluate candidate operating grids.
Features:
- Add stationary stations by callsign or grid square
- Click map to add operating positions (snaps to Maidenhead grid)
- Async SRTM terrain analysis: each stop computes terrain profile
to every station, shows CLEAR/BLOCKED verdict + diffraction loss
- Terrain lines on map: green=workable, red=blocked, dashed=marginal
- Propagation score and 18-hour forecast sparkline per stop
- Route summary: driving distance, unique grid-station pairs
- Band selector affects range limits and propagation scores
- Numbered waypoint markers with score-colored backgrounds
New files:
- Geo module (shared haversine/bearing, used by PathLive too)
- RoverLive with fullscreen map + sidebar
- RoverMap JS hook with grid overlay, station/stop markers, route line
- Nav links in header and map control panel
- "My Location" button with text label (was just an icon)
- Two share buttons after results:
- "Copy Link" — fixed coordinates, exact result
- "Copy Link (use viewer's GPS)" — source=gps, dynamic per viewer
- CopyLink JS hook copies full URL to clipboard with "Copied!" feedback
Bookmarking /path?source=gps&destination=K5TR&band=10000 will use
the viewer's device GPS to resolve the source location on load.
The LocateMe hook handles both button clicks and server-pushed
request_gps events. Once coords are resolved, the URL is updated
with actual lat,lon and calculation runs automatically.
- TX/RX height in feet (converted to meters for terrain analysis)
- TX power in dBm with mW equivalent display
- TX gain and RX gain (dBi) inputs
- Power budget: TX power + TX gain - feed loss = EIRP - path loss
+ RX gain = RX power vs CW sensitivity = margin
- Loss budget: FSPL + gaseous (O2+H2O) + diffraction + rain
with per-km gas rate detail
- GPS "Locate Me" button using browser geolocation API
- Lat,lon coordinate pairs accepted as input
- Weather displayed in F with C below
- LiveStash preserves form inputs across page reloads
- Terrain cross-section with beam line and Fresnel zone
Collapsible control panel with hamburger toggle, detail panel as
bottom sheet, compact legend moved to top-right, and scaled timeline
buttons for narrow viewports.
Without a distance limit, the BFS would expand across the entire grid
when conditions were broadly favorable, producing a polygon covering
most of CONUS.
- Mark duct closest to endpoint elevations as likely signal path
- Likely duct drawn with solid borders, bold label, stronger fill
- Other ducts shown dimmed with dashed borders
- Fix path info layout: table columns prevent El wrapping
- Surface observations as compact table, limited to 5 nearest stations
- Use all HRRR profiles along path (start, mid, end) for duct detection
- Fall back to sounding ducts when HRRR too coarse to detect layers
- Merge overlapping ducts, show top 3 strongest with source labels
- Limit surface observations to 5 closest stations along path
- Compact solar conditions as inline row
- Compact band/mode/distance info line
- Fix button text to "Flag as Invalid"
- Overlay HRRR duct layers on elevation profile chart (green bands)
- Add flagged_invalid field to contacts with toggle button on detail page
- Fix prune_old_grid_profiles to only delete 0.125° grid profiles,
preserving QSO-linked HRRR data at arbitrary positions
- Auto-enqueue HRRR fetch when viewing contact without HRRR data (<48h)
- Compact solar conditions and contact details layout
- Add band/mode/distance info line to elevation profile section
- Leaflet map on contact detail pages showing both station locations
- SRTM elevation profile with Chart.js: terrain, LOS, Fresnel zone
- Path info: distance, azimuth, elevation angle in both directions
- Compact 4-column grid layout for contact details
- Contact map page with all contacts plotted
- Rename QSOs to Contacts in page header
- Wrap algo page in standard layout
- All menus and UI text now say Contacts instead of QSOs
- Add 68, 122, 134, 241 GHz bands to submit form and validation
- Add info box on submit page explaining why contacts matter
- Larger submit button with icon
- Make HRRR partition migration idempotent for partial re-runs