Phase 2/3/5 of the building-blockage support: * Parser streams csv.gz tiles into compact records (centroid, radius, height) — keeps RAM ~32 B per polygon, drops -1.0-height entries. * Index buckets records into 0.01° (~1 km) grid cells in a public ETS table for concurrent reads; max_height_near/3 and records_near/3 scan only the 9 nearest buckets. * Loader lazily parses any cached quadkey before a Calculate so the scorer is terrain-only when tiles aren't on disk yet. * Rover.PathTerrain now treats each path-sample obstacle as terrain_elev + max_local_building_height, so blocked LOS through recent construction actually reduces clearance. * Selecting a candidate pushes a candidate_buildings event with the buildings near each rover→station path; the hook renders them as height-coloured circles (yellow <10 m, orange 10-30 m, red >=30 m) with a tooltip showing height in meters.
60 lines
1.7 KiB
Elixir
60 lines
1.7 KiB
Elixir
defmodule Microwaveprop.Buildings.Loader do
|
|
@moduledoc """
|
|
Lazily loads cached Microsoft footprint csv.gz tiles into the
|
|
spatial `Microwaveprop.Buildings.Index`. Idempotent: a quadkey
|
|
loaded once stays loaded for the lifetime of the BEAM.
|
|
|
|
Tiles that haven't been pre-fetched are skipped with a debug log —
|
|
the rover scorer falls back to terrain-only path analysis when the
|
|
index is missing for a region.
|
|
"""
|
|
|
|
alias Microwaveprop.Buildings.Index
|
|
alias Microwaveprop.Buildings.MsFootprints
|
|
alias Microwaveprop.Buildings.Parser
|
|
|
|
require Logger
|
|
|
|
@zoom 9
|
|
|
|
@doc """
|
|
Ensures every quadkey covering `bbox` has its csv.gz tile parsed
|
|
into the index. Returns the list of quadkeys that were *attempted*
|
|
(whether already loaded, freshly loaded, or missing on disk).
|
|
"""
|
|
@spec ensure_loaded_for_bbox(map()) :: [String.t()]
|
|
def ensure_loaded_for_bbox(bbox) do
|
|
quadkeys = MsFootprints.quadkeys_for_bbox(bbox, @zoom)
|
|
Enum.each(quadkeys, &ensure_loaded/1)
|
|
quadkeys
|
|
end
|
|
|
|
@doc """
|
|
Load a single quadkey. No-op if already loaded; logs and skips if
|
|
the csv.gz file is missing on disk.
|
|
"""
|
|
@spec ensure_loaded(String.t()) :: :ok
|
|
def ensure_loaded(quadkey) do
|
|
if Index.loaded?(quadkey) do
|
|
:ok
|
|
else
|
|
do_load(quadkey)
|
|
end
|
|
end
|
|
|
|
defp do_load(quadkey) do
|
|
path = Path.join(MsFootprints.cache_dir(), "#{quadkey}.csv.gz")
|
|
|
|
if File.exists?(path) do
|
|
records = path |> Parser.parse_tile() |> Enum.to_list()
|
|
Index.insert_records(records)
|
|
Index.mark_loaded(quadkey)
|
|
Logger.info("buildings index: loaded #{quadkey} (#{length(records)} polygons)")
|
|
else
|
|
Logger.debug("buildings index: tile #{quadkey} not on disk; skipping")
|
|
Index.mark_loaded(quadkey)
|
|
end
|
|
|
|
:ok
|
|
end
|
|
end
|