Files
boc/iom/visual_geolocation/map_matcher.py
T
Bernt bae705aa97 ARCHITECTURE: NFC roadmap, edge AI, audit logging
- Add NFC ePassport roadmap (ICAO 9303, eIDAS)
- Add TensorFlow.js edge face detection (BlazeFace)
- Add structured audit logger (GDPR-compliant)
- Risk scoring support

Part of KYC Apple Native UX v1.1.0
2026-06-29 16:24:48 +00:00

297 lines
8.9 KiB
Python

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