Viewshed Analyzer – Technical Details

Back to Demo

High-Level System Overview

  • Purpose: Compute the viewshed — the set of terrain cells visible from an observer — over a dense elevation grid, and render it as a visibility map.
  • Business problem: Line-of-sight analysis drives real decisions: cell-tower and radar placement, wildfire lookout siting, scenic-view real-estate valuation, and defilade planning.
  • Architecture: Razor Page controls → /api/v1/viewshed endpoint → IViewshedService → optional viewshed_kernel native library → managed fallback.
  • Technologies: .NET 10, Razor Pages, Bootstrap, vanilla JavaScript, C++20/CMake, P/Invoke, dense numeric arrays.
  • Scope: Ray-based viewshed over generated DEMs. Earth-curvature and atmospheric-refraction corrections and GeoTIFF/COG ingestion are future work.

Algorithm

  • Ray-casting viewshed: For every target cell a ray is walked from the observer; the maximum vertical angle to intervening terrain is tracked, and the target is visible only if its own angle clears that running maximum.
  • Observer height: The eye sits at the observer cell's elevation plus a configurable height, which materially changes coverage — a key thing to demonstrate interactively.
  • Deterministic sampling: Each ray takes max(|dx|, |dy|) steps and samples the nearest cell, keeping the native and managed passes numerically aligned.
  • Complexity: O(n · √n) for an n-cell grid — cheap enough for interactive grids, and an obvious SIMD/tiling candidate for large DEMs.

API Layer

  • Controller: ViewshedController is versioned at /api/v1/viewshed and allows anonymous demo access.
  • Endpoint: POST /api/v1/viewshed/compute accepts ViewshedRequestDto (elevation grid, observer position, observer height).
  • Response: ViewshedResultDto returns a row-major visibility grid (1 = visible, 0 = hidden) plus a visible-cell count and the echoed observer position.
  • Validation: The service enforces grid bounds, a cell cap, a positive cell size, an in-bounds observer, and a finite observer height.

Services and Native Boundary

  • Service: ViewshedService validates the request and dispatches to ViewshedNativeBridge when the kernel is present.
  • Native bridge: ViewshedNativeBridge passes a dense elevation array and a preallocated visibility buffer into C++ and maps the result to the DTO.
  • C++ library: native/viewshed_kernel exposes Viewshed_Compute, returning the visible-cell count.
  • Why C++ here: The per-cell ray walk is dense pointer arithmetic over contiguous elevation memory — exactly the workload where cache locality and vectorization matter.
  • Managed fallback: ViewshedService mirrors the ray-casting logic in C#, keeping the demo portable on Render with no native artifact.

Engineering Decisions and Tradeoffs

  • Ray-per-cell over sweep: The R2 ray method is easy to reason about and parallelize; a sweep-line/reference-plane method would be faster but harder to demonstrate.
  • Generated DEMs: Keeps the demo self-contained but skips georeferencing, nodata handling, and reprojection that a production pipeline needs.
  • Nearest-cell sampling: Simple and parity-friendly; bilinear sampling along the ray would reduce aliasing at a modest cost.
  • Future improvements: Earth-curvature correction, multi-observer cumulative viewsheds, GDAL-backed DEM ingestion, and tile/PNG output for map overlays.

Interview Discussion Points

  • How does observer height change coverage, and why is it non-linear over rough terrain?
  • What accuracy do you trade away with nearest-cell ray sampling versus interpolation?
  • How would earth curvature and refraction enter the angle computation at long range?
  • Where would you parallelize — per target cell, per ray, or per tile — and why?