fix(coverage): use gdalwarp for elevation grid + sanitise UI errors

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.
This commit is contained in:
Graham McIntire 2026-05-06 17:27:56 -05:00
parent edb82bcc40
commit 824fe480aa
2 changed files with 20 additions and 8 deletions

View file

@ -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

View file

@ -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."