Skip to content

feat: add Tianditu elevation provider, GCJ-02 conversion, and GPU acceleration - #1238

Open
ChidcGithub wants to merge 27 commits into
louis-e:mainfrom
ChidcGithub:master
Open

feat: add Tianditu elevation provider, GCJ-02 conversion, and GPU acceleration#1238
ChidcGithub wants to merge 27 commits into
louis-e:mainfrom
ChidcGithub:master

Conversation

@ChidcGithub

@ChidcGithub ChidcGithub commented Aug 4, 2026

Copy link
Copy Markdown

Summary

This PR adds two independent features:

1. Tianditu (天地图) elevation provider for China

  • GCJ-02 ↔ WGS-84 bidirectional coordinate conversion (src/coordinate_system/gcj02.rs)
  • Tianditu DEM provider (src/elevation/providers/tianditu.rs, ~30 m resolution via Terrain-RGB tiles)
  • Auto-selected when TIANDITU_TOKEN is set and the bbox overlaps China; graceful fallback to Mapterhorn otherwise
  • GUI settings: elevation toggle + API key input (off by default); 19 locale files updated

2. GPU acceleration (opt-in: --features gpu + ARNIS_GPU=1)

GPU-resident elevation post-processing pipeline built on wgpu compute shaders:

  • Passes: 5×5 median/MAD anomaly repair (gpu_anomaly.wgsl), Jacobi NaN fill (gpu_nan.wgsl), separable Gaussian blur (gpu_blur.wgsl) — a single upload, multiple passes, a single download
  • Cross-platform: DX12/Vulkan on Windows, Vulkan on Linux, Metal on macOS
  • Transparent CPU (rayon) fallback when no adapter is available, the grid exceeds device buffer limits, or a driver error occurs (catch_unwind guards every entry point)
  • GUI toggle (off by default) with a confirmation warning for small areas (< 25 km²)

Building & enabling the GPU path

The GPU code lives behind the optional gpu Cargo feature (wgpu + pollster) and is off by default:

# CLI build with GPU support
cargo build --release --no-default-features --features gpu

### GUI build with GPU support
cargo build --release --features gpu

At runtime the GPU path stays opt-in:

  • CLI: set the ARNIS_GPU=1 environment variable, e.g. ARNIS_GPU=1 arnis --bbox="..." --path="..."
  • GUI: enable GPU acceleration in Settings (this sets ARNIS_GPU in-process; it has an effect only on builds compiled with the gpu feature)

Requirements: any GPU with a DX12 (Windows), Vulkan (Windows/Linux), or Metal (macOS) driver — no CUDA or vendor SDK needed. Without the feature flag, without the runtime opt-in, or when no suitable adapter / buffer limit is available, every step transparently runs on the existing CPU (rayon) path and behaviour is unchanged from upstream.

Benchmarks

RTX 4060 Laptop, terrain-only mode, 16×11 km bbox → 16384×11145 grid (183M cells):

Phase CPU GPU Speedup
Anomaly repair (5×5 median/MAD, ≤10 passes) 14.8 s 3.0 s 4.9×
Land-cover repair (two Gaussian blurs) 23.6 s 4.9 s 4.8×
Elevation fetch + post-processing 51.2 s 20.5 s 2.5×
Total wall time ~94 s ~59 s ~1.6×

This PR also fixes a full-grid connected-component analysis that ran even when no OSM water elements exist (−23 s on large terrain-only runs, benefits CPU and GPU paths alike).

Note on earlier revisions: the previous iteration of this PR reported near-zero GPU gains. Profiling showed the GPU path silently panicked on grids above 128 MB (wgpu's default max_storage_buffer_binding_size) and fell back to CPU — so both sides of those tables measured the CPU path. Device limits are now requested from the adapter, and the passes stay resident on the GPU instead of round-tripping per call.

Correctness fixes in the GPU path

  • The blur now skips non-finite samples and renormalises kernel weights, matching the CPU implementation exactly (the built-up smoothing deliberately blurs grids containing NaN water cells; the old shader also clamp-extended edges instead of renormalising)
  • Gaussian kernel centred at size/2.0 like the CPU (the previous kernel was asymmetric)
  • NaN-fill and anomaly passes reproduce the CPU early-break semantics via an atomic change counter

Testing

  • 365 unit tests pass, including new CPU/GPU output-equivalence tests; they skip cleanly on GPU-less CI runners
  • Regression tests for the water-override early-out (empty/non-water elements leave the grid untouched; polygons and waterways still fill)
  • The project's existing pr-benchmark.yml workflow is intentionally left untouched — GPU benchmarking runs locally via bench_gpu.ps1 since GitHub-hosted runners have no GPU. The GPU numbers above are from local testing

Files changed

  • New: gcj02.rs, tianditu.rs, gpu.rs, gpu_blur.wgsl, gpu_anomaly.wgsl, gpu_nan.wgsl, bench_gpu.ps1
  • Modified: coordinate system, elevation pipeline, land cover, GUI, 19 locale files, Cargo.toml

- Add GCJ-02/WGS-84 bidirectional coordinate conversion module
- Implement Tianditu elevation provider for China
- Add Tianditu settings to GUI (toggle + token input)
- Update 19 locale files with new keys
- Fix broken en.json locale file
@ChidcGithub
ChidcGithub force-pushed the master branch 2 times, most recently from 8e57c96 to f3cac1c Compare August 4, 2026 12:11
- Add optional gpu feature (wgpu 22 + pollster) for separable Gaussian blur
- WGSL compute shader with horizontal/vertical passes
- Auto-fallback to CPU (rayon) when GPU unavailable
- Cross-platform: DX12/Vulkan on Windows, Vulkan on Linux, Metal on macOS
- Benchmark workflow: add Windows/macOS/Linux matrix with fail-fast disabled
- Trigger benchmark on push to master/main and workflow_dispatch
@ChidcGithub ChidcGithub changed the title feat: add Tianditu elevation provider and GCJ-02 coordinate conversion feat: add Tianditu elevation provider, GCJ-02 conversion, and GPU acceleration Aug 4, 2026
@ChidcGithub ChidcGithub changed the title feat: add Tianditu elevation provider, GCJ-02 conversion, and GPU acceleration feat: add Tianditu elevation provider, GCJ-02 conversion, and GPU acceleration (Unfinished) Aug 4, 2026
apply_osm_water_override built the water context (connected-component
analysis over the whole land-cover grid) before checking whether any
element could trigger a fill. In terrain-only mode or water-free bboxes
this was pure overhead: ~23 s on a 16384x11145 grid (~20% of total).

Add a has_water_override_candidates early-out mirroring the existing
bridge_repair pattern; the predicate reuses is_water_polygon_way and
is_water_relation so behaviour is identical whenever water elements do
exist.

Add regression tests: empty/non-water elements leave the grid untouched;
water polygons and waterway lines still fill through the public entry.
The previous GPU path never actually ran on large grids and produced
mismatched output when it did:

- Device limits: request the adapter's real buffer limits instead of the
  wgpu defaults. The default 128 MB max_storage_buffer_binding_size made
  every grid above ~33M cells panic at bind time and silently fall back
  to CPU (a 16384x11145 f32 grid is 730 MB).
- gpu_blur.wgsl rewritten to match CPU semantics exactly: non-finite
  samples are skipped and kernel weights renormalised (the built-up
  smoothing deliberately blurs grids containing NaN water cells), edges
  renormalise instead of clamp-extending, and the kernel is centred at
  size/2.0 like the CPU (the old kernel was asymmetric at size/2).
- New resident passes between one upload/download: gpu_anomaly.wgsl
  (5x5 median/MAD repair) and gpu_nan.wgsl (Jacobi NaN dilation), with
  an atomic change counter preserving the CPU early-break semantics.
  NaN fill falls back to the CPU loop if the iteration cap is hit.
- Parallelise f64<->f32 conversion, grid flatten/reshape, and the
  built-up blend loop with rayon; warm up the GPU device during data
  download so adapter init stays out of the timed phases.

Large-area benchmark (16x11 km bbox, 183M-cell grid, terrain-only,
RTX 4060 Laptop):
  anomaly repair      14.8s ->  3.0s
  land-cover repair   23.6s ->  4.9s   (two Gaussian blurs)
  terrain total       51.2s -> 20.5s
  overall wall        ~94 s -> ~59 s

CPU/GPU output-equivalence unit tests included; they skip cleanly on
GPU-less CI runners.
@ChidcGithub ChidcGithub changed the title feat: add Tianditu elevation provider, GCJ-02 conversion, and GPU acceleration (Unfinished) feat: add Tianditu elevation provider, GCJ-02 conversion, and GPU acceleration Aug 5, 2026
The fork-specific GPU benchmark matrix rewrote pr-benchmark.yml for
local experimentation (push triggers, no PR comment step). That removed
the project's automated PR benchmark comments, baseline verdicts, and
the retrigger-benchmark command — none of which this PR intends to
change. Restore the upstream file so the PR stays focused on the
Tianditu/GPU features; GPU numbers are produced locally via
bench_gpu.ps1.
@ChidcGithub

Copy link
Copy Markdown
Author

Benchmark results (CSV)

Committed to this branch as benchmark_gpu_large.csv — download: benchmark_gpu_large.csv

scenario,wall_cpu_s,wall_gpu_s,gen_cpu_ms,gen_gpu_ms,repair_cpu_ms,repair_gpu_ms,speedup
large,165.4,63.9,52128,41617,23012,4717,1.25

Measured locally on an RTX 4060 Laptop via bench_gpu.ps1 (terrain-only mode, 16×11 km bbox → 16384×11145 grid, ~183M cells). wall includes elevation fetch + post-processing; repair is the land-cover repair phase containing the two Gaussian blurs; speedup is the generation-time ratio (CPU / GPU).

@ChidcGithub

ChidcGithub commented Aug 5, 2026

Copy link
Copy Markdown
Author

A note on benchmark variance

The numbers in the table above and in the attached CSV come from individual local runs, and some variance between runs is expected:

  • Wall time includes elevation/land-cover tile downloads, so it fluctuates with network conditions and tile-cache state (cold vs. warm cache can differ by tens of seconds).
  • Generation time varies with system load and thermal state (±10% between runs on the same machine is normal).

The phase-level conclusions are stable across runs, though: the GPU-resident pipeline consistently cuts anomaly repair (~15s → ~3s) and land-cover repair (~24s → ~5s), and the relative speedups hold regardless of absolute wall-time noise. Treat the exact figures as indicative rather than precise.

@ChidcGithub

ChidcGithub commented Aug 5, 2026

Copy link
Copy Markdown
Author

Due to GitHub Actions permission issues, the benchmark comment could not be published properly. However, the benchmark workflow actually ran successfully, so I have pasted the generated results here.

@ChidcGithub

Copy link
Copy Markdown
Author

⏱️ Benchmark run finished in 0m 50s
🏗️ Generation time: 19s (excl. data fetching)
🧠 Peak memory usage: 1177 MB

📈 Compared against baseline: 18s time, 1160 MB memory
🧮 Delta: 32s time, 17 MB memory
🔢 Commit: ef00d6d

🟢 Generation time is unchanged.
🟢 Peak memory is unchanged.

📅 Last benchmark: 2026-08-05 04:07:03 UTC

@ChidcGithub

Copy link
Copy Markdown
Author

@louis-e 👋 This PR is now ready for your review. It adds a Tianditu elevation provider, supports GCJ-02 conversion, and enables GPU-accelerated processing and has passed all local tests. Could you please take a look when you have a moment? Let me know if you need any additional context or testing steps. Thanks in advance! =)

@ChidcGithub

Copy link
Copy Markdown
Author

Update: ⏱️ Benchmark run finished in 0m 38s
🏗️ Generation time: 19s (excl. data fetching)
🧠 Peak memory usage: 1190 MB

📈 Compared against baseline: 18s time, 1160 MB memory
🧮 Delta: 20s time, 30 MB memory
🔢 Commit: 2ab5cc4

🟢 Generation time is unchanged.
🟢 Peak memory is unchanged.

📅 Last benchmark: 2026-08-06 02:38:29 UTC

@louis-e

louis-e commented Aug 9, 2026

Copy link
Copy Markdown
Owner

Hey there, thanks a lot! :) I'll look into it as soon as I have time for it!

@ChidcGithub

Copy link
Copy Markdown
Author

@louis-e Can you please review it as soon as possible, because other commits are starting to make breaking changes to this code?

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