Skip to content

feat: Add Map Generator tab for offline map tile downloading - #2599

Open
MartinovEm wants to merge 46 commits into
iNavFlight:maintenance-10.xfrom
MartinovEm:feature/map-generator
Open

feat: Add Map Generator tab for offline map tile downloading#2599
MartinovEm wants to merge 46 commits into
iNavFlight:maintenance-10.xfrom
MartinovEm:feature/map-generator

Conversation

@MartinovEm

@MartinovEm MartinovEm commented Mar 26, 2026

Copy link
Copy Markdown
Contributor

Add a new Map Generator tab that allows downloading offline map tiles for OSD map widgets on radios. Works without a flight controller connection.

Hero

Supported output targets: b14ckyy ETHOS Mapping Widget, Yaapu Telemetry (ETHOS and EdgeTX sub-targets). Map providers: OpenStreetMap, ESRI, Google (Street/Satellite/Hybrid/Terrain). Features: draw rectangle to select download area with live measurements, configurable zoom level range (1-20), sync tiles directly to mounted SD card, export tiles as ZIP archive, eject SD card from the app (Windows/macOS/Linux), local tile cache for faster repeated downloads, save/restore settings persistence, geocoder search, measure tool, right-click context menu.

Drawing Sync

@github-actions

Copy link
Copy Markdown

Branch Targeting Suggestion

You've targeted the master branch with this PR. Please consider if a version branch might be more appropriate:

  • maintenance-9.x - If your change is backward-compatible and won't create compatibility issues between INAV firmware and Configurator 9.x versions. This will allow your PR to be included in the next 9.x release.

  • maintenance-10.x - If your change introduces compatibility requirements between firmware and configurator that would break 9.x compatibility. This is for PRs which will be included in INAV 10.x

If master is the correct target for this change, no action is needed.


This is an automated suggestion to help route contributions to the appropriate branch.

@MartinovEm
MartinovEm force-pushed the feature/map-generator branch from 0f18283 to e6e75da Compare March 26, 2026 09:33
@MartinovEm
MartinovEm changed the base branch from master to maintenance-9.x March 26, 2026 09:37
@MartinovEm
MartinovEm force-pushed the feature/map-generator branch 5 times, most recently from 2861132 to bf1f572 Compare March 28, 2026 09:06
@MartinovEm

Copy link
Copy Markdown
Contributor Author

Update: Added INAV Terrain (SRTM) target

The Map Generator now supports generating INAV-compatible .DAT terrain files directly from the configurator — no external tools needed.

What it does

Select "INAV Terrain (SRTM)" as the target, draw a rectangle over your flying area, and the configurator downloads NASA SRTM 30m elevation data, converts it to .DAT files, and writes them directly to the flight controller's SD card (or exports as ZIP).

How it works

  1. Downloads SRTM1 HGT tiles from AWS (public domain NASA data)
  2. Decompresses gzip, performs bilinear interpolation at each grid point
  3. Packs into 2048-byte blocks with CRC16-XMODEM checksums
  4. Outputs one .DAT file per 1°×1° degree square (INAV-compatible format)

Features

  • 1°×1° terrain grid overlay with .DAT filename labels on the map
  • IndexedDB HGT cache — downloaded SRTM tiles persist across sessions, no re-download on repeat generation
  • Live altitude readout — hover the mouse to see SRTM elevation after generation (from cached data, no server requests)
  • SD card sync with FREESPAC.E auto-deletion, write progress with speed/ETA
  • ZIP export when no SD card is linked
  • SRTM coverage warning for selections beyond 60°N / 56°S
  • Completion screen shows the CLI command to enable terrain (set terrain_enabled = ON)
  • Ocean depths clamped to 0m (matches INAV firmware behavior)

Validation

The terrain math has been validated against 133M+ data points across 13 tiles (flat terrain to 2900m mountains). Flight-tested on real hardware (SpeedyBee F405 Wing, INAV 9.0.1) — sub-meter mean error vs source data.

Screenshots

Terrain mode — grid overlay and area selection:

selecting_area

Generating terrain files — sync modal with progress:

generating_tiles

Sync complete — files written to SD card:

terrain_synced

@MartinovEm

Copy link
Copy Markdown
Contributor Author

Fix: OSM tiles returning 403 in packaged builds (commit 97e9739)

While working on the Map Generator tab, I noticed that the packaged/release builds of the configurator get 403 errors from OSM tile servers (affects GPS tab, Mission Control — any tab that loads OSM tiles). Dev mode works fine.

Root cause: The packaged build loads the app using the local file protocol (mainWindow.loadFile(...)), so tile requests carry an invalid or empty Referer header and Chromium's generic User-Agent. OSM rejects these per their Tile Usage Policy (sections 3.1 and 3.4), which requires:

  • A unique User-Agent identifying the application (not a library/browser default)
  • A valid Referer header from web contexts

In dev mode, Vite serves via http://localhost, giving a valid Referer — so the issue only appears in packaged builds.

Fix: Added session.webRequest.onBeforeSendHeaders in js/main/main.js to set a proper Referer and User-Agent for OSM tile requests. 6 lines, no other code affected. Tested in a packaged win64 build — zero 403s.

This brings the configurator into compliance with OSM's tile usage requirements.

@MartinovEm
MartinovEm force-pushed the feature/map-generator branch from ead53a0 to 18be333 Compare April 19, 2026 21:43
@sonarqubecloud

Copy link
Copy Markdown

@sensei-hacker

Copy link
Copy Markdown
Member

Fix: OSM tiles returning 403 in packaged builds (commit 97e9739)

While working on the Map Generator tab, I noticed that the packaged/release builds of the configurator get 403 errors from OSM tile servers (affects GPS tab, Mission Control — any tab that loads OSM tiles). Dev mode works fine.

Root cause: The packaged build loads the app using the local file protocol (mainWindow.loadFile(...)), so tile requests carry an invalid or empty Referer header and Chromium's generic User-Agent. OSM rejects these per their Tile Usage Policy (sections 3.1 and 3.4), which requires:

  • A unique User-Agent identifying the application (not a library/browser default)
  • A valid Referer header from web contexts

In dev mode, Vite serves via http://localhost, giving a valid Referer — so the issue only appears in packaged builds.

Fix: Added session.webRequest.onBeforeSendHeaders in js/main/main.js to set a proper Referer and User-Agent for OSM tile requests. 6 lines, no other code affected. Tested in a packaged win64 build — zero 403s.

This brings the configurator into compliance with OSM's tile usage requirements.

That's really interesting. I have confirmed it does set the User-Agent as required -- but I checked in dev mode.
They say it doesn't have to set a referer since it's not a web site, but it doesn't hurt to send one.

Speaking of their TOS, I see also:

You must not:

Bulk download (“scrape”) tiles or offer prefetch features.

Do you have thoughts on that? I think the spirit of it is they don't want people downloading tiles they don't end up using, to reduce wasteful load on their servers. Would this feature, if merged, potentially cause that?

@MartinovEm

MartinovEm commented May 25, 2026

Copy link
Copy Markdown
Contributor Author

@sensei-hacker Thanks for pointing this out!

I agree with your interpretation of the OSM Standard tile policy regarding offline/bulk download. Before I change behavior in this PR, I want to confirm the intended direction with you:

  1. Keep OSM available for normal interactive map viewing, so the Map Generator tab follows the rest of the Configurator tabs, where OSM is used for previews and quick orientation.
  2. Restrict offline export flows (Sync to SD / ZIP) when OSM is selected.
  3. Show a clear in-app message explaining that OSM Standard does not permit offline bulk export, and ask users to choose another provider for export.

If you agree with this direction, I will implement it in this PR.

@sensei-hacker

sensei-hacker commented May 31, 2026

Copy link
Copy Markdown
Member

@sensei-hacker Thanks for pointing this out!

I agree with your interpretation of the OSM Standard tile policy regarding offline/bulk download. Before I change behavior in this PR, I want to confirm the intended direction with you:

  1. Keep OSM available for normal interactive map viewing, so the Map Generator tab follows the rest of the Configurator tabs, where OSM is used for previews and quick orientation.
  2. Restrict offline export flows (Sync to SD / ZIP) when OSM is selected.
  3. Show a clear in-app message explaining that OSM Standard does not permit offline bulk export, and ask users to choose another provider for export.

If you agree with this direction, I will implement it in this PR.

Seems reasonable to me. I see that for example MapTiler offers up to 5,000 sessions per month with their free tier, using the same API. Would it make sense to have a link to a documentation page that has a couple such providers?
perhaps even add a commercial provider or two (such as Maptiler) directly int he drop-down? These would be providers that are free for any reasonable number of requests.

@MartinovEm

Copy link
Copy Markdown
Contributor Author

@sensei-hacker Thank you for the suggestions! I've implemented the first part:

  • OSM is now preview-only -- the Sync to SD and Export as ZIP buttons are disabled when OSM Standard is selected, with an inline notice directing users to choose ESRI or Google for offline export. OSM still works for interactive map viewing, consistent with how it's used in other configurator tabs.

Regarding MapTiler: I'm starting work on adding it as a provider directly in the dropdown. Since it requires a personal API key, the UI will include a key input field (shown only when MapTiler is selected) with a short inline message guiding users to get a free key at maptiler.com -- no separate documentation page needed.

@sensei-hacker

Copy link
Copy Markdown
Member

Since it requires a personal API key, the UI will include a key input field (shown only when MapTiler is selected) with a short inline message guiding users to get a free key at maptiler.com -- no separate documentation page needed.

That will probably be more convenient than the existing API key field that is under the gear icon at top right of Configurator, won't it.

I guess it can be the same setting under the hood, but just put the input field right where people need it?

@MartinovEm

MartinovEm commented May 31, 2026

Copy link
Copy Markdown
Contributor Author

@sensei-hacker Thanks for the suggestion! Just to clarify — there isn't an existing map API key field under the gear icon. The gear settings only have the map provider for the Mission/GPS tabs (OSM / ESRI / MapProxy with URL + layer), which is separate from the Map Generator. So the key is new and lives right in the Map Generator tab where it's most useful, stored persistently in electron-store (mapgen_maptiler_key) so users only need to enter it once.

Here's a summary of what's been added:

New provider: MapTiler with 6 map types:

Type Notes
Satellite Aerial imagery (JPEG)
Hybrid Satellite + labels (JPEG)
Street MapTiler streets style (PNG)
OSM Style OpenStreetMap-rendered tiles — a compliant alternative for bulk export
Outdoor Topographic + outdoor features (PNG)
Topo Clean topo lines (PNG)

The key input shows only when MapTiler is selected, with an inline "Get a free key at maptiler.com" link. A help icon next to the Provider dropdown links directly to the README setup section. MapTiler's free tier covers 5,000 sessions/month which is plenty for personal use.

MapTiler_Key

Full setup guide with registration steps: README → MapTiler Setup

MartinovEm and others added 8 commits June 25, 2026 18:02
Add a new Map Generator tab that allows downloading offline map tiles for OSD map widgets on radios. Works without a flight controller connection. Supported output targets: b14ckyy ETHOS Mapping Widget, Yaapu Telemetry (ETHOS and EdgeTX sub-targets). Map providers: OpenStreetMap, ESRI, Google (Street/Satellite/Hybrid/Terrain). Features: draw rectangle to select download area with live measurements, configurable zoom level range (0-19), sync tiles directly to mounted SD card, export tiles as ZIP archive, eject SD card from the app (Windows/macOS/Linux), local tile cache for faster repeated downloads, save/restore settings persistence, geocoder search, measure tool, right-click context menu.
…dropdown

- Make INAV Terrain (SRTM) the default target and list it first, grouped
  under "Flight Controller" vs the "Radio Widgets" (b14ckyy, Yaapu).
- Rename the target label "Target Widget" -> "Target" (Terrain is a flight
  controller target, not a radio widget).
- Apply target mode at init so cold-starting / restoring into terrain no
  longer leaves the tab in tile-mode UI. updateTerrainMode() previously only
  ran via syncMapOptions() before map init, where it bailed on the !map guard.
- Suppress all tile-provider UI in terrain mode via a single tileMode flag:
  Google/OSM warnings, the MapTiler API key row, and Sync/ZIP export blocking.
  Terrain ignores the provider, so the default OSM provider must not show the
  OSM warning, keep the MapTiler key row visible, or disable export.
@MartinovEm
MartinovEm force-pushed the feature/map-generator branch from 6a2edb1 to 05e37a6 Compare June 25, 2026 15:52
@MartinovEm
MartinovEm changed the base branch from maintenance-9.x to maintenance-10.x June 25, 2026 15:52
@MartinovEm

Copy link
Copy Markdown
Contributor Author

Rebased onto maintenance-10.x (was 9.x). The diff is now clean and feature-only (24 files), with the commit history preserved. INAV Terrain is now the default target.

All six platform builds pass on my fork: https://github.com/MartinovEm/inav-configurator/actions/runs/28181247774

Replaces the hardcoded "EthosMaps_" prefix (misleading for Yaapu,
contradictory for EdgeTX) with {projectName}_{target}.zip, sanitized
for filesystem safety. Gives the previously-unused Project Folder Name
field a real purpose.
…/map-generator

# Conflicts:
#	js/appUpdater.js
#	js/configurator_main.js
@github-actions

github-actions Bot commented Jul 17, 2026

Copy link
Copy Markdown

Configurator test build ready — commit 8aef0e2

Download build artifacts for PR #2599

Available platforms (scroll to the Artifacts section at the bottom of the run page):

  • Windows x64 (ZIP, MSI) and x32 (ZIP, MSI)
  • macOS arm64 (ZIP, DMG) and x64 (ZIP, DMG)
  • Linux x64 (DEB, RPM, ZIP) and aarch64 (DEB, RPM, ZIP)

A GitHub login is required to download artifacts. Build is for testing only.

…indings

- Associate an aria-label with the MapTiler API key input (Web:InputWithoutLabelCheck).
- Hoist the drive-letter sanitization in ejectDrive above the platform branches so the
  macOS/Linux execFile calls receive the sanitized value too (jssecurity:S6350). The
  renderer only ever supplies a drive letter, so behavior is unchanged.
@MartinovEm

Copy link
Copy Markdown
Contributor Author

@sensei-hacker

The latest push resolves the Reliability finding (added an aria-label to the MapTiler key input) and 2 of the Security findings (hoisted the drive-letter sanitizer in ejectDrive so the macOS/Linux execFile calls get the sanitized value too).

The remaining 3 S2083 "path from user-controlled data" findings are on the writeFile / rm / pathExists IPC handlers. These are false positives in the Electron desktop context: the "user-controlled" path is the user's own chosen SD-card folder / save-dialog destination — writing tiles there is the feature's entire purpose, so the path can't be constrained to a fixed root. writeFile and rm are also pre-existing handlers already on maintenance-10.x (and shared by the backup/SITL features). Could you review and mark these as Safe? I'll adjust it if you'd prefer a different approach.

Yaapu maps widgets read Google-style tiles as JPEG — each maplib.lua's
google_tiles_to_path() returns "s_%d.jpg" — so the previous PNG output
was never found without hand-editing the widget Lua. Encode Yaapu tiles
as JPEG (quality 0.8) and change the output extension from .png to .jpg
for both ETHOS and EdgeTX (SD and ZIP). Update the on-screen example
path and the tile-count label to say JPEG. b14ckyy and terrain output
are unchanged.
On a fresh launch the tab opens in terrain mode (the default), but the
hidden provider dropdown still holds its startup default 'OSM'. The
Sync/ZIP click handlers checked provider === 'OSM' before the terrain
branch, so both buttons silently did nothing until the user switched to
a tile target and back. Move the isTerrainMode() check to the top of
both handlers so terrain opens its sync modal directly and ignores the
provider. Tile targets keep their OSM/MapTiler guards.
@MartinovEm

MartinovEm commented Jul 17, 2026

Copy link
Copy Markdown
Contributor Author

@sensei-hacker Pushed two small Map Generator fixes (Yaapu tiles now JPEG; terrain Sync/ZIP buttons work on cold start). All 7 builds are green. The SonarCloud failure is unchanged and expected — these fixes don't touch the flagged code, so it's still just the same 3 S2083 path findings on the writeFile/rm/pathExists handlers, which only need to be manually marked Safe. Whatever you think is the correct way to handle them, I'll go with it.

Tile x/y were computed and fetched at slippy zoom z-1 but written to the
folder named z, so folder "12" held zoom-11 imagery. The ETHOS/Yaapu map
widgets open folder N and address tiles at slippy zoom N, so they never
found the off-by-one tiles and showed a blank map for every provider.
Drop the -1 offset (tz = z) in calculateTiles() and buildTileList() so the
folder number equals the real slippy zoom, matching the widgets and the
original standalone tool. Verified on hardware.
…pped

fetchResizedTile() drew a fixed 256x256 source rect, but MapTiler serves
512px tiles, so only the top-left quarter was captured and MapTiler maps
came out as patchwork. Use the image's natural size as the source rect so
the whole tile is scaled to 100x100. No-op for 256px providers (ESRI,
Google, OSM); the ESRI labels overlay is left unchanged.
Add "Winter" to the MapTiler map-type list (style id winter-v4, PNG
raster), wired into the tile URL builder, the preview layer, and the
map-type dropdown. Other providers are unaffected.
…ource

Terrain .DAT files can now be built from Copernicus GLO-30 instead of
SRTM1. Validated against ICESat-2 ATL08 (n=10,777 paired points), GLO-30
reaches MAE 1.07 m on bare earth versus 2.01 m for SRTM, and it covers up
to 84°N where SRTM stops at 60°N. SRTM stays selectable as a fallback.

Copernicus ships Cloud-Optimized GeoTIFF, so geotiff (MIT) is added as a
dependency. Its 3600x3600 pixel-is-point grid lands on the same whole
arc-second graticule as SRTM .hgt, so filling the 3601x3601 grid the
converter expects is a direct pixel copy with no resampling; the tile's
south and east edge lines are copied from the neighbouring tiles, and the
north/east/north-east neighbours are fetched as thin strips to cover the
block grid's overshoot past the 1° square.

The .DAT encoder is untouched: block layout, CRC-16/XMODEM, geodesy
constants and filenames are unchanged, only the height values differ.
Copernicus and SRTM grids are cached under separate keys so switching
source cannot mix them. If the Copernicus service is unreachable the user
is asked before falling back to SRTM; the source is never switched
silently.

Per the WorldDEM-30 licence the required source and liability notices are
shown in the tab and written into the exported ZIP as NOTICE.txt.

Verified on N42E023: CRC valid on all 15,500 blocks, sampled heights match
the source GeoTIFF, no edge holes, and a real flight controller reads the
files and transitions seamlessly into neighbouring tiles generated from
SRTM.
@MartinovEm

MartinovEm commented Jul 26, 2026

Copy link
Copy Markdown
Contributor Author

Terrain: Copernicus GLO-30 is now the default elevation source (SRTM kept as fallback)

The Terrain target can now build .DAT files from Copernicus GLO-30 instead of SRTM1. SRTM stays selectable, and the .DAT format is unchanged — only the height values differ.

Why

I validated four public 30 m DEMs against ICESat-2 ATL08 laser ground returns (bare earth, orthometric EGM2008, cloud-free / snow-free / uncertainty < 10 m).

Pooled study — n = 10,777 paired points, block-bootstrap 95% CI, Wilcoxon signed-rank:

Source MAE vs ICESat-2 95% CI
Copernicus GLO-30 1.07 m [0.89, 1.31]
Re:Earth (Copernicus-derived) 1.10 m [0.91, 1.34]
SRTM / Mapzen (current) 2.01 m [1.68, 2.53]
JAXA ALOS AW3D30 3.06 m [2.77, 3.41]

Copernicus vs the current SRTM path: ΔMAE −0.94 m [−1.23, −0.75], p < 0.001.

stageB_stats_forest

Denser single-tile check — same product and same QA filters, but seven summers (2019–2025) of ground tracks over one tile, n = 16,962: Copernicus 1.08 m, Re:Earth 1.13 m, SRTM 2.29 m, JAXA 3.68 m. Copernicus is unchanged with 6× more truth data and the ranking holds. SRTM also carries a +1.77 m high bias against the laser, versus +0.47 m for Copernicus.

stageB_errormap_N42E023

Spatially, how each candidate differs from the SRTM baseline shipped today (source rasters, identical sampling grid):

stageA_quad_N42E023

Copernicus and Re:Earth are nearly identical, as expected — Re:Earth is Copernicus-derived. JAXA is visibly noisier and biased high.

Two honest caveats: at n ≈ 11k almost any real difference is "significant", so the number that matters is the effect size in metres, not the p-value — all four are sub-3 m DEMs. And the DEMs are fixed-epoch (SRTM 2000, Copernicus 2010–2014) while the laser data is 2019–2025, so real terrain change adds noise — but equally to all four, so the comparison stays fair. The practical wins are bare-earth accuracy, coverage to 84°N (SRTM stops at 60°N), and fresher data.

How it works

Copernicus ships Cloud-Optimized GeoTIFF, so geotiff (MIT) is added as a dependency. Its 3600×3600 pixel-is-point grid sits on the same whole-arc-second graticule as SRTM .hgt, so filling the 3601×3601 grid the converter already expects is a direct pixel copy — no resampling, no half-pixel shift. The south and east edge lines come from the neighbouring tiles; the N/E/NE neighbours are fetched as thin strips to cover the block grid's overshoot past the 1° square.

The .DAT encoder is untouched — block layout, CRC-16/XMODEM, geodesy constants and filenames are identical.

  • Copernicus and SRTM grids are cached under separate keys, so switching source can't mix them.
  • If the Copernicus service is unreachable, the user is asked before falling back to SRTM — never switched silently.
  • A missing tile (ocean / outside coverage) is treated as no data, not an error.
copernicus_inav_cinfig

Licence

Copernicus WorldDEM-30 permits redistribution of derived products, including commercially, conditional on two verbatim notices. These are shown in the tab and written into the exported ZIP as NOTICE.txt (the licence ties them to distribution, so a direct write to the user's own SD card doesn't carry the file). No endorsement by the EU, ESA, Airbus or DLR is implied.

Verification

On N42E023 (15,500 blocks / 31,744,000 bytes — byte-layout identical to the SRTM output):

  • CRC-16 valid on all 15,500 blocks; magic, version and spacing correct
  • sampled heights match the source GeoTIFF exactly, including the south edge (cross-checked against the neighbouring tile)
  • no edge holes — 0 zero-heights out of 13,888,000
  • Copernicus vs SRTM .DAT output: mean |Δ| 2.72 m
  • tested on a real flight controller: the files load, and flying from a Copernicus tile into a neighbouring tile generated earlier from SRTM is seamless

…loader

Use optional chaining for the not-found checks and Object.hasOwn() for the
tile-cache lookup. Behaviour is unchanged.
@sonarqubecloud

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
E Security Rating on New Code (required ≥ A)

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants