Geoeasy commited on
Commit
be53510
·
verified ·
1 Parent(s): 659df74

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +117 -8
app.py CHANGED
@@ -6,6 +6,7 @@ from geopy.geocoders import Nominatim
6
  import osmnx as ox
7
  import folium
8
  import branca
 
9
 
10
  # --- Initial settings ---
11
  geolocator = Nominatim(user_agent="gradio_osm_app")
@@ -17,15 +18,117 @@ os.makedirs(DOWNLOAD_DIR, exist_ok=True)
17
  def slugify(name):
18
  return re.sub(r"[^0-9A-Za-z]+", "_", name).strip("_")
19
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
  def ensure_saved(gdf, slug, layer):
21
- if gdf.empty:
22
  return
23
  filename = f"{slug}_{layer}.gpkg"
24
  path = os.path.join(DOWNLOAD_DIR, filename)
25
  if os.path.exists(path):
26
  os.remove(path)
27
- gdf.to_file(path, driver="GPKG")
28
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29
  def make_legend(selected_layers):
30
  color_map = {
31
  "Highways": "yellow",
@@ -68,7 +171,7 @@ style_funcs = {
68
 
69
  def map_with_layers(place_name, cb_highways, cb_buildings, cb_school,
70
  cb_fire, cb_hospital, cb_police, cb_rest, cb_hotels, cb_monuments):
71
- if not place_name.strip():
72
  yield None, "❌ Please enter a valid place name."
73
  return
74
  slug = slugify(place_name)
@@ -92,10 +195,14 @@ def map_with_layers(place_name, cb_highways, cb_buildings, cb_school,
92
  ensure_saved(gdf, slug, 'highways')
93
 
94
  tag_map = {
95
- 'Buildings': {'building': True}, 'School': {'amenity': 'school'},
96
- 'Fire Station': {'amenity': 'fire_station'}, 'Hospital': {'amenity': 'hospital'},
97
- 'Police Station': {'amenity': 'police'}, 'Restaurants': {'amenity': 'restaurant'},
98
- 'Hotels': {'tourism': 'hotel'}, 'Monuments': {'historic': 'monument'}
 
 
 
 
99
  }
100
  flags = [cb_buildings, cb_school, cb_fire, cb_hospital,
101
  cb_police, cb_rest, cb_hotels, cb_monuments]
@@ -103,6 +210,7 @@ def map_with_layers(place_name, cb_highways, cb_buildings, cb_school,
103
  if flag:
104
  yield None, f"🔄 Downloading {name}..."
105
  gdf2 = ox.features_from_polygon(poly, tags)
 
106
  gdf2 = gdf2[gdf2.geometry.type.isin(['Polygon', 'MultiPolygon'])]
107
  if not gdf2.empty:
108
  layers[name] = gdf2
@@ -115,7 +223,7 @@ def map_with_layers(place_name, cb_highways, cb_buildings, cb_school,
115
  continue
116
  has_name = 'name' in gdf.columns and gdf['name'].notna().any()
117
  cols = ['geometry', 'name'] if has_name else ['geometry']
118
- gj = folium.GeoJson(gdf[cols], name=name, style_function=style_funcs[name])
119
  if has_name:
120
  gj.add_child(folium.GeoJsonPopup(fields=['name'], labels=False))
121
  gj.add_to(m)
@@ -230,3 +338,4 @@ with gr.Blocks(title="Geoeasy View") as demo:
230
 
231
  if __name__ == '__main__':
232
  demo.launch()
 
 
6
  import osmnx as ox
7
  import folium
8
  import branca
9
+ import pandas as pd
10
 
11
  # --- Initial settings ---
12
  geolocator = Nominatim(user_agent="gradio_osm_app")
 
18
  def slugify(name):
19
  return re.sub(r"[^0-9A-Za-z]+", "_", name).strip("_")
20
 
21
+ # --------- Utilidades para salvar GPKG sem erros ----------
22
+ def _is_complex_obj(v):
23
+ # Tipos que o OGR não aceita direto como campo (list, dict, set, tuple, bytes)
24
+ return isinstance(v, (list, dict, tuple, set, bytes))
25
+
26
+ def clean_for_gpkg(gdf):
27
+ """Sanitiza colunas e dtypes para gravação segura em GPKG."""
28
+ gdf = gdf.copy()
29
+
30
+ # CRS
31
+ if getattr(gdf, "crs", None) is None:
32
+ try:
33
+ gdf.set_crs(4326, inplace=True)
34
+ except Exception:
35
+ pass
36
+
37
+ # Nomes de colunas seguros e não muito longos
38
+ gdf.columns = (
39
+ pd.Index(gdf.columns)
40
+ .map(str)
41
+ .str.replace(r"[^0-9a-zA-Z_]", "_", regex=True)
42
+ .str.replace(r"_{2,}", "_", regex=True)
43
+ .str.strip("_")
44
+ .str.slice(0, 60)
45
+ )
46
+
47
+ # Converter dtypes problemáticos
48
+ for col in gdf.columns:
49
+ if col == gdf.geometry.name:
50
+ continue
51
+ dt = gdf[col].dtype
52
+
53
+ # Pandas tipo string nativo ajuda a evitar NaN/None
54
+ if dt == "object":
55
+ # Se houver qualquer valor complexo, vira string integral
56
+ if gdf[col].apply(_is_complex_obj).any():
57
+ gdf[col] = gdf[col].astype(str)
58
+ else:
59
+ # Objetos simples (str/num/None) -> string estável
60
+ gdf[col] = gdf[col].astype("string")
61
+
62
+ # Inteiros nulos (Int64) -> float64 (OGR lida melhor) ou string
63
+ if pd.api.types.is_integer_dtype(dt) and str(dt).startswith("Int"):
64
+ gdf[col] = gdf[col].astype("float64")
65
+
66
+ # Booleans com nulos -> string "true/false" para evitar falhas
67
+ if pd.api.types.is_bool_dtype(dt):
68
+ if gdf[col].isna().any():
69
+ gdf[col] = gdf[col].astype("string").fillna("")
70
+ else:
71
+ # bool puro costuma funcionar, mantém
72
+ pass
73
+
74
+ # Datetime -> manter como datetime64[ns]; OGR aceita (como DateTime)
75
+ # Se quiser, pode converter para string ISO:
76
+ # if pd.api.types.is_datetime64_any_dtype(dt):
77
+ # gdf[col] = gdf[col].dt.strftime("%Y-%m-%d %H:%M:%S").astype("string")
78
+
79
+ # Preencher NaN em strings
80
+ if gdf[col].dtype == "string":
81
+ gdf[col] = gdf[col].fillna("")
82
+
83
+ return gdf
84
+
85
+ def try_to_file(gdf, path, driver="GPKG"):
86
+ """Tenta salvar; em caso de erro por campo, remove apenas o campo problemático e tenta novamente."""
87
+ try:
88
+ gdf.to_file(path, driver=driver)
89
+ return
90
+ except Exception as e:
91
+ msg = str(e)
92
+ # Detecta qual campo quebrou (mensagem típica do pyogrio/OGR)
93
+ # Ex.: "Error adding field 'fixme' to layer"
94
+ m = re.search(r"field '([^']+)'", msg)
95
+ if m:
96
+ bad = m.group(1)
97
+ if bad in gdf.columns:
98
+ gdf2 = gdf.drop(columns=[bad])
99
+ gdf2.to_file(path, driver=driver)
100
+ return
101
+ # Se não identificar, relança
102
+ raise
103
+
104
  def ensure_saved(gdf, slug, layer):
105
+ if gdf is None or gdf.empty:
106
  return
107
  filename = f"{slug}_{layer}.gpkg"
108
  path = os.path.join(DOWNLOAD_DIR, filename)
109
  if os.path.exists(path):
110
  os.remove(path)
 
111
 
112
+ gdf_clean = clean_for_gpkg(gdf)
113
+
114
+ # Fallback final: se ainda der erro, tenta remover colunas 'problemáticas' comuns em OSM
115
+ try:
116
+ try_to_file(gdf_clean, path, driver="GPKG")
117
+ except Exception:
118
+ # Remove colunas muito propensas a erro
119
+ drop_candidates = [c for c in gdf_clean.columns if c.lower() in {"fixme", "note", "source_ref"}]
120
+ if drop_candidates:
121
+ gdf_clean2 = gdf_clean.drop(columns=drop_candidates, errors="ignore")
122
+ try_to_file(gdf_clean2, path, driver="GPKG")
123
+ else:
124
+ # Sem pistas: tenta converter tudo não-geom para string como último recurso
125
+ gdf_last = gdf_clean.copy()
126
+ for c in gdf_last.columns:
127
+ if c != gdf_last.geometry.name:
128
+ gdf_last[c] = gdf_last[c].astype(str)
129
+ try_to_file(gdf_last, path, driver="GPKG")
130
+
131
+ # --------- UI / Lógica principal ----------
132
  def make_legend(selected_layers):
133
  color_map = {
134
  "Highways": "yellow",
 
171
 
172
  def map_with_layers(place_name, cb_highways, cb_buildings, cb_school,
173
  cb_fire, cb_hospital, cb_police, cb_rest, cb_hotels, cb_monuments):
174
+ if not place_name or not place_name.strip():
175
  yield None, "❌ Please enter a valid place name."
176
  return
177
  slug = slugify(place_name)
 
195
  ensure_saved(gdf, slug, 'highways')
196
 
197
  tag_map = {
198
+ 'Buildings': {'building': True},
199
+ 'School': {'amenity': 'school'},
200
+ 'Fire Station': {'amenity': 'fire_station'},
201
+ 'Hospital': {'amenity': 'hospital'},
202
+ 'Police Station': {'amenity': 'police'},
203
+ 'Restaurants': {'amenity': 'restaurant'},
204
+ 'Hotels': {'tourism': 'hotel'},
205
+ 'Monuments': {'historic': 'monument'}
206
  }
207
  flags = [cb_buildings, cb_school, cb_fire, cb_hospital,
208
  cb_police, cb_rest, cb_hotels, cb_monuments]
 
210
  if flag:
211
  yield None, f"🔄 Downloading {name}..."
212
  gdf2 = ox.features_from_polygon(poly, tags)
213
+ # Apenas polígonos para essas camadas
214
  gdf2 = gdf2[gdf2.geometry.type.isin(['Polygon', 'MultiPolygon'])]
215
  if not gdf2.empty:
216
  layers[name] = gdf2
 
223
  continue
224
  has_name = 'name' in gdf.columns and gdf['name'].notna().any()
225
  cols = ['geometry', 'name'] if has_name else ['geometry']
226
+ gj = folium.GeoJson(gdf[cols], name=name, style_function=style_funcs.get(name, lambda f: {}))
227
  if has_name:
228
  gj.add_child(folium.GeoJsonPopup(fields=['name'], labels=False))
229
  gj.add_to(m)
 
338
 
339
  if __name__ == '__main__':
340
  demo.launch()
341
+