From 824fe480aa8fd0ca049251b209e01d3dc5bb706f Mon Sep 17 00:00:00 2001 From: Graham McIntire Date: Wed, 6 May 2026 17:27:56 -0500 Subject: [PATCH] fix(coverage): use gdalwarp for elevation grid + sanitise UI errors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit gdal_translate doesn't accept -t_srs (only -projwin_srs for input), so reprojection silently fell over against any non-WGS84 source COG (3DEP Albers tiles). Switch to gdalwarp which actually reprojects. Also wrap raw GDAL stderr behind a generic user-facing message and log the technical detail server-side — operators shouldn't see the gdal_translate usage banner pasted into a coverage failure card. --- lib/towerops/lidar/reader.ex | 18 +++++++++++------- lib/towerops/workers/coverage_worker.ex | 10 +++++++++- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/lib/towerops/lidar/reader.ex b/lib/towerops/lidar/reader.ex index 93167bf3..9f464f11 100644 --- a/lib/towerops/lidar/reader.ex +++ b/lib/towerops/lidar/reader.ex @@ -58,26 +58,30 @@ defmodule Towerops.Lidar.Reader do @spec elevation_grid(Tile.t(), {number(), number(), number(), number()}, number()) :: {:ok, map()} | {:error, term()} def elevation_grid(%Tile{url: url}, {west, south, east, north}, cell_size_deg) do + # gdalwarp reprojects to EPSG:4326 on the fly. gdal_translate cannot + # reproject — it only crops in the source CRS — so 3DEP Albers tiles + # would otherwise come back in projected metres. args = [ "-of", "AAIGrid", - "-projwin_srs", + "-t_srs", "EPSG:4326", - "-projwin", + "-te", to_string(west), - to_string(north), - to_string(east), to_string(south), + to_string(east), + to_string(north), "-tr", to_string(cell_size_deg), to_string(cell_size_deg), - "-t_srs", - "EPSG:4326", + "-r", + "bilinear", + "-overwrite", vsicurl(url), "/vsistdout/" ] - case run("gdal_translate", args) do + case run("gdalwarp", args) do {output, 0} -> parse_aaigrid(output) {output, code} -> {:error, {:gdal, code, output}} end diff --git a/lib/towerops/workers/coverage_worker.ex b/lib/towerops/workers/coverage_worker.ex index 4bfb7cd9..03ff8f42 100644 --- a/lib/towerops/workers/coverage_worker.ex +++ b/lib/towerops/workers/coverage_worker.ex @@ -431,7 +431,15 @@ defmodule Towerops.Workers.CoverageWorker do defp describe_error({:terrain_unavailable, :nodata}), do: "LIDAR tiles for this area returned no data." - defp describe_error({:terrain_unavailable, reason}), do: "Terrain fetch failed: #{inspect(reason)}" + defp describe_error({:terrain_unavailable, {:gdal, _code, _output} = reason}) do + Logger.warning("CoverageWorker: GDAL failure: #{inspect(reason)}") + "Terrain data fetch failed. The TowerOps team has been notified — please retry shortly." + end + + defp describe_error({:terrain_unavailable, reason}) do + Logger.warning("CoverageWorker: terrain fetch failed: #{inspect(reason)}") + "Terrain data is temporarily unavailable for this site. Please retry shortly." + end defp describe_error({:unknown_antenna, slug}), do: "Antenna #{inspect(slug)} is not in the registry."