129 lines
4.2 KiB
Python
129 lines
4.2 KiB
Python
import math
|
|
from enum import Enum
|
|
from typing import Optional
|
|
|
|
|
|
class BuildingMaterial(Enum):
|
|
"""Building materials with RF properties"""
|
|
CONCRETE = "concrete"
|
|
BRICK = "brick"
|
|
GLASS = "glass"
|
|
WOOD = "wood"
|
|
METAL = "metal"
|
|
STONE = "stone"
|
|
PLASTER = "plaster"
|
|
UNKNOWN = "unknown"
|
|
|
|
|
|
# ITU-R P.2040 based attenuation (dB per wall at 1-3 GHz)
|
|
MATERIAL_LOSS = {
|
|
BuildingMaterial.CONCRETE: 15.0,
|
|
BuildingMaterial.BRICK: 10.0,
|
|
BuildingMaterial.GLASS: 3.0,
|
|
BuildingMaterial.WOOD: 5.0,
|
|
BuildingMaterial.METAL: 25.0, # Or full reflection
|
|
BuildingMaterial.STONE: 12.0,
|
|
BuildingMaterial.PLASTER: 4.0,
|
|
BuildingMaterial.UNKNOWN: 10.0, # Default assumption
|
|
}
|
|
|
|
# Reflection coefficient (0-1, portion of signal reflected)
|
|
MATERIAL_REFLECTION = {
|
|
BuildingMaterial.CONCRETE: 0.6,
|
|
BuildingMaterial.BRICK: 0.5,
|
|
BuildingMaterial.GLASS: 0.3,
|
|
BuildingMaterial.WOOD: 0.2,
|
|
BuildingMaterial.METAL: 0.9,
|
|
BuildingMaterial.STONE: 0.55,
|
|
BuildingMaterial.PLASTER: 0.3,
|
|
BuildingMaterial.UNKNOWN: 0.4,
|
|
}
|
|
|
|
|
|
class MaterialsService:
|
|
"""Building material detection and RF properties"""
|
|
|
|
# OSM building:material tag mapping
|
|
OSM_MATERIAL_MAP = {
|
|
"concrete": BuildingMaterial.CONCRETE,
|
|
"brick": BuildingMaterial.BRICK,
|
|
"glass": BuildingMaterial.GLASS,
|
|
"wood": BuildingMaterial.WOOD,
|
|
"metal": BuildingMaterial.METAL,
|
|
"steel": BuildingMaterial.METAL,
|
|
"stone": BuildingMaterial.STONE,
|
|
"plaster": BuildingMaterial.PLASTER,
|
|
"cement_block": BuildingMaterial.CONCRETE,
|
|
"timber": BuildingMaterial.WOOD,
|
|
}
|
|
|
|
# Fallback by building type
|
|
BUILDING_TYPE_MATERIAL = {
|
|
"industrial": BuildingMaterial.METAL,
|
|
"warehouse": BuildingMaterial.METAL,
|
|
"garage": BuildingMaterial.METAL,
|
|
"shed": BuildingMaterial.WOOD,
|
|
"house": BuildingMaterial.BRICK,
|
|
"residential": BuildingMaterial.CONCRETE,
|
|
"apartments": BuildingMaterial.CONCRETE,
|
|
"commercial": BuildingMaterial.GLASS, # Often glass facades
|
|
"office": BuildingMaterial.GLASS,
|
|
"retail": BuildingMaterial.GLASS,
|
|
"church": BuildingMaterial.STONE,
|
|
"cathedral": BuildingMaterial.STONE,
|
|
"school": BuildingMaterial.BRICK,
|
|
"hospital": BuildingMaterial.CONCRETE,
|
|
"university": BuildingMaterial.CONCRETE,
|
|
}
|
|
|
|
def detect_material(self, building_tags: dict) -> BuildingMaterial:
|
|
"""Detect building material from OSM tags"""
|
|
|
|
# Direct material tag
|
|
if "building:material" in building_tags:
|
|
material_str = building_tags["building:material"].lower()
|
|
if material_str in self.OSM_MATERIAL_MAP:
|
|
return self.OSM_MATERIAL_MAP[material_str]
|
|
|
|
# Facade material (often more relevant for RF)
|
|
if "building:facade:material" in building_tags:
|
|
material_str = building_tags["building:facade:material"].lower()
|
|
if material_str in self.OSM_MATERIAL_MAP:
|
|
return self.OSM_MATERIAL_MAP[material_str]
|
|
|
|
# Fallback by building type
|
|
building_type = building_tags.get("building", "yes").lower()
|
|
if building_type in self.BUILDING_TYPE_MATERIAL:
|
|
return self.BUILDING_TYPE_MATERIAL[building_type]
|
|
|
|
return BuildingMaterial.UNKNOWN
|
|
|
|
def get_penetration_loss(self, material: BuildingMaterial, frequency_mhz: float = 1800) -> float:
|
|
"""
|
|
Get RF penetration loss through wall
|
|
|
|
Frequency correction: +2dB per octave above 1GHz
|
|
"""
|
|
base_loss = MATERIAL_LOSS[material]
|
|
|
|
# Frequency correction (simplified)
|
|
freq_factor = max(0, (frequency_mhz - 1000) / 1000) * 2
|
|
|
|
return base_loss + freq_factor
|
|
|
|
def get_reflection_coefficient(self, material: BuildingMaterial) -> float:
|
|
"""Get reflection coefficient (0-1)"""
|
|
return MATERIAL_REFLECTION[material]
|
|
|
|
def get_reflection_loss(self, material: BuildingMaterial) -> float:
|
|
"""Get loss due to reflection (dB)"""
|
|
coeff = MATERIAL_REFLECTION[material]
|
|
if coeff <= 0:
|
|
return 30.0 # Effectively no reflection
|
|
|
|
# Reflection loss in dB = -10 * log10(coefficient)
|
|
return -10 * math.log10(coeff)
|
|
|
|
|
|
materials_service = MaterialsService()
|