Tohru127 commited on
Commit
f03c78d
·
verified ·
1 Parent(s): ddc6b30

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +180 -0
app.py ADDED
@@ -0,0 +1,180 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Mapillary Finder — Minimal MVP
3
+ ==============================
4
+ Goal: Start slow. Take an **address** string, geocode it, search Mapillary near that spot,
5
+ and show the nearest images (thumbnails + metadata).
6
+
7
+ What you get
8
+ ------------
9
+ - Text input: address (e.g., "College Station, TX, USA")
10
+ - Button: "Find in Mapillary"
11
+ - Output:
12
+ - The resolved coordinates of the address
13
+ - A small table of the **nearest Mapillary images** (id, distance, timestamp, bearing)
14
+ - A **thumbnail gallery** of those images
15
+
16
+ Requirements
17
+ ------------
18
+ - Environment variable: `MAPILLARY_TOKEN` (long‑lived token from your Mapillary account)
19
+ - `pip install -r requirements.txt` where requirements.txt contains:
20
+ gradio>=4.44.0
21
+ requests
22
+ geopy
23
+ numpy
24
+
25
+ Run
26
+ ---
27
+ python app.py
28
+
29
+ Notes
30
+ -----
31
+ - We query Graph API v4 `/images` using a **tiny bounding box** around the geocoded point.
32
+ - You can increase the search radius in `pad_meters` if coverage is sparse.
33
+ - We prefer 2048px thumbnails when available; fall back to 1024.
34
+ """
35
+
36
+ import os
37
+ import math
38
+ import json
39
+ from typing import List, Tuple
40
+ from pathlib import Path
41
+
42
+ import gradio as gr
43
+ import requests
44
+ import numpy as np
45
+ from geopy.geocoders import Nominatim
46
+
47
+ MAPILLARY_TOKEN = os.getenv("MAPILLARY_TOKEN", "")
48
+
49
+ # ---------------------- Helpers ----------------------
50
+
51
+ def geocode(address: str) -> Tuple[float, float]:
52
+ geo = Nominatim(user_agent="mapillary-finder-mvp")
53
+ res = geo.geocode(address)
54
+ if not res:
55
+ raise RuntimeError("Could not geocode that address. Try a more specific query.")
56
+ return float(res.latitude), float(res.longitude)
57
+
58
+
59
+ def pad_bbox(lat: float, lon: float, pad_meters: float = 80.0) -> Tuple[float, float, float, float]:
60
+ # Rough meters→degrees conversion near the given latitude
61
+ dlat = pad_meters / 111_111.0
62
+ dlon = pad_meters / (111_111.0 * math.cos(math.radians(lat)))
63
+ return (lon - dlon, lat - dlat, lon + dlon, lat + dlat)
64
+
65
+
66
+ def haversine_km(lat1, lon1, lat2, lon2) -> float:
67
+ R = 6371.0088
68
+ p1 = math.radians(lat1)
69
+ p2 = math.radians(lat2)
70
+ dp = p2 - p1
71
+ dl = math.radians(lon2 - lon1)
72
+ a = math.sin(dp/2)**2 + math.cos(p1)*math.cos(p2)*math.sin(dl/2)**2
73
+ return 2*R*math.asin(math.sqrt(a))
74
+
75
+
76
+ def mapillary_search_bbox(bbox, limit=50):
77
+ if not MAPILLARY_TOKEN:
78
+ raise RuntimeError("MAPILLARY_TOKEN not set. Add it as an environment variable.")
79
+ url = "https://graph.mapillary.com/images"
80
+ fields = [
81
+ "id",
82
+ "thumb_2048_url",
83
+ "thumb_1024_url",
84
+ "computed_geometry",
85
+ "captured_at",
86
+ "compass_angle",
87
+ "camera_type",
88
+ "sequence",
89
+ ]
90
+ params = {
91
+ "access_token": MAPILLARY_TOKEN,
92
+ "fields": ",".join(fields),
93
+ "limit": min(limit, 200),
94
+ "bbox": f"{bbox[0]},{bbox[1]},{bbox[2]},{bbox[3]}",
95
+ }
96
+ r = requests.get(url, params=params, timeout=60)
97
+ r.raise_for_status()
98
+ return r.json().get("data", [])
99
+
100
+
101
+ def find_nearest_images(address: str, pad_m: float = 80.0, topk: int = 12):
102
+ lat, lon = geocode(address)
103
+ bbox = pad_bbox(lat, lon, pad_m)
104
+ items = mapillary_search_bbox(bbox, limit=100)
105
+
106
+ if not items:
107
+ return (
108
+ f"📍 {address}\nGeocoded to lat={lat:.6f}, lon={lon:.6f}.\n\nNo Mapillary images found in ~{int(pad_m)} m. Try increasing the radius.",
109
+ None,
110
+ None,
111
+ )
112
+
113
+ rows = []
114
+ thumbs = []
115
+ for it in items:
116
+ geom = it.get("computed_geometry", {})
117
+ coords = geom.get("coordinates") if geom else None
118
+ if not coords or len(coords) != 2:
119
+ continue
120
+ ilon, ilat = float(coords[0]), float(coords[1])
121
+ dist_km = haversine_km(lat, lon, ilat, ilon)
122
+ thumb = it.get("thumb_2048_url") or it.get("thumb_1024_url")
123
+ rows.append({
124
+ "id": it.get("id"),
125
+ "lat": round(ilat, 6),
126
+ "lon": round(ilon, 6),
127
+ "distance_m": int(dist_km * 1000),
128
+ "captured_at": it.get("captured_at"),
129
+ "compass": it.get("compass_angle"),
130
+ "camera_type": it.get("camera_type"),
131
+ "thumb": thumb,
132
+ })
133
+
134
+ # sort by distance and keep topk
135
+ rows.sort(key=lambda r: r["distance_m"])
136
+ top = rows[:topk]
137
+
138
+ # gallery expects list of image URLs/paths
139
+ gallery = [r["thumb"] for r in top if r.get("thumb")]
140
+
141
+ # small pretty JSON table (string) for display
142
+ pretty = [
143
+ {k: v for k, v in r.items() if k != "thumb"}
144
+ for r in top
145
+ ]
146
+ info = (
147
+ f"📍 **{address}** → lat={lat:.6f}, lon={lon:.6f}\n"
148
+ f"Found {len(rows)} images within ~{int(pad_m)} m. Showing {len(pretty)} nearest."
149
+ )
150
+ return info, json.dumps(pretty, indent=2), gallery
151
+
152
+ # ---------------------- UI ----------------------
153
+ with gr.Blocks(title="Mapillary Finder — Minimal", fill_height=True) as demo:
154
+ gr.Markdown("""
155
+ # 🗺️ Mapillary Finder — Minimal
156
+ Enter an **address**. I’ll geocode it and fetch the **nearest Mapillary images** in a small radius.
157
+ Use this to verify coverage before attempting reconstruction.
158
+ """)
159
+
160
+ with gr.Row():
161
+ with gr.Column(scale=1):
162
+ addr = gr.Textbox(label="Address", value="College Station, TX, USA")
163
+ radius = gr.Slider(40, 300, step=10, value=80, label="Search radius (meters)")
164
+ k = gr.Slider(4, 24, step=1, value=12, label="Max images to show")
165
+ btn = gr.Button("Find in Mapillary", variant="primary")
166
+ msg = gr.Markdown("Ready.")
167
+ with gr.Column(scale=1):
168
+ table = gr.Code(label="Nearest images (JSON)")
169
+ gallery = gr.Gallery(label="Thumbnails").style(grid=[4], height="auto")
170
+
171
+ def _run(a, r, topk):
172
+ try:
173
+ return find_nearest_images(a, r, int(topk))
174
+ except Exception as e:
175
+ return f"Error: {e}", None, None
176
+
177
+ btn.click(_run, inputs=[addr, radius, k], outputs=[msg, table, gallery])
178
+
179
+ if __name__ == "__main__":
180
+ demo.launch(server_name="0.0.0.0", server_port=int(os.getenv("PORT", 7860)))