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()