added bootstrap buildings

This commit is contained in:
2026-06-23 09:41:06 +03:00
parent 3b15045fb7
commit bcf0ce76e7
4 changed files with 96 additions and 45 deletions
Binary file not shown.
+34 -44
View File
@@ -19,53 +19,43 @@ def knife_edge_v(h: float, d1: float, d2: float, freq_hz: float) -> float:
return h * sqrt(2 * (d1 + d2) / (lmbda * d1 * d2))
def bullington_loss(
profile: SurfaceProfile,
tx_height_agl: float,
rx_height_agl: float,
freq_hz: float,
) -> float:
"""Bullington-style equivalent edge loss for a terrain profile.
The current implementation uses the dominant obstacle relative to the TX-RX
chord as the equivalent Bullington edge, then applies ITU-R P.526 J(v).
"""
if len(profile.samples) < 3:
return 0.0
total_distance = profile.distance_m
if total_distance <= 0:
return 0.0
tx_elevation = profile.samples[0].ground_m + tx_height_agl
rx_elevation = profile.samples[-1].ground_m + rx_height_agl
max_v = float("-inf")
for sample in profile.samples[1:-1]:
d1 = sample.distance_m
d2 = total_distance - d1
path_height = tx_elevation + (rx_elevation - tx_elevation) * (d1 / total_distance)
h = sample.surface_m - path_height
max_v = max(max_v, knife_edge_v(h, d1, d2, freq_hz))
return knife_edge_loss(max_v)
def deygout(
profile: SurfaceProfile,
tx_height_agl: float,
rx_height_agl: float,
freq_hz: float,
) -> float:
"""Recursive Deygout diffraction loss using the dominant edge and subprofiles."""
if len(profile.samples) < 3:
return 0.0
endpoint_heights = {
0: profile.samples[0].ground_m + tx_height_agl,
len(profile.samples) - 1: profile.samples[-1].ground_m + rx_height_agl,
}
def sample_height(index: int) -> float:
return endpoint_heights.get(index, profile.samples[index].surface_m)
def solve(left: int, right: int) -> float:
if right - left < 2:
return 0.0
left_sample = profile.samples[left]
right_sample = profile.samples[right]
span_m = right_sample.distance_m - left_sample.distance_m
left_height = sample_height(left)
right_height = sample_height(right)
max_v = float("-inf")
max_index: int | None = None
for index in range(left + 1, right):
sample = profile.samples[index]
d1 = sample.distance_m - left_sample.distance_m
d2 = right_sample.distance_m - sample.distance_m
path_height = left_height + (right_height - left_height) * (d1 / span_m)
h = sample.surface_m - path_height
v = knife_edge_v(h, d1, d2, freq_hz)
if v > max_v:
max_v = v
max_index = index
if max_index is None:
return 0.0
main_loss = knife_edge_loss(max_v)
if main_loss == 0.0:
return 0.0
return main_loss + solve(left, max_index) + solve(max_index, right)
return solve(0, len(profile.samples) - 1)
"""Compatibility wrapper; use Bullington equivalent loss for multi-edge profiles."""
return bullington_loss(profile, tx_height_agl, rx_height_agl, freq_hz)