Files
boc/iom/visual_geolocation/map_matcher.py
T

297 lines
8.9 KiB
Python
Raw Normal View History

"""
Map Matcher
Match observations against OpenStreetMap and other GIS data
"""
from typing import Dict, List, Optional, Tuple
from dataclasses import dataclass
import math
@dataclass
class MapMatch:
"""Map match result"""
osm_id: str
name: str
match_type: str # street, building, poi, etc.
distance: float # meters
confidence: float
tags: Dict
class MapMatcher:
"""
Match observations against map data
Sources:
- OpenStreetMap (OSM)
- Municipal GIS data
- Property maps
- Road databases
- quiXzoom historical observations
"""
def __init__(self):
self.osm_data = {} # In production, load from OSM API or local database
self.historical_observations = []
def match_location(
self,
lat: float,
lng: float,
evidence: Dict,
radius: float = 100.0 # meters
) -> List[MapMatch]:
"""
Match location against map data
Returns list of potential matches with confidence scores
"""
matches = []
# 1. Match against OSM streets
street_matches = self._match_streets(lat, lng, evidence, radius)
matches.extend(street_matches)
# 2. Match against OSM buildings
building_matches = self._match_buildings(lat, lng, evidence, radius)
matches.extend(building_matches)
# 3. Match against OSM POIs
poi_matches = self._match_pois(lat, lng, evidence, radius)
matches.extend(poi_matches)
# 4. Match against historical observations
historical_matches = self._match_historical(lat, lng, evidence, radius)
matches.extend(historical_matches)
# Sort by confidence
matches.sort(key=lambda x: x.confidence, reverse=True)
return matches
def _match_streets(
self,
lat: float,
lng: float,
evidence: Dict,
radius: float
) -> List[MapMatch]:
"""Match against OSM streets"""
matches = []
# In production, query OSM API or local database
# For demo, simulate matches
# Check if street names in evidence match known streets
text_evidence = evidence.get("text_detections", [])
known_streets = {
"Sukhumvit Road": {
"osm_id": "way_12345",
"lat": 13.7563,
"lng": 100.5018,
"tags": {"highway": "primary", "name": "Sukhumvit Road"}
},
"Silom Road": {
"osm_id": "way_67890",
"lat": 13.7285,
"lng": 100.5293,
"tags": {"highway": "primary", "name": "Silom Road"}
}
}
for text in text_evidence:
street_name = text.get("text", "")
if street_name in known_streets:
street = known_streets[street_name]
distance = self._haversine_distance(lat, lng, street["lat"], street["lng"])
if distance <= radius:
confidence = self._calculate_match_confidence(
distance, radius, text.get("confidence", 0.5)
)
matches.append(MapMatch(
osm_id=street["osm_id"],
name=street_name,
match_type="street",
distance=distance,
confidence=confidence,
tags=street["tags"]
))
return matches
def _match_buildings(
self,
lat: float,
lng: float,
evidence: Dict,
radius: float
) -> List[MapMatch]:
"""Match against OSM buildings"""
matches = []
# In production, query OSM building data
# For demo, return empty
return matches
def _match_pois(
self,
lat: float,
lng: float,
evidence: Dict,
radius: float
) -> List[MapMatch]:
"""Match against OSM POIs"""
matches = []
# Check for business names in evidence
text_evidence = evidence.get("text_detections", [])
known_pois = {
"Sainokuni": {
"osm_id": "node_11111",
"lat": 13.7565,
"lng": 100.5020,
"tags": {"amenity": "restaurant", "name": "Sainokuni", "cuisine": "japanese"}
}
}
for text in text_evidence:
business_name = text.get("text", "")
for poi_name, poi in known_pois.items():
if poi_name.lower() in business_name.lower():
distance = self._haversine_distance(lat, lng, poi["lat"], poi["lng"])
if distance <= radius:
confidence = self._calculate_match_confidence(
distance, radius, text.get("confidence", 0.5)
)
matches.append(MapMatch(
osm_id=poi["osm_id"],
name=poi_name,
match_type="poi",
distance=distance,
confidence=confidence,
tags=poi["tags"]
))
return matches
def _match_historical(
self,
lat: float,
lng: float,
evidence: Dict,
radius: float
) -> List[MapMatch]:
"""Match against historical observations"""
matches = []
# In production, query database of previous observations
# For demo, simulate one historical match
historical = {
"obs_001": {
"lat": 13.7563,
"lng": 100.5018,
"timestamp": "2026-03-15T10:00:00Z",
"visual_objects": ["street_light", "building"],
"embedding_similarity": 0.92
}
}
for obs_id, obs in historical.items():
distance = self._haversine_distance(lat, lng, obs["lat"], obs["lng"])
if distance <= radius:
confidence = obs.get("embedding_similarity", 0.5) * (1 - distance / radius)
matches.append(MapMatch(
osm_id=obs_id,
name=f"Historical observation {obs_id}",
match_type="historical",
distance=distance,
confidence=confidence,
tags={"timestamp": obs["timestamp"]}
))
return matches
def _calculate_match_confidence(
self,
distance: float,
radius: float,
evidence_confidence: float
) -> float:
"""Calculate confidence based on distance and evidence quality"""
# Distance factor: closer = higher confidence
distance_factor = 1 - (distance / radius)
# Combine with evidence confidence
confidence = distance_factor * evidence_confidence
return max(0.0, min(1.0, confidence))
def _haversine_distance(self, lat1, lng1, lat2, lng2) -> float:
"""Calculate distance between two coordinates in meters"""
R = 6371000 # Earth radius in meters
phi1 = math.radians(lat1)
phi2 = math.radians(lat2)
delta_phi = math.radians(lat2 - lat1)
delta_lambda = math.radians(lng2 - lng1)
a = math.sin(delta_phi / 2) ** 2 + \
math.cos(phi1) * math.cos(phi2) * math.sin(delta_lambda / 2) ** 2
c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a))
return R * c
# Example usage
def test_map_matching():
"""Test map matching"""
print("=== Testing Map Matching ===\n")
matcher = MapMatcher()
# Simulate evidence from Bangkok image
evidence = {
"text_detections": [
{"text": "Sukhumvit Road", "confidence": 0.92},
{"text": "Sainokuni", "confidence": 0.85}
],
"visual_objects": [
{"label": "street_light", "confidence": 0.85},
{"label": "building", "confidence": 0.92}
]
}
# Match location
matches = matcher.match_location(
lat=13.7563,
lng=100.5018,
evidence=evidence,
radius=100
)
print(f"Found {len(matches)} matches:")
for match in matches:
print(f" - {match.name} ({match.match_type})")
print(f" Distance: {match.distance:.1f}m")
print(f" Confidence: {match.confidence:.2f}")
print(f" OSM ID: {match.osm_id}")
print(f" Tags: {match.tags}")
print()
return matches
if __name__ == '__main__':
test_map_matching()