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