@mytec: feat: Phase 3.0 Architecture Refactor ✅
Major refactoring of RFCP backend: - Modular propagation models (8 models) - SharedMemoryManager for terrain data - ProcessPoolExecutor parallel processing - WebSocket progress streaming - Building filtering pipeline (351k → 15k) - 82 unit tests Performance: Standard preset 38s → 5s (7.6x speedup) Known issue: Detailed preset timeout (fix in 3.1.0)
This commit is contained in:
103
backend/app/core/calculator.py
Normal file
103
backend/app/core/calculator.py
Normal file
@@ -0,0 +1,103 @@
|
||||
"""
|
||||
Point calculator — coordinates per-point propagation calculation.
|
||||
"""
|
||||
|
||||
import math
|
||||
from typing import Optional
|
||||
|
||||
from app.propagation.base import PropagationModel, PropagationInput
|
||||
from app.propagation.itu_r_p526 import KnifeEdgeDiffractionModel
|
||||
from app.core.result import PointResult
|
||||
|
||||
|
||||
class PointCalculator:
|
||||
"""Calculates propagation for individual grid points."""
|
||||
|
||||
def __init__(self, model: PropagationModel, environment: str = "urban"):
|
||||
self.model = model
|
||||
self.environment = environment
|
||||
self.diffraction = KnifeEdgeDiffractionModel()
|
||||
|
||||
def calculate_point(
|
||||
self,
|
||||
site_lat: float, site_lon: float, site_height: float,
|
||||
site_power: float, site_gain: float, site_frequency: float,
|
||||
point_lat: float, point_lon: float,
|
||||
distance: float,
|
||||
has_los: bool = True,
|
||||
terrain_clearance: Optional[float] = None,
|
||||
building_loss: float = 0.0,
|
||||
extra_loss: float = 0.0,
|
||||
azimuth: Optional[float] = None,
|
||||
beamwidth: float = 360,
|
||||
) -> PointResult:
|
||||
if distance < 1:
|
||||
distance = 1
|
||||
|
||||
prop_input = PropagationInput(
|
||||
frequency_mhz=site_frequency,
|
||||
distance_m=distance,
|
||||
tx_height_m=site_height,
|
||||
rx_height_m=1.5,
|
||||
environment=self.environment,
|
||||
)
|
||||
|
||||
if self.model.is_valid_for(prop_input):
|
||||
output = self.model.calculate(prop_input)
|
||||
path_loss = output.path_loss_db
|
||||
else:
|
||||
from app.propagation.free_space import FreeSpaceModel
|
||||
output = FreeSpaceModel().calculate(prop_input)
|
||||
path_loss = output.path_loss_db
|
||||
|
||||
antenna_loss = 0.0
|
||||
if azimuth is not None and beamwidth < 360:
|
||||
antenna_loss = self._antenna_pattern_loss(
|
||||
site_lat, site_lon, point_lat, point_lon, azimuth, beamwidth,
|
||||
)
|
||||
|
||||
terrain_loss = 0.0
|
||||
if terrain_clearance is not None and terrain_clearance < 0:
|
||||
terrain_loss = self.diffraction.calculate_clearance_loss(
|
||||
terrain_clearance, site_frequency,
|
||||
)
|
||||
has_los = False
|
||||
|
||||
rsrp = (
|
||||
site_power + site_gain
|
||||
- path_loss - antenna_loss
|
||||
- terrain_loss - building_loss - extra_loss
|
||||
)
|
||||
|
||||
return PointResult(
|
||||
lat=point_lat, lon=point_lon, rsrp=rsrp,
|
||||
distance=distance, path_loss=path_loss,
|
||||
terrain_loss=terrain_loss, building_loss=building_loss,
|
||||
diffraction_loss=terrain_loss, has_los=has_los,
|
||||
model_used=self.model.name,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _antenna_pattern_loss(
|
||||
site_lat: float, site_lon: float,
|
||||
point_lat: float, point_lon: float,
|
||||
azimuth: float, beamwidth: float,
|
||||
) -> float:
|
||||
lat1, lon1 = math.radians(site_lat), math.radians(site_lon)
|
||||
lat2, lon2 = math.radians(point_lat), math.radians(point_lon)
|
||||
dlon = lon2 - lon1
|
||||
x = math.sin(dlon) * math.cos(lat2)
|
||||
y = math.cos(lat1) * math.sin(lat2) - math.sin(lat1) * math.cos(lat2) * math.cos(dlon)
|
||||
bearing = (math.degrees(math.atan2(x, y)) + 360) % 360
|
||||
|
||||
angle_diff = abs(bearing - azimuth)
|
||||
if angle_diff > 180:
|
||||
angle_diff = 360 - angle_diff
|
||||
|
||||
half_bw = beamwidth / 2
|
||||
if angle_diff <= half_bw:
|
||||
loss = 3 * (angle_diff / half_bw) ** 2
|
||||
else:
|
||||
loss = 3 + 12 * ((angle_diff - half_bw) / half_bw) ** 2
|
||||
loss = min(loss, 25)
|
||||
return loss
|
||||
Reference in New Issue
Block a user