@mytec: iter3.2.2 ready for test

This commit is contained in:
2026-02-02 12:42:02 +02:00
parent c8c2608266
commit f5429e40fd
2 changed files with 60 additions and 16 deletions

View File

@@ -47,6 +47,7 @@ from app.services.materials_service import materials_service
from app.services.dominant_path_service import (
dominant_path_service, find_dominant_paths_vectorized,
get_lod_level, LODLevel, SIMPLIFIED_MAX_BUILDINGS,
_filter_buildings_by_distance,
)
from app.services.street_canyon_service import street_canyon_service, Street
from app.services.reflection_service import reflection_service
@@ -853,6 +854,16 @@ class CoverageService:
if spatial_idx else buildings
)
# Cap building count for the intersection loop — query_line can
# return hundreds of buildings; sort by proximity so the first
# intersecting building is found faster (loop breaks early).
if len(nearby_buildings) > 50:
nearby_buildings = _filter_buildings_by_distance(
nearby_buildings,
(site.lat, site.lon), (lat, lon),
max_count=50, max_distance=500,
)
if settings.use_buildings and nearby_buildings:
site_total_h = site.height + site_elevation
point_total_h = 1.5 + point_elevation
@@ -897,22 +908,22 @@ class CoverageService:
try:
# LOD_SIMPLIFIED: limit buildings for mid-range points (1.5-3km)
dp_buildings = nearby_buildings
dp_spatial = spatial_idx
if lod == LODLevel.SIMPLIFIED:
timing.setdefault("lod_simplified", 0)
timing["lod_simplified"] += 1
if len(nearby_buildings) > SIMPLIFIED_MAX_BUILDINGS:
dp_buildings = nearby_buildings[:SIMPLIFIED_MAX_BUILDINGS]
dp_spatial = None # Skip spatial queries, use filtered list only
else:
timing.setdefault("lod_full", 0)
timing["lod_full"] += 1
# nearby_buildings already filtered via spatial index —
# don't pass spatial_idx to avoid redundant query_line()
# and query_point() inside the vectorized function.
dominant = find_dominant_paths_vectorized(
site.lat, site.lon, site.height,
lat, lon, 1.5,
site.frequency, dp_buildings,
spatial_idx=dp_spatial,
)
if dominant['path_type'] == 'direct':
has_los = True

View File

@@ -167,10 +167,14 @@ def find_dominant_paths_vectorized(
3. Vectorized reflection point calculation
4. Simplified diffraction estimate
Callers should pass pre-filtered buildings and spatial_idx=None
to avoid redundant spatial queries (coverage_service already filters).
Returns dict with:
has_los, path_type, total_loss, path_length, reflection_point
"""
global _vec_log_count
t_total = time.perf_counter()
# Fast path: no buildings at all → direct LOS, skip all numpy work
has_spatial_data = spatial_idx is not None and spatial_idx._grid
@@ -183,11 +187,13 @@ def find_dominant_paths_vectorized(
'reflection_point': None,
}
# Get nearby buildings via spatial index (same filtering as sync version)
# Get nearby buildings via spatial index or use pre-filtered list
t0 = time.perf_counter()
if spatial_idx:
line_buildings = spatial_idx.query_line(tx_lat, tx_lon, rx_lat, rx_lon)
else:
line_buildings = buildings
t_query = time.perf_counter() - t0
# No nearby buildings along this line → direct LOS
if not line_buildings:
@@ -199,12 +205,14 @@ def find_dominant_paths_vectorized(
'reflection_point': None,
}
t0 = time.perf_counter()
line_buildings = _filter_buildings_by_distance(
line_buildings,
(tx_lat, tx_lon), (rx_lat, rx_lon),
max_count=MAX_BUILDINGS_FOR_LINE,
max_distance=MAX_DISTANCE_FROM_PATH,
)
t_filter = time.perf_counter() - t0
# Reference point for local coordinate system
ref_lat = (tx_lat + rx_lat) / 2
@@ -219,19 +227,11 @@ def find_dominant_paths_vectorized(
direct_dist = np.linalg.norm(rx - tx)
# Convert buildings to arrays
t0 = time.perf_counter()
walls_start, walls_end, wall_to_bldg, poly_x, poly_y, poly_lengths = (
_buildings_to_arrays(line_buildings, ref_lat, ref_lon)
)
# Diagnostic log for first few points
_vec_log_count += 1
if _vec_log_count <= 3:
print(
f"[DOMINANT_PATH_VEC] Point #{_vec_log_count}: "
f"buildings={len(line_buildings)}, walls={len(walls_start)}, "
f"dist={direct_dist:.0f}m",
flush=True,
)
t_arrays = time.perf_counter() - t0
# No buildings → direct LOS
if len(poly_lengths) == 0 or np.all(poly_lengths < 3):
@@ -244,9 +244,22 @@ def find_dominant_paths_vectorized(
}
# Step 1: Vectorized direct LOS check
t0 = time.perf_counter()
intersects, _ = line_intersects_polygons_batch(tx, rx, poly_x, poly_y, poly_lengths)
t_los = time.perf_counter() - t0
if not np.any(intersects):
t_total_ms = (time.perf_counter() - t_total) * 1000
_vec_log_count += 1
if _vec_log_count <= 10:
print(
f"[DP_TIMING] #{_vec_log_count} LOS_CLEAR "
f"bldgs={len(line_buildings)} walls={len(walls_start)} "
f"query={t_query*1000:.1f}ms filter={t_filter*1000:.1f}ms "
f"arrays={t_arrays*1000:.1f}ms los={t_los*1000:.1f}ms "
f"total={t_total_ms:.1f}ms",
flush=True,
)
return {
'has_los': True,
'path_type': 'direct',
@@ -256,7 +269,8 @@ def find_dominant_paths_vectorized(
}
# Step 2: Vectorized reflection path finding
# Use all line buildings for reflection walls
# Reuse line buildings for reflection (no separate spatial query)
t0 = time.perf_counter()
if spatial_idx:
mid_lat = (tx_lat + rx_lat) / 2
mid_lon = (tx_lon + rx_lon) / 2
@@ -280,14 +294,33 @@ def find_dominant_paths_vectorized(
else:
r_walls_start, r_walls_end, r_wall_to_bldg = walls_start, walls_end, wall_to_bldg
r_poly_x, r_poly_y, r_poly_lengths = poly_x, poly_y, poly_lengths
t_refl_setup = time.perf_counter() - t0
t0 = time.perf_counter()
refl_point, refl_length, refl_loss = find_best_reflection_path_vectorized(
tx, rx,
r_walls_start, r_walls_end, r_wall_to_bldg,
r_poly_x, r_poly_y, r_poly_lengths,
max_candidates=30,
max_walls=100,
max_los_checks=10,
max_los_checks=5,
)
t_refl_calc = time.perf_counter() - t0
t_total_ms = (time.perf_counter() - t_total) * 1000
# Diagnostic log for first few points
_vec_log_count += 1
if _vec_log_count <= 10:
print(
f"[DP_TIMING] #{_vec_log_count} LOS_BLOCKED "
f"bldgs={len(line_buildings)} walls={len(walls_start)} "
f"dist={direct_dist:.0f}m "
f"query={t_query*1000:.1f}ms filter={t_filter*1000:.1f}ms "
f"arrays={t_arrays*1000:.1f}ms los={t_los*1000:.1f}ms "
f"refl_setup={t_refl_setup*1000:.1f}ms refl_calc={t_refl_calc*1000:.1f}ms "
f"total={t_total_ms:.1f}ms",
flush=True,
)
if refl_point is not None: