Files
rfcp/backend/app/services/materials_service.py

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