@mytec: before 3.0 REFACTOR
This commit is contained in:
@@ -1,10 +1,15 @@
|
||||
import time
|
||||
import numpy as np
|
||||
from typing import List, Tuple, Optional, TYPE_CHECKING
|
||||
from typing import List, Tuple, Optional, Dict, Any, TYPE_CHECKING
|
||||
from dataclasses import dataclass
|
||||
from app.services.terrain_service import terrain_service
|
||||
from app.services.buildings_service import buildings_service, Building
|
||||
from app.services.materials_service import materials_service, BuildingMaterial
|
||||
from app.services.geometry_vectorized import (
|
||||
points_to_local_coords,
|
||||
line_intersects_polygons_batch,
|
||||
find_best_reflection_path_vectorized,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from app.services.spatial_index import SpatialIndex
|
||||
@@ -21,9 +26,9 @@ class RayPath:
|
||||
is_valid: bool # Does this path exist?
|
||||
|
||||
|
||||
MAX_BUILDINGS_FOR_LINE = 50
|
||||
MAX_BUILDINGS_FOR_REFLECTION = 30
|
||||
MAX_DISTANCE_FROM_PATH = 300 # meters
|
||||
MAX_BUILDINGS_FOR_LINE = 30
|
||||
MAX_BUILDINGS_FOR_REFLECTION = 20
|
||||
MAX_DISTANCE_FROM_PATH = 200 # meters
|
||||
|
||||
|
||||
def _filter_buildings_by_distance(buildings, tx_point, rx_point, max_count=100, max_distance=500):
|
||||
@@ -60,6 +65,204 @@ def _filter_buildings_by_distance(buildings, tx_point, rx_point, max_count=100,
|
||||
return filtered[:max_count]
|
||||
|
||||
|
||||
# ── Vectorized dominant path (NumPy) ──
|
||||
|
||||
_vec_log_count = 0
|
||||
|
||||
|
||||
def _buildings_to_arrays(buildings: List[Building], ref_lat: float, ref_lon: float):
|
||||
"""Convert Building objects to numpy arrays for vectorized geometry.
|
||||
|
||||
Returns:
|
||||
walls_start: (W, 2) wall start points in local XY meters
|
||||
walls_end: (W, 2) wall end points in local XY meters
|
||||
wall_to_building: (W,) mapping wall index -> building index
|
||||
poly_x: flattened polygon X coords
|
||||
poly_y: flattened polygon Y coords
|
||||
poly_lengths: (num_polygons,) vertices per polygon
|
||||
"""
|
||||
all_walls_start = []
|
||||
all_walls_end = []
|
||||
wall_to_building = []
|
||||
|
||||
all_poly_x = []
|
||||
all_poly_y = []
|
||||
poly_lengths = []
|
||||
|
||||
for i, b in enumerate(buildings):
|
||||
geom = b.geometry # [[lon, lat], ...]
|
||||
if not geom or len(geom) < 3:
|
||||
poly_lengths.append(0)
|
||||
continue
|
||||
|
||||
poly_lats = np.array([p[1] for p in geom])
|
||||
poly_lons = np.array([p[0] for p in geom])
|
||||
px, py = points_to_local_coords(ref_lat, ref_lon, poly_lats, poly_lons)
|
||||
|
||||
all_poly_x.extend(px)
|
||||
all_poly_y.extend(py)
|
||||
poly_lengths.append(len(geom))
|
||||
|
||||
# Extract wall segments
|
||||
for j in range(len(geom) - 1):
|
||||
all_walls_start.append([px[j], py[j]])
|
||||
all_walls_end.append([px[j + 1], py[j + 1]])
|
||||
wall_to_building.append(i)
|
||||
|
||||
return (
|
||||
np.array(all_walls_start) if all_walls_start else np.zeros((0, 2)),
|
||||
np.array(all_walls_end) if all_walls_end else np.zeros((0, 2)),
|
||||
np.array(wall_to_building, dtype=int) if wall_to_building else np.zeros(0, dtype=int),
|
||||
np.array(all_poly_x) if all_poly_x else np.zeros(0),
|
||||
np.array(all_poly_y) if all_poly_y else np.zeros(0),
|
||||
np.array(poly_lengths, dtype=int),
|
||||
)
|
||||
|
||||
|
||||
def find_dominant_paths_vectorized(
|
||||
tx_lat: float, tx_lon: float, tx_height: float,
|
||||
rx_lat: float, rx_lon: float, rx_height: float,
|
||||
frequency_mhz: float,
|
||||
buildings: List[Building],
|
||||
spatial_idx: 'Optional[SpatialIndex]' = None,
|
||||
) -> Dict[str, Any]:
|
||||
"""Vectorized dominant path finding using NumPy batch operations.
|
||||
|
||||
Replaces the loop-based find_dominant_paths_sync() with:
|
||||
1. Batch building-to-array conversion
|
||||
2. Vectorized LOS polygon intersection check
|
||||
3. Vectorized reflection point calculation
|
||||
4. Simplified diffraction estimate
|
||||
|
||||
Returns dict with:
|
||||
has_los, path_type, total_loss, path_length, reflection_point
|
||||
"""
|
||||
global _vec_log_count
|
||||
|
||||
# Get nearby buildings via spatial index (same filtering as sync version)
|
||||
if spatial_idx:
|
||||
line_buildings = spatial_idx.query_line(tx_lat, tx_lon, rx_lat, rx_lon)
|
||||
else:
|
||||
line_buildings = buildings
|
||||
|
||||
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,
|
||||
)
|
||||
|
||||
# Reference point for local coordinate system
|
||||
ref_lat = (tx_lat + rx_lat) / 2
|
||||
ref_lon = (tx_lon + rx_lon) / 2
|
||||
|
||||
# Convert TX/RX to local meters
|
||||
tx_xy = points_to_local_coords(ref_lat, ref_lon, np.array([tx_lat]), np.array([tx_lon]))
|
||||
rx_xy = points_to_local_coords(ref_lat, ref_lon, np.array([rx_lat]), np.array([rx_lon]))
|
||||
tx = np.array([tx_xy[0][0], tx_xy[1][0]])
|
||||
rx = np.array([rx_xy[0][0], rx_xy[1][0]])
|
||||
|
||||
direct_dist = np.linalg.norm(rx - tx)
|
||||
|
||||
# Convert buildings to arrays
|
||||
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,
|
||||
)
|
||||
|
||||
# No buildings → direct LOS
|
||||
if len(poly_lengths) == 0 or np.all(poly_lengths < 3):
|
||||
return {
|
||||
'has_los': True,
|
||||
'path_type': 'direct',
|
||||
'total_loss': 0.0,
|
||||
'path_length': direct_dist,
|
||||
'reflection_point': None,
|
||||
}
|
||||
|
||||
# Step 1: Vectorized direct LOS check
|
||||
intersects, _ = line_intersects_polygons_batch(tx, rx, poly_x, poly_y, poly_lengths)
|
||||
|
||||
if not np.any(intersects):
|
||||
return {
|
||||
'has_los': True,
|
||||
'path_type': 'direct',
|
||||
'total_loss': 0.0,
|
||||
'path_length': direct_dist,
|
||||
'reflection_point': None,
|
||||
}
|
||||
|
||||
# Step 2: Vectorized reflection path finding
|
||||
# Use all line buildings for reflection walls
|
||||
if spatial_idx:
|
||||
mid_lat = (tx_lat + rx_lat) / 2
|
||||
mid_lon = (tx_lon + rx_lon) / 2
|
||||
refl_buildings = spatial_idx.query_point(mid_lat, mid_lon, buffer_cells=3)
|
||||
refl_buildings = _filter_buildings_by_distance(
|
||||
refl_buildings,
|
||||
(tx_lat, tx_lon), (rx_lat, rx_lon),
|
||||
max_count=MAX_BUILDINGS_FOR_REFLECTION,
|
||||
max_distance=MAX_DISTANCE_FROM_PATH,
|
||||
)
|
||||
# Merge line + reflection buildings (deduplicate by id)
|
||||
seen_ids = {b.id for b in line_buildings}
|
||||
merged = list(line_buildings)
|
||||
for b in refl_buildings:
|
||||
if b.id not in seen_ids:
|
||||
merged.append(b)
|
||||
seen_ids.add(b.id)
|
||||
r_walls_start, r_walls_end, r_wall_to_bldg, r_poly_x, r_poly_y, r_poly_lengths = (
|
||||
_buildings_to_arrays(merged, ref_lat, ref_lon)
|
||||
)
|
||||
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
|
||||
|
||||
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,
|
||||
)
|
||||
|
||||
if refl_point is not None:
|
||||
# Convert reflection point back to lat/lon
|
||||
cos_lat = np.cos(np.radians(ref_lat))
|
||||
refl_lat = ref_lat + refl_point[1] / 110540.0
|
||||
refl_lon = ref_lon + refl_point[0] / (111320.0 * cos_lat)
|
||||
|
||||
return {
|
||||
'has_los': False,
|
||||
'path_type': 'reflection',
|
||||
'total_loss': refl_loss,
|
||||
'path_length': refl_length,
|
||||
'reflection_point': (refl_lat, refl_lon),
|
||||
}
|
||||
|
||||
# Step 3: Diffraction fallback
|
||||
num_blocking = int(np.sum(intersects))
|
||||
diffraction_loss = 10.0 + 5.0 * min(num_blocking, 5)
|
||||
|
||||
return {
|
||||
'has_los': False,
|
||||
'path_type': 'diffraction',
|
||||
'total_loss': diffraction_loss,
|
||||
'path_length': direct_dist,
|
||||
'reflection_point': None,
|
||||
}
|
||||
|
||||
|
||||
class DominantPathService:
|
||||
"""
|
||||
Find dominant propagation paths (2-3 strongest)
|
||||
|
||||
Reference in New Issue
Block a user