83 lines
2.6 KiB
Python
83 lines
2.6 KiB
Python
import math
|
|
|
|
|
|
class IndoorService:
|
|
"""ITU-R P.2109 building entry loss model"""
|
|
|
|
# Building Entry Loss (BEL) by construction type at 2 GHz
|
|
# Format: (median_loss_dB, std_dev_dB)
|
|
BUILDING_TYPES = {
|
|
"none": (0.0, 0.0), # Outdoor only
|
|
"light": (8.0, 4.0), # Wood frame, large windows
|
|
"medium": (15.0, 6.0), # Brick, standard windows
|
|
"heavy": (22.0, 8.0), # Concrete, small windows
|
|
"basement": (30.0, 10.0), # Underground
|
|
"vehicle": (6.0, 3.0), # Inside car
|
|
"train": (20.0, 5.0), # Inside train
|
|
}
|
|
|
|
# Frequency correction factor (dB per octave above 2 GHz)
|
|
FREQ_CORRECTION = 2.5
|
|
|
|
def calculate_indoor_loss(
|
|
self,
|
|
frequency_mhz: float,
|
|
building_type: str = "medium",
|
|
floor_number: int = 0,
|
|
depth_m: float = 0.0,
|
|
) -> float:
|
|
"""
|
|
Calculate building entry/indoor penetration loss
|
|
|
|
Args:
|
|
frequency_mhz: Frequency in MHz
|
|
building_type: Type of building construction
|
|
floor_number: Floor number (0=ground, negative=basement)
|
|
depth_m: Distance from exterior wall in meters
|
|
|
|
Returns:
|
|
Loss in dB
|
|
"""
|
|
if building_type == "none":
|
|
return 0.0
|
|
|
|
base_loss, _ = self.BUILDING_TYPES.get(building_type, (15.0, 6.0))
|
|
|
|
# Frequency correction
|
|
freq_ghz = frequency_mhz / 1000
|
|
if freq_ghz > 2.0:
|
|
octaves = math.log2(freq_ghz / 2.0)
|
|
freq_correction = self.FREQ_CORRECTION * octaves
|
|
else:
|
|
freq_correction = 0.0
|
|
|
|
# Floor correction (higher floors = less loss due to better angle)
|
|
if floor_number > 0:
|
|
floor_correction = -1.5 * min(floor_number, 10)
|
|
elif floor_number < 0:
|
|
# Basement - additional loss
|
|
floor_correction = 5.0 * abs(floor_number)
|
|
else:
|
|
floor_correction = 0.0
|
|
|
|
# Depth correction (signal attenuates inside building)
|
|
# Approximately 0.5 dB per meter for first 10m
|
|
depth_correction = 0.5 * min(depth_m, 20)
|
|
|
|
total_loss = base_loss + freq_correction + floor_correction + depth_correction
|
|
|
|
return max(0.0, min(total_loss, 50.0)) # Clamp 0-50 dB
|
|
|
|
def calculate_outdoor_to_indoor_coverage(
|
|
self,
|
|
outdoor_rsrp: float,
|
|
building_type: str,
|
|
frequency_mhz: float,
|
|
) -> float:
|
|
"""Calculate expected indoor RSRP from outdoor signal"""
|
|
indoor_loss = self.calculate_indoor_loss(frequency_mhz, building_type)
|
|
return outdoor_rsrp - indoor_loss
|
|
|
|
|
|
indoor_service = IndoorService()
|