feat(rover): show buildings stacked on terrain in path profile
Each profile sample now carries the tallest building height within 80m of the path point. The candidate-detail SVG renders these as a red polygon stacked on top of the terrain so blockage from buildings is visible alongside ridges. max_obstacle_m now reflects building tops too, so the clearance label downgrades when buildings sit on the link.
This commit is contained in:
parent
3b8d87c356
commit
b97af8a5ce
2 changed files with 50 additions and 4 deletions
|
|
@ -5,6 +5,7 @@ defmodule Microwaveprop.Rover.CandidateDetail do
|
|||
along the great-circle path to each selected fixed station.
|
||||
"""
|
||||
|
||||
alias Microwaveprop.Buildings.Index, as: BuildingsIndex
|
||||
alias Microwaveprop.Radio.Maidenhead
|
||||
alias Microwaveprop.Rover.DriveTime
|
||||
alias Microwaveprop.Rover.LinkMargin
|
||||
|
|
@ -12,6 +13,11 @@ defmodule Microwaveprop.Rover.CandidateDetail do
|
|||
|
||||
@sample_count 48
|
||||
|
||||
# Search radius around each profile sample when checking buildings.
|
||||
# Slightly tighter than the path-clearance radius (150 m) because the
|
||||
# profile is a narrower visualization — we want what's "on the line".
|
||||
@profile_building_radius_m 80
|
||||
|
||||
@type station :: %{callsign: String.t(), lat: float(), lon: float()}
|
||||
|
||||
@type link_summary :: %{
|
||||
|
|
@ -24,7 +30,7 @@ defmodule Microwaveprop.Rover.CandidateDetail do
|
|||
station_elev_m: integer() | nil,
|
||||
max_obstacle_m: integer() | nil,
|
||||
clearance_m: integer() | nil,
|
||||
profile: [%{dist_km: float(), elev: integer()}]
|
||||
profile: [%{dist_km: float(), elev: integer(), building_m: float()}]
|
||||
}
|
||||
|
||||
@spec summarize(map(), [station()], non_neg_integer(), DateTime.t(), atom()) :: %{
|
||||
|
|
@ -43,7 +49,7 @@ defmodule Microwaveprop.Rover.CandidateDetail do
|
|||
max_obstacle =
|
||||
profile
|
||||
|> middle_samples()
|
||||
|> Enum.map(& &1.elev)
|
||||
|> Enum.map(fn pt -> pt.elev + round(pt.building_m) end)
|
||||
|> safe_max()
|
||||
|
||||
clearance =
|
||||
|
|
@ -93,7 +99,13 @@ defmodule Microwaveprop.Rover.CandidateDetail do
|
|||
@sample_count
|
||||
) do
|
||||
{:ok, points} ->
|
||||
Enum.map(points, fn pt -> %{dist_km: pt.dist_km, elev: pt.elev} end)
|
||||
Enum.map(points, fn pt ->
|
||||
%{
|
||||
dist_km: pt.dist_km,
|
||||
elev: pt.elev,
|
||||
building_m: BuildingsIndex.max_height_near(pt.lat, pt.lon, @profile_building_radius_m)
|
||||
}
|
||||
end)
|
||||
|
||||
_ ->
|
||||
[]
|
||||
|
|
|
|||
|
|
@ -791,9 +791,10 @@ defmodule MicrowavepropWeb.RoverLive do
|
|||
defp profile_to_svg([]), do: nil
|
||||
|
||||
defp profile_to_svg(points) do
|
||||
tops = Enum.map(points, fn pt -> pt.elev + round(Map.get(pt, :building_m, 0.0)) end)
|
||||
elevs = Enum.map(points, & &1.elev)
|
||||
min_e = Enum.min(elevs)
|
||||
max_e = Enum.max(elevs)
|
||||
max_e = max(Enum.max(elevs), Enum.max(tops))
|
||||
span = max(max_e - min_e, 1)
|
||||
first = List.first(points)
|
||||
last = List.last(points)
|
||||
|
|
@ -808,12 +809,38 @@ defmodule MicrowavepropWeb.RoverLive do
|
|||
|
||||
fill = "0,60 " <> line <> " 240,60"
|
||||
|
||||
has_buildings? = Enum.any?(points, fn pt -> Map.get(pt, :building_m, 0.0) > 0.5 end)
|
||||
|
||||
building_fill =
|
||||
if has_buildings? do
|
||||
building_line =
|
||||
Enum.map_join(points, " ", fn pt ->
|
||||
x = pt.dist_km / total_km * 240.0
|
||||
top = pt.elev + round(Map.get(pt, :building_m, 0.0))
|
||||
y = 60.0 - (top - min_e) / span * 50.0
|
||||
"#{Float.round(x, 2)},#{Float.round(y, 2)}"
|
||||
end)
|
||||
|
||||
# Close back along the terrain so the building polygon sits on top.
|
||||
terrain_back =
|
||||
points
|
||||
|> Enum.reverse()
|
||||
|> Enum.map_join(" ", fn pt ->
|
||||
x = pt.dist_km / total_km * 240.0
|
||||
y = 60.0 - (pt.elev - min_e) / span * 50.0
|
||||
"#{Float.round(x, 2)},#{Float.round(y, 2)}"
|
||||
end)
|
||||
|
||||
building_line <> " " <> terrain_back
|
||||
end
|
||||
|
||||
los_y_start = Float.round(60.0 - (first.elev - min_e) / span * 50.0, 2)
|
||||
los_y_end = Float.round(60.0 - (last.elev - min_e) / span * 50.0, 2)
|
||||
|
||||
%{
|
||||
line: line,
|
||||
fill: fill,
|
||||
building_fill: building_fill,
|
||||
start_elev_m: first.elev,
|
||||
end_elev_m: last.elev,
|
||||
total_km: total_km,
|
||||
|
|
@ -1208,6 +1235,13 @@ defmodule MicrowavepropWeb.RoverLive do
|
|||
~H"""
|
||||
<svg viewBox="0 0 240 64" class="w-full h-12 mt-1" preserveAspectRatio="none">
|
||||
<polygon points={@svg.fill} fill="rgb(14 165 233 / 0.25)" />
|
||||
<polygon
|
||||
:if={@svg.building_fill}
|
||||
points={@svg.building_fill}
|
||||
fill="rgb(220 38 38 / 0.55)"
|
||||
stroke="#dc2626"
|
||||
stroke-width="0.6"
|
||||
/>
|
||||
<polyline points={@svg.line} fill="none" stroke="#0ea5e9" stroke-width="1.2" />
|
||||
<line
|
||||
x1="0"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue