Spaces:
Running
Running
Commit
Β·
3141721
1
Parent(s):
65927d1
Add converted Gradio app
Browse files- app.py +42 -131
- requirements.txt +6 -3
app.py
CHANGED
@@ -16,12 +16,6 @@
|
|
16 |
|
17 |
|
18 |
|
19 |
-
|
20 |
-
|
21 |
-
|
22 |
-
|
23 |
-
|
24 |
-
|
25 |
import gradio as gr
|
26 |
import requests
|
27 |
import pyproj
|
@@ -38,7 +32,6 @@ import time
|
|
38 |
|
39 |
|
40 |
|
41 |
-
|
42 |
# OSM Overpass API URL
|
43 |
OVERPASS_URL = "https://overpass-api.de/api/interpreter"
|
44 |
|
@@ -48,9 +41,8 @@ def latlon_to_utm(lat: float, lon: float) -> Tuple[float, float]:
|
|
48 |
x, y = proj(lon, lat) # Note: pyproj uses (lon, lat) order
|
49 |
return x, y
|
50 |
|
51 |
-
|
52 |
-
|
53 |
def fetch_osm_data(lat: float, lon: float, radius: int = 500) -> Optional[Dict]:
|
|
|
54 |
query = f"""
|
55 |
[out:json];
|
56 |
(
|
@@ -60,22 +52,17 @@ def fetch_osm_data(lat: float, lon: float, radius: int = 500) -> Optional[Dict]:
|
|
60 |
>;
|
61 |
out skel qt;
|
62 |
"""
|
63 |
-
|
64 |
try:
|
65 |
-
|
66 |
-
|
67 |
-
|
68 |
-
|
|
|
69 |
return None
|
70 |
-
|
71 |
-
data = r.json()
|
72 |
-
print(f"β
Fetched {len(data.get('elements', []))} OSM elements")
|
73 |
-
return data
|
74 |
-
|
75 |
except Exception as e:
|
76 |
-
print(f"
|
77 |
-
|
78 |
-
|
79 |
|
80 |
def parse_osm_data(osm_data: Dict) -> List[Dict]:
|
81 |
"""Extract building footprints and heights from OSM data."""
|
@@ -119,130 +106,60 @@ def parse_osm_data(osm_data: Dict) -> List[Dict]:
|
|
119 |
return buildings
|
120 |
|
121 |
def create_3d_model(buildings: List[Dict]) -> trimesh.Scene:
|
122 |
-
"""Create a 3D model using trimesh with
|
123 |
scene = trimesh.Scene()
|
124 |
|
125 |
-
for
|
126 |
-
debug = (i < 5)
|
127 |
footprint = building["footprint"]
|
128 |
height = building.get("height", 10)
|
129 |
-
|
130 |
-
print(f"ποΈ Building #{i}: {len(footprint)} points, height={height}")
|
131 |
if height <= 0:
|
132 |
-
if debug: print(" β³ skipping: non-positive height")
|
133 |
continue
|
134 |
-
|
135 |
-
# build the polygon
|
136 |
try:
|
137 |
polygon = sg.Polygon(footprint)
|
138 |
-
if
|
139 |
-
print(f" β³ polygon valid={polygon.is_valid}, area={polygon.area:.2f}")
|
140 |
-
if not polygon.is_valid or polygon.area == 0:
|
141 |
polygon = polygon.buffer(0)
|
142 |
-
if
|
143 |
-
print(f" β³ after buffer valid={polygon.is_valid}, area={polygon.area:.2f}")
|
144 |
-
if not polygon.is_valid or polygon.area == 0:
|
145 |
-
if debug: print(" β³ skipping: invalid or zero-area polygon")
|
146 |
continue
|
147 |
-
except Exception
|
148 |
-
if debug: print(f" β³ skipping: polygon error: {e}")
|
149 |
continue
|
150 |
-
|
151 |
try:
|
152 |
-
# Try
|
153 |
-
extruded = None
|
154 |
-
|
155 |
-
# Method 1: Try triangle engine
|
156 |
try:
|
157 |
extruded = trimesh.creation.extrude_polygon(polygon, height, engine="triangle")
|
158 |
-
|
159 |
-
except (ImportError, ValueError) as e:
|
160 |
-
if debug: print(f" β³ triangle engine error ({e})")
|
161 |
-
|
162 |
-
# Method 2: Try earcut engine
|
163 |
-
if extruded is None:
|
164 |
try:
|
165 |
extruded = trimesh.creation.extrude_polygon(polygon, height, engine="earcut")
|
166 |
-
|
167 |
-
|
168 |
-
if debug: print(f" β³ earcut engine error ({e})")
|
169 |
|
170 |
-
#
|
171 |
-
|
172 |
-
|
173 |
-
|
174 |
-
# Get polygon exterior coordinates
|
175 |
-
coords = list(polygon.exterior.coords[:-1]) # Remove duplicate last point
|
176 |
-
|
177 |
-
# Create bottom vertices
|
178 |
-
bottom_vertices = np.array([[x, y, 0] for x, y in coords])
|
179 |
-
# Create top vertices
|
180 |
-
top_vertices = np.array([[x, y, height] for x, y in coords])
|
181 |
-
# Combine all vertices
|
182 |
-
vertices = np.vstack([bottom_vertices, top_vertices])
|
183 |
-
|
184 |
-
# Create faces
|
185 |
-
faces = []
|
186 |
-
n = len(coords)
|
187 |
-
|
188 |
-
# Triangulate bottom face (reversed for correct normal)
|
189 |
-
if n >= 3:
|
190 |
-
bottom_triangles = simple_triangulate_polygon(list(range(n)))
|
191 |
-
for tri in bottom_triangles:
|
192 |
-
faces.append([tri[2], tri[1], tri[0]]) # Reverse for correct normal
|
193 |
-
|
194 |
-
# Triangulate top face
|
195 |
-
if n >= 3:
|
196 |
-
top_triangles = simple_triangulate_polygon(list(range(n)))
|
197 |
-
for tri in top_triangles:
|
198 |
-
faces.append([tri[0] + n, tri[1] + n, tri[2] + n])
|
199 |
-
|
200 |
-
# Side faces
|
201 |
-
for i in range(n):
|
202 |
-
next_i = (i + 1) % n
|
203 |
-
# Two triangles per side
|
204 |
-
faces.append([i, next_i, next_i + n])
|
205 |
-
faces.append([i, next_i + n, i + n])
|
206 |
-
|
207 |
-
extruded = trimesh.Trimesh(vertices=vertices, faces=faces)
|
208 |
-
if debug: print(" β³ manual extrusion successful")
|
209 |
|
210 |
-
|
211 |
-
|
212 |
-
|
213 |
-
|
214 |
-
|
215 |
-
|
216 |
-
|
217 |
-
|
218 |
-
|
219 |
-
|
220 |
-
|
221 |
-
if debug: print(" β³ added to scene")
|
222 |
-
else:
|
223 |
-
if debug: print(" β³ all extrusion methods failed")
|
224 |
-
|
225 |
-
except Exception as e:
|
226 |
-
if debug: print(f" β³ skipping: extrusion/orientation error: {e}")
|
227 |
continue
|
228 |
-
|
229 |
-
print(f"π§ Final scene.geometry count: {len(scene.geometry)}")
|
230 |
return scene
|
231 |
|
232 |
-
|
233 |
-
|
234 |
def save_3d_model(scene: trimesh.Scene, filename: str) -> bool:
|
235 |
"""Export the 3D scene to a GLB file."""
|
236 |
try:
|
237 |
scene.export(filename)
|
238 |
-
|
239 |
-
|
240 |
-
if file_exists:
|
241 |
-
print("πΎ filesize:", os.path.getsize(filename))
|
242 |
-
return file_exists
|
243 |
-
|
244 |
-
except Exception as e:
|
245 |
-
print(f"β Error saving 3D model: {e}")
|
246 |
return False
|
247 |
|
248 |
|
@@ -280,18 +197,14 @@ def generate_3d_model(latitude: float, longitude: float, radius: int) -> Tuple[s
|
|
280 |
# Step 3: Create 3D model
|
281 |
status_msg += f"\nβ
Found {len(buildings)} buildings\nπ Creating 3D model..."
|
282 |
scene = create_3d_model(buildings)
|
283 |
-
print(f"π§ trimesh scene has {len(scene.geometry)} geometries")
|
284 |
-
|
285 |
|
286 |
if len(scene.geometry) == 0:
|
287 |
return None, "β Could not create 3D model from the buildings found.", ""
|
288 |
-
|
|
|
289 |
timestamp = int(time.time())
|
290 |
filename = f"osm_3d_model_{timestamp}.glb"
|
291 |
|
292 |
-
# Ensure we can write to the current directory
|
293 |
-
os.makedirs(os.path.dirname(os.path.abspath(filename)) if os.path.dirname(filename) else ".", exist_ok=True)
|
294 |
-
|
295 |
status_msg += f"\nβ
3D model created with {len(scene.geometry)} buildings\nπΎ Saving model..."
|
296 |
|
297 |
if save_3d_model(scene, filename):
|
@@ -312,10 +225,7 @@ def generate_3d_model(latitude: float, longitude: float, radius: int) -> Tuple[s
|
|
312 |
return None, "β Failed to save 3D model file.", ""
|
313 |
|
314 |
except Exception as e:
|
315 |
-
|
316 |
-
traceback.print_exc()
|
317 |
-
raise
|
318 |
-
|
319 |
|
320 |
|
321 |
|
@@ -462,6 +372,7 @@ app = create_gradio_app()
|
|
462 |
# Launch the app
|
463 |
if __name__ == "__main__":
|
464 |
app.launch(
|
|
|
465 |
server_name="0.0.0.0", # Allow external connections
|
466 |
server_port=7860, # Standard port for Hugging Face
|
467 |
show_error=True,
|
|
|
16 |
|
17 |
|
18 |
|
|
|
|
|
|
|
|
|
|
|
|
|
19 |
import gradio as gr
|
20 |
import requests
|
21 |
import pyproj
|
|
|
32 |
|
33 |
|
34 |
|
|
|
35 |
# OSM Overpass API URL
|
36 |
OVERPASS_URL = "https://overpass-api.de/api/interpreter"
|
37 |
|
|
|
41 |
x, y = proj(lon, lat) # Note: pyproj uses (lon, lat) order
|
42 |
return x, y
|
43 |
|
|
|
|
|
44 |
def fetch_osm_data(lat: float, lon: float, radius: int = 500) -> Optional[Dict]:
|
45 |
+
"""Fetch OSM data for buildings within a given radius of a coordinate."""
|
46 |
query = f"""
|
47 |
[out:json];
|
48 |
(
|
|
|
52 |
>;
|
53 |
out skel qt;
|
54 |
"""
|
55 |
+
|
56 |
try:
|
57 |
+
response = requests.get(OVERPASS_URL, params={"data": query}, timeout=30)
|
58 |
+
if response.status_code == 200:
|
59 |
+
data = response.json()
|
60 |
+
return data
|
61 |
+
else:
|
62 |
return None
|
|
|
|
|
|
|
|
|
|
|
63 |
except Exception as e:
|
64 |
+
print(f"Error fetching OSM data: {e}")
|
65 |
+
return None
|
|
|
66 |
|
67 |
def parse_osm_data(osm_data: Dict) -> List[Dict]:
|
68 |
"""Extract building footprints and heights from OSM data."""
|
|
|
106 |
return buildings
|
107 |
|
108 |
def create_3d_model(buildings: List[Dict]) -> trimesh.Scene:
|
109 |
+
"""Create a 3D model using trimesh with PROPER ORIENTATION FIX."""
|
110 |
scene = trimesh.Scene()
|
111 |
|
112 |
+
for building in buildings:
|
|
|
113 |
footprint = building["footprint"]
|
114 |
height = building.get("height", 10)
|
115 |
+
|
|
|
116 |
if height <= 0:
|
|
|
117 |
continue
|
118 |
+
|
|
|
119 |
try:
|
120 |
polygon = sg.Polygon(footprint)
|
121 |
+
if not polygon.is_valid:
|
|
|
|
|
122 |
polygon = polygon.buffer(0)
|
123 |
+
if not polygon.is_valid:
|
|
|
|
|
|
|
124 |
continue
|
125 |
+
except Exception:
|
|
|
126 |
continue
|
127 |
+
|
128 |
try:
|
129 |
+
# Try triangle engine first, then earcut
|
|
|
|
|
|
|
130 |
try:
|
131 |
extruded = trimesh.creation.extrude_polygon(polygon, height, engine="triangle")
|
132 |
+
except ValueError:
|
|
|
|
|
|
|
|
|
|
|
133 |
try:
|
134 |
extruded = trimesh.creation.extrude_polygon(polygon, height, engine="earcut")
|
135 |
+
except ValueError:
|
136 |
+
continue
|
|
|
137 |
|
138 |
+
# β
PROPER ORIENTATION FIX - This is the solution you provided
|
139 |
+
# This rotates the model so the front view shows properly
|
140 |
+
transform_x = trimesh.transformations.rotation_matrix(np.pi/2, (1, 0, 0))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
141 |
|
142 |
+
# Also rotate around Z-axis for proper left-right orientation
|
143 |
+
transform_z = trimesh.transformations.rotation_matrix(np.pi, (0, 0, 1))
|
144 |
+
|
145 |
+
# Apply the transformations
|
146 |
+
extruded.apply_transform(transform_x)
|
147 |
+
extruded.apply_transform(transform_z)
|
148 |
+
|
149 |
+
# Add to scene
|
150 |
+
scene.add_geometry(extruded)
|
151 |
+
|
152 |
+
except Exception:
|
|
|
|
|
|
|
|
|
|
|
|
|
153 |
continue
|
154 |
+
|
|
|
155 |
return scene
|
156 |
|
|
|
|
|
157 |
def save_3d_model(scene: trimesh.Scene, filename: str) -> bool:
|
158 |
"""Export the 3D scene to a GLB file."""
|
159 |
try:
|
160 |
scene.export(filename)
|
161 |
+
return os.path.exists(filename)
|
162 |
+
except Exception:
|
|
|
|
|
|
|
|
|
|
|
|
|
163 |
return False
|
164 |
|
165 |
|
|
|
197 |
# Step 3: Create 3D model
|
198 |
status_msg += f"\nβ
Found {len(buildings)} buildings\nπ Creating 3D model..."
|
199 |
scene = create_3d_model(buildings)
|
|
|
|
|
200 |
|
201 |
if len(scene.geometry) == 0:
|
202 |
return None, "β Could not create 3D model from the buildings found.", ""
|
203 |
+
|
204 |
+
# Step 4: Save model
|
205 |
timestamp = int(time.time())
|
206 |
filename = f"osm_3d_model_{timestamp}.glb"
|
207 |
|
|
|
|
|
|
|
208 |
status_msg += f"\nβ
3D model created with {len(scene.geometry)} buildings\nπΎ Saving model..."
|
209 |
|
210 |
if save_3d_model(scene, filename):
|
|
|
225 |
return None, "β Failed to save 3D model file.", ""
|
226 |
|
227 |
except Exception as e:
|
228 |
+
return None, f"β Unexpected error: {str(e)}", ""
|
|
|
|
|
|
|
229 |
|
230 |
|
231 |
|
|
|
372 |
# Launch the app
|
373 |
if __name__ == "__main__":
|
374 |
app.launch(
|
375 |
+
share=True, # Creates public link for Hugging Face
|
376 |
server_name="0.0.0.0", # Allow external connections
|
377 |
server_port=7860, # Standard port for Hugging Face
|
378 |
show_error=True,
|
requirements.txt
CHANGED
@@ -1,6 +1,9 @@
|
|
1 |
gradio
|
2 |
-
|
3 |
-
pyproj
|
4 |
shapely
|
|
|
5 |
trimesh
|
6 |
-
numpy
|
|
|
|
|
|
|
|
1 |
gradio
|
2 |
+
osmnx
|
|
|
3 |
shapely
|
4 |
+
pyproj
|
5 |
trimesh
|
6 |
+
numpy
|
7 |
+
requests
|
8 |
+
triangle
|
9 |
+
mapbox_earcut
|