mustafa2ak commited on
Commit
0bf0107
·
verified ·
1 Parent(s): 4d247cd

Update utils.py

Browse files
Files changed (1) hide show
  1. utils.py +123 -54
utils.py CHANGED
@@ -1,70 +1,139 @@
 
 
 
 
 
 
 
 
1
  import exifread
2
- import requests
3
-
4
- def dms_to_decimal(dms, ref):
5
- """Convert [deg, min, sec] to decimal degrees."""
6
- d, m, s = [float(x) for x in dms]
7
- decimal = d + m / 60.0 + s / 3600.0
8
- if ref in ["S", "W"]:
9
- decimal = -decimal
10
- return decimal
11
-
12
- def reverse_geocode(lat, lon):
13
- """Convert coordinates into human-readable address using OpenStreetMap Nominatim."""
14
- try:
15
- url = f"https://nominatim.openstreetmap.org/reverse"
16
- params = {"lat": lat, "lon": lon, "format": "json", "zoom": 18, "addressdetails": 1}
17
- headers = {"User-Agent": "roadside-clean-app"}
18
- resp = requests.get(url, params=params, headers=headers, timeout=5)
19
- if resp.status_code == 200:
20
- data = resp.json()
21
- return data.get("display_name")
22
- except Exception as e:
23
- print(f"⚠️ Reverse geocoding failed: {e}")
24
- return None
25
 
 
26
  def extract_gps_from_image(image):
27
- """
28
- Extract GPS coordinates and human-readable address from EXIF metadata.
29
- Tries both PIL and exifread for Xiaomi/Android phones.
30
- """
31
- # --- First try PIL ---
32
  try:
33
- exifdata = image.getexif()
34
- if exifdata and "GPSInfo" in exifdata:
35
- gps_info = {}
36
- for t, val in exifdata["GPSInfo"].items():
37
- gps_info[GPSTAGS.get(t, t)] = val
38
- lat = gps_info.get("GPSLatitude")
39
- lon = gps_info.get("GPSLongitude")
 
 
 
40
  if lat and lon:
41
- lat = dms_to_decimal(lat, gps_info.get("GPSLatitudeRef", "N"))
42
- lon = dms_to_decimal(lon, gps_info.get("GPSLongitudeRef", "E"))
43
- return {"coords": (lat, lon), "address": reverse_geocode(lat, lon)}
44
- except Exception as e:
45
- print(f"⚠️ PIL GPS parse failed: {e}")
46
 
47
- # --- Fallback: use exifread ---
48
  try:
49
  import io
 
 
 
50
  buf = io.BytesIO()
51
  image.save(buf, format="JPEG")
52
  buf.seek(0)
 
53
  tags = exifread.process_file(buf, details=False)
54
 
55
- lat = tags.get("GPS GPSLatitude")
56
- lon = tags.get("GPS GPSLongitude")
57
- lat_ref = tags.get("GPS GPSLatitudeRef")
58
- lon_ref = tags.get("GPS GPSLongitudeRef")
59
-
60
- if lat and lon and lat_ref and lon_ref:
61
- # Convert to [d, m, s]
62
- lat_vals = [x.num / x.den for x in lat.values]
63
- lon_vals = [x.num / x.den for x in lon.values]
64
- lat_dd = dms_to_decimal(lat_vals, str(lat_ref))
65
- lon_dd = dms_to_decimal(lon_vals, str(lon_ref))
66
- return {"coords": (lat_dd, lon_dd), "address": reverse_geocode(lat_dd, lon_dd)}
67
  except Exception as e:
68
- print(f"⚠️ exifread GPS parse failed: {e}")
69
 
70
  return {"coords": None, "address": None}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ utils.py - GPS, Maps, and Statistics helpers
3
+ """
4
+
5
+ import os, json
6
+ from pathlib import Path
7
+ from datetime import datetime
8
+ from PIL import ExifTags
9
  import exifread
10
+ import folium
11
+ from folium import plugins
12
+ from config import HISTORY_FILE
13
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
 
15
+ # ---------------- GPS Extraction ----------------
16
  def extract_gps_from_image(image):
17
+ """Try GPS extraction via PIL, fallback to exifread"""
18
+ # 1️⃣ Try PIL
 
 
 
19
  try:
20
+ exif = image.getexif()
21
+ gps_info = {}
22
+ for tag_id, val in exif.items():
23
+ tag = ExifTags.TAGS.get(tag_id, tag_id)
24
+ if tag == "GPSInfo":
25
+ gps_info = {ExifTags.GPSTAGS.get(t, t): v for t, v in val.items()}
26
+
27
+ if gps_info:
28
+ lat = _to_decimal(gps_info.get("GPSLatitude"), gps_info.get("GPSLatitudeRef"))
29
+ lon = _to_decimal(gps_info.get("GPSLongitude"), gps_info.get("GPSLongitudeRef"))
30
  if lat and lon:
31
+ return {"coords": (lat, lon), "address": None}
32
+ except:
33
+ pass
 
 
34
 
35
+ # 2️⃣ Fallback: exifread
36
  try:
37
  import io
38
+ import numpy as np
39
+ from PIL import Image
40
+
41
  buf = io.BytesIO()
42
  image.save(buf, format="JPEG")
43
  buf.seek(0)
44
+
45
  tags = exifread.process_file(buf, details=False)
46
 
47
+ lat = _to_decimal(tags.get("GPS GPSLatitude"), tags.get("GPS GPSLatitudeRef"))
48
+ lon = _to_decimal(tags.get("GPS GPSLongitude"), tags.get("GPS GPSLongitudeRef"))
49
+
50
+ if lat and lon:
51
+ return {"coords": (lat, lon), "address": None}
 
 
 
 
 
 
 
52
  except Exception as e:
53
+ print(f"⚠️ exifread failed: {e}")
54
 
55
  return {"coords": None, "address": None}
56
+
57
+
58
+ def _to_decimal(dms, ref):
59
+ """Convert GPS dms to decimal"""
60
+ if not dms:
61
+ return None
62
+ try:
63
+ d, m, s = [float(x.num) / float(x.den) if hasattr(x, "num") else float(x) for x in dms]
64
+ decimal = d + (m / 60.0) + (s / 3600.0)
65
+ if isinstance(ref, str) and ref in ["S", "W"]:
66
+ decimal = -decimal
67
+ return decimal
68
+ except Exception as e:
69
+ print(f"⚠️ GPS decode failed: {e}")
70
+ return None
71
+
72
+
73
+ # ---------------- History ----------------
74
+ def save_detection_to_history(detection):
75
+ Path("data").mkdir(exist_ok=True)
76
+ history = []
77
+ if os.path.exists(HISTORY_FILE):
78
+ with open(HISTORY_FILE, "r") as f:
79
+ history = json.load(f)
80
+
81
+ history.append(detection)
82
+ if len(history) > 100:
83
+ history = history[-100:]
84
+
85
+ with open(HISTORY_FILE, "w") as f:
86
+ json.dump(history, f, indent=2)
87
+
88
+
89
+ def load_detection_history():
90
+ if os.path.exists(HISTORY_FILE):
91
+ with open(HISTORY_FILE, "r") as f:
92
+ return json.load(f)
93
+ return []
94
+
95
+
96
+ # ---------------- Map ----------------
97
+ def create_detection_map(detections):
98
+ if not detections:
99
+ return "<p>🗺️ No GPS data yet</p>"
100
+
101
+ center = detections[-1]["gps"]["coords"] if detections[-1]["gps"]["coords"] else (41.015, 28.979) # Istanbul default
102
+ m = folium.Map(location=center, zoom_start=12)
103
+ plugins.Fullscreen().add_to(m)
104
+
105
+ for d in detections:
106
+ gps = d.get("gps", {})
107
+ if gps and gps.get("coords"):
108
+ lat, lon = gps["coords"]
109
+ sev = d.get("severity", "LOW")
110
+ color = {"HIGH": "red", "MEDIUM": "orange", "LOW": "yellow"}.get(sev, "blue")
111
+ popup = f"<b>{sev}</b> | {d['count']} items"
112
+ folium.Marker([lat, lon], popup=popup, icon=folium.Icon(color=color)).add_to(m)
113
+
114
+ return m._repr_html_()
115
+
116
+
117
+ # ---------------- Stats ----------------
118
+ def generate_statistics():
119
+ history = load_detection_history()
120
+ if not history:
121
+ return {"total": 0, "items": 0, "high": 0, "medium": 0, "low": 0}
122
+
123
+ return {
124
+ "total": len(history),
125
+ "items": sum(h["count"] for h in history),
126
+ "high": sum(1 for h in history if h["severity"] == "HIGH"),
127
+ "medium": sum(1 for h in history if h["severity"] == "MEDIUM"),
128
+ "low": sum(1 for h in history if h["severity"] == "LOW"),
129
+ }
130
+
131
+
132
+ def format_statistics_text(stats):
133
+ if stats["total"] == 0:
134
+ return "📊 No detections yet"
135
+ return (
136
+ f"📊 Reports: {stats['total']}\n"
137
+ f"📦 Items: {stats['items']}\n"
138
+ f"🔴 High: {stats['high']} | 🟠 Medium: {stats['medium']} | 🟡 Low: {stats['low']}"
139
+ )