defmodule Microwaveprop.Rover.Elevation do @moduledoc """ Bulk SRTM elevation lookup for a list of `{lat, lon}` points. Groups requested points by SRTM tile filename so each `.hgt` is opened exactly once per call, then closed. Cells whose tile is missing or whose value is the SRTM void sentinel are mapped to `nil`. """ alias Microwaveprop.Terrain.Srtm @samples 3601 @void -32_768 @spec lookup_many([{float(), float()}]) :: %{{float(), float()} => integer() | nil} def lookup_many([]), do: %{} def lookup_many(points) when is_list(points) do tiles_dir = Application.get_env(:microwaveprop, :srtm_tiles_dir, "/data/srtm") unique = Enum.uniq(points) grouped = Enum.group_by(unique, fn {lat, lon} -> Srtm.tile_filename(lat, lon) end) grouped |> Enum.flat_map(fn {filename, pts} -> read_tile(Path.join(tiles_dir, filename), pts) end) |> Map.new() end defp read_tile(path, pts) do case :file.open(path, [:read, :binary, :raw]) do {:ok, fd} -> try do Enum.map(pts, fn {lat, lon} = key -> {key, read_point(fd, lat, lon)} end) after :file.close(fd) end {:error, _} -> Enum.map(pts, fn key -> {key, nil} end) end end defp read_point(fd, lat, lon) do row = round((floor(lat) + 1 - lat) * (@samples - 1)) col = round((lon - floor(lon)) * (@samples - 1)) offset = (row * @samples + col) * 2 case :file.pread(fd, offset, 2) do {:ok, <>} when elev == @void -> nil {:ok, <>} -> elev _ -> nil end end end