Files
boc/iom/visual_geolocation/geolocation_engine.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

298 lines
9.7 KiB
Python

"""
Geolocation Engine
Multi-layer geolocation using evidence from images
"""
from typing import Dict, List, Optional, Tuple
from dataclasses import dataclass
import numpy as np
@dataclass
class GeolocationEstimate:
"""Geolocation estimate with confidence"""
lat: float
lng: float
accuracy: float # meters
confidence: float # 0-1
method: str # gps, visual, semantic, geometric, temporal, ensemble
evidence: Dict
class GeolocationEngine:
"""
Multi-layer geolocation engine
Methods:
1. GPS (if available)
2. Visual geolocation (landmarks, buildings)
3. Semantic geolocation (signs, text)
4. Geometric geolocation (vanishing points, camera pose)
5. Temporal geolocation (historical matches)
6. Map matching (OpenStreetMap, GIS)
7. Similarity matching (embeddings)
8. Ensemble fusion (combine all)
"""
def __init__(self):
self.map_data = None # OpenStreetMap data
self.historical_observations = [] # Previous observations
self.embedding_index = None # FAISS or similar
def geolocate(self, evidence_package) -> GeolocationEstimate:
"""
Estimate geolocation from evidence package
Returns best estimate with confidence
"""
estimates = []
# Method 1: GPS (if available)
if evidence_package.metadata.gps_lat and evidence_package.metadata.gps_lng:
gps_estimate = self._gps_geolocate(evidence_package)
if gps_estimate:
estimates.append(gps_estimate)
# Method 2: Visual geolocation
visual_estimate = self._visual_geolocate(evidence_package)
if visual_estimate:
estimates.append(visual_estimate)
# Method 3: Semantic geolocation
semantic_estimate = self._semantic_geolocate(evidence_package)
if semantic_estimate:
estimates.append(semantic_estimate)
# Method 4: Geometric geolocation
geometric_estimate = self._geometric_geolocate(evidence_package)
if geometric_estimate:
estimates.append(geometric_estimate)
# Method 5: Temporal geolocation
temporal_estimate = self._temporal_geolocate(evidence_package)
if temporal_estimate:
estimates.append(temporal_estimate)
# Method 6: Map matching
map_estimate = self._map_match(evidence_package)
if map_estimate:
estimates.append(map_estimate)
# Method 7: Similarity matching
similarity_estimate = self._similarity_match(evidence_package)
if similarity_estimate:
estimates.append(similarity_estimate)
# Method 8: Ensemble fusion
if len(estimates) > 1:
final_estimate = self._ensemble_fusion(estimates)
elif len(estimates) == 1:
final_estimate = estimates[0]
else:
# No estimates available
final_estimate = GeolocationEstimate(
lat=0.0,
lng=0.0,
accuracy=100000, # 100km
confidence=0.0,
method="none",
evidence={"error": "No geolocation evidence available"}
)
return final_estimate
def _gps_geolocate(self, evidence) -> Optional[GeolocationEstimate]:
"""Geolocate using GPS metadata"""
metadata = evidence.metadata
if not metadata.gps_lat or not metadata.gps_lng:
return None
# GPS accuracy depends on device and conditions
accuracy = 10.0 # Default 10 meters
if metadata.altitude:
accuracy = 5.0 # Better if altitude available
return GeolocationEstimate(
lat=metadata.gps_lat,
lng=metadata.gps_lng,
accuracy=accuracy,
confidence=0.9,
method="gps",
evidence={
"gps_lat": metadata.gps_lat,
"gps_lng": metadata.gps_lng,
"altitude": metadata.altitude
}
)
def _visual_geolocate(self, evidence) -> Optional[GeolocationEstimate]:
"""Geolocate using visual landmarks"""
# In production, match against landmark database
# For now, return None
if not evidence.visual_objects:
return None
# Check for known landmarks
landmarks = {
"eiffel_tower": (48.8584, 2.2945),
"statue_of_liberty": (40.6892, -74.0445),
"big_ben": (51.5007, -0.1246)
}
for obj in evidence.visual_objects:
if obj.label in landmarks:
lat, lng = landmarks[obj.label]
return GeolocationEstimate(
lat=lat,
lng=lng,
accuracy=50.0,
confidence=obj.confidence * 0.8,
method="visual",
evidence={"landmark": obj.label, "confidence": obj.confidence}
)
return None
def _semantic_geolocate(self, evidence) -> Optional[GeolocationEstimate]:
"""Geolocate using semantic objects (signs, text)"""
if not evidence.text_detections:
return None
# In production, geocode text addresses
# For now, check for known locations in text
location_keywords = {
"bangkok": (13.7563, 100.5018),
"tokyo": (35.6762, 139.6503),
"london": (51.5074, -0.1278),
"new york": (40.7128, -74.0060)
}
for text_det in evidence.text_detections:
text_lower = text_det.text.lower()
for location, coords in location_keywords.items():
if location in text_lower:
return GeolocationEstimate(
lat=coords[0],
lng=coords[1],
accuracy=1000.0, # 1km
confidence=text_det.confidence * 0.6,
method="semantic",
evidence={"text": text_det.text, "matched_location": location}
)
return None
def _geometric_geolocate(self, evidence) -> Optional[GeolocationEstimate]:
"""Geolocate using geometric features"""
# In production, use camera pose estimation
# For now, return None
return None
def _temporal_geolocate(self, evidence) -> Optional[GeolocationEstimate]:
"""Geolocate using temporal signals"""
# In production, match against historical observations
# For now, return None
return None
def _map_match(self, evidence) -> Optional[GeolocationEstimate]:
"""Match against map data (OpenStreetMap)"""
# In production, use OSM data
# For now, return None
return None
def _similarity_match(self, evidence) -> Optional[GeolocationEstimate]:
"""Match against historical observations using embeddings"""
# In production, use FAISS for similarity search
# For now, return None
return None
def _ensemble_fusion(self, estimates: List[GeolocationEstimate]) -> GeolocationEstimate:
"""Combine multiple estimates using weighted average"""
if not estimates:
return None
if len(estimates) == 1:
return estimates[0]
# Weight by confidence
total_weight = sum(e.confidence for e in estimates)
if total_weight == 0:
# Equal weights if no confidence
weights = [1.0 / len(estimates)] * len(estimates)
else:
weights = [e.confidence / total_weight for e in estimates]
# Weighted average
lat = sum(e.lat * w for e, w in zip(estimates, weights))
lng = sum(e.lng * w for e, w in zip(estimates, weights))
# Accuracy is weighted average
accuracy = sum(e.accuracy * w for e, w in zip(estimates, weights))
# Confidence is max confidence
confidence = max(e.confidence for e in estimates)
# Method is ensemble
methods = [e.method for e in estimates]
return GeolocationEstimate(
lat=lat,
lng=lng,
accuracy=accuracy,
confidence=confidence,
method=f"ensemble({','.join(methods)})",
evidence={
"methods": methods,
"estimates": [
{"lat": e.lat, "lng": e.lng, "accuracy": e.accuracy, "confidence": e.confidence, "method": e.method}
for e in estimates
]
}
)
# Example usage
def example_geolocation():
"""Example: Geolocation estimation"""
from evidence_extractor import EvidencePackage, ImageMetadata
from datetime import datetime
engine = GeolocationEngine()
# Create sample evidence
evidence = EvidencePackage(
image_id="demo_001",
timestamp=datetime.now(),
metadata=ImageMetadata(
gps_lat=13.7563,
gps_lng=100.5018,
altitude=10.0
),
visual_objects=[],
semantic_objects=[],
text_detections=[],
geometric_features=[],
environmental_signals=[],
temporal_signals={}
)
# Geolocate
estimate = engine.geolocate(evidence)
print(f"Geolocation estimate:")
print(f" Lat: {estimate.lat}")
print(f" Lng: {estimate.lng}")
print(f" Accuracy: {estimate.accuracy}m")
print(f" Confidence: {estimate.confidence}")
print(f" Method: {estimate.method}")
return estimate
if __name__ == '__main__':
example_geolocation()