Spaces:
Running
on
Zero
Running
on
Zero
Update app.py
Browse files
app.py
CHANGED
@@ -82,74 +82,204 @@ class SafeGeocoder:
|
|
82 |
self.cache[location] = None
|
83 |
return None
|
84 |
|
85 |
-
|
86 |
-
|
87 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
88 |
|
89 |
try:
|
90 |
-
|
91 |
-
|
92 |
-
|
93 |
-
|
94 |
-
|
95 |
-
|
96 |
-
|
97 |
-
|
98 |
-
|
99 |
-
|
100 |
-
|
101 |
-
|
102 |
-
|
103 |
-
|
104 |
-
|
105 |
-
|
106 |
-
|
107 |
-
|
108 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
109 |
|
110 |
-
#
|
111 |
-
for
|
112 |
-
|
113 |
-
|
114 |
-
|
115 |
-
location = str(row[places_column]).strip()
|
116 |
|
117 |
-
|
118 |
-
|
119 |
-
|
120 |
-
|
121 |
-
|
122 |
-
|
123 |
-
|
124 |
-
|
125 |
-
|
126 |
-
|
127 |
-
|
128 |
-
|
129 |
-
|
130 |
-
|
131 |
-
|
132 |
-
|
133 |
-
|
134 |
-
|
135 |
-
|
136 |
-
|
137 |
-
|
138 |
-
|
139 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
140 |
|
141 |
-
|
142 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
143 |
|
144 |
-
|
|
|
|
|
|
|
|
|
|
|
145 |
|
146 |
-
|
147 |
-
|
148 |
-
|
149 |
-
|
150 |
-
|
151 |
-
|
152 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
153 |
|
154 |
# Corrected model loading function based on official usage example
|
155 |
|
|
|
82 |
self.cache[location] = None
|
83 |
return None
|
84 |
|
85 |
+
|
86 |
+
def process_excel(file, places_column):
|
87 |
+
if file is None:
|
88 |
+
return None, "No file uploaded", None
|
89 |
+
|
90 |
+
try:
|
91 |
+
if hasattr(file, 'name'):
|
92 |
+
df = pd.read_excel(file.name)
|
93 |
+
elif isinstance(file, bytes):
|
94 |
+
df = pd.read_excel(io.BytesIO(file))
|
95 |
+
else:
|
96 |
+
df = pd.read_excel(file)
|
97 |
+
|
98 |
+
print(f"Spalten in der Excel-Tabelle: {list(df.columns)}")
|
99 |
+
|
100 |
+
if places_column not in df.columns:
|
101 |
+
return None, f"Spalte '{places_column}' wurde in der Excel-Datei nicht gefunden. Verfügbare Spalten: {', '.join(df.columns)}", None
|
102 |
+
|
103 |
+
# Create coordinates columns
|
104 |
+
df['latitude'] = None
|
105 |
+
df['longitude'] = None
|
106 |
+
# Add column for structured location data
|
107 |
+
df['location_data'] = None
|
108 |
+
|
109 |
+
geocoder = SafeGeocoder()
|
110 |
+
coords = []
|
111 |
+
processed_count = 0
|
112 |
+
|
113 |
+
# Process each location and store as JSON
|
114 |
+
for idx, row in df.iterrows():
|
115 |
+
if pd.isna(row[places_column]):
|
116 |
+
continue
|
117 |
+
|
118 |
+
location = str(row[places_column]).strip()
|
119 |
|
120 |
try:
|
121 |
+
locations = [loc.strip() for loc in location.split(',') if loc.strip()]
|
122 |
+
if not locations:
|
123 |
+
locations = [location]
|
124 |
+
except:
|
125 |
+
locations = [location]
|
126 |
+
|
127 |
+
# Process all locations and store as JSON
|
128 |
+
location_data = []
|
129 |
+
for loc in locations:
|
130 |
+
point = geocoder.get_coords(loc)
|
131 |
+
if point:
|
132 |
+
location_data.append({
|
133 |
+
'location': loc,
|
134 |
+
'latitude': point[0],
|
135 |
+
'longitude': point[1]
|
136 |
+
})
|
137 |
+
coords.append(point)
|
138 |
+
processed_count += 1
|
139 |
+
|
140 |
+
if location_data:
|
141 |
+
df.at[idx, 'location_data'] = json.dumps(location_data)
|
142 |
+
# Store first location's coordinates in main columns for compatibility
|
143 |
+
df.at[idx, 'latitude'] = location_data[0]['latitude']
|
144 |
+
df.at[idx, 'longitude'] = location_data[0]['longitude']
|
145 |
+
|
146 |
+
# Create the map
|
147 |
+
map_html, _ = create_map(df, places_column)
|
148 |
+
|
149 |
+
# Save the updated DataFrame to a new Excel file
|
150 |
+
with tempfile.NamedTemporaryFile(suffix=".xlsx", delete=False) as tmp:
|
151 |
+
processed_path = tmp.name
|
152 |
+
df.to_excel(processed_path, index=False)
|
153 |
+
|
154 |
+
total_locations = df[places_column].count()
|
155 |
+
success_rate = (processed_count / total_locations * 100) if total_locations > 0 else 0
|
156 |
+
|
157 |
+
stats = f"Gefunden: {processed_count} von {total_locations} Orten ({success_rate:.1f}%)"
|
158 |
+
|
159 |
+
return map_html, stats, processed_path
|
160 |
+
except Exception as e:
|
161 |
+
import traceback
|
162 |
+
trace = traceback.format_exc()
|
163 |
+
print(f"Error processing file: {e}\n{trace}")
|
164 |
+
return None, f"Fehler bei der Verarbeitung der Datei: {str(e)}", None
|
165 |
+
|
166 |
+
|
167 |
+
def create_map(df, location_col):
|
168 |
+
m = folium.Map(
|
169 |
+
location=[20, 0],
|
170 |
+
zoom_start=2,
|
171 |
+
control_scale=True
|
172 |
+
)
|
173 |
+
|
174 |
+
folium.TileLayer(
|
175 |
+
tiles=MAP_TILES["GreenMap"]["url"],
|
176 |
+
attr=MAP_TILES["GreenMap"]["attr"],
|
177 |
+
name="GreenMap",
|
178 |
+
overlay=False,
|
179 |
+
control=False
|
180 |
+
).add_to(m)
|
181 |
+
|
182 |
+
Fullscreen().add_to(m)
|
183 |
+
MeasureControl(position='topright', primary_length_unit='kilometers').add_to(m)
|
184 |
+
|
185 |
+
coords = []
|
186 |
+
marker_cluster = MarkerCluster(name="Locations").add_to(m)
|
187 |
+
processed_count = 0
|
188 |
+
|
189 |
+
for idx, row in df.iterrows():
|
190 |
+
# Check if we have JSON location data
|
191 |
+
if 'location_data' in df.columns and not pd.isna(row['location_data']):
|
192 |
+
try:
|
193 |
+
# Parse the JSON location data
|
194 |
+
location_data = json.loads(row['location_data'])
|
195 |
|
196 |
+
# Process each location in the JSON data
|
197 |
+
for loc_item in location_data:
|
198 |
+
location_name = loc_item['location']
|
199 |
+
point = (loc_item['latitude'], loc_item['longitude'])
|
|
|
|
|
200 |
|
201 |
+
# Create additional info for popup
|
202 |
+
additional_info = ""
|
203 |
+
for col in df.columns:
|
204 |
+
if col not in [location_col, 'location_data', 'latitude', 'longitude'] and not pd.isna(row[col]):
|
205 |
+
additional_info += f"<br><b>{col}:</b> {row[col]}"
|
206 |
+
|
207 |
+
# Add marker to map
|
208 |
+
popup_content = f"""
|
209 |
+
<div style="min-width: 200px; max-width: 300px">
|
210 |
+
<h4 style="font-family: 'Source Sans Pro', sans-serif; margin-bottom: 5px;">{location_name}</h4>
|
211 |
+
<div style="font-family: 'Source Sans Pro', sans-serif; font-size: 14px;">
|
212 |
+
{additional_info}
|
213 |
+
</div>
|
214 |
+
</div>
|
215 |
+
"""
|
216 |
+
|
217 |
+
folium.Marker(
|
218 |
+
location=point,
|
219 |
+
popup=folium.Popup(popup_content, max_width=300),
|
220 |
+
tooltip=location_name,
|
221 |
+
icon=folium.Icon(color="blue", icon="info-sign")
|
222 |
+
).add_to(marker_cluster)
|
223 |
+
|
224 |
+
coords.append(point)
|
225 |
+
processed_count += 1
|
226 |
+
except (json.JSONDecodeError, KeyError) as e:
|
227 |
+
print(f"Error processing JSON location data for row {idx}: {e}")
|
228 |
+
# Continue with fallback method if JSON processing fails
|
229 |
+
|
230 |
+
# Fallback to the old method if no JSON data is present
|
231 |
+
elif not pd.isna(row[location_col]):
|
232 |
+
location = str(row[location_col]).strip()
|
233 |
+
|
234 |
+
additional_info = ""
|
235 |
+
for col in df.columns:
|
236 |
+
if col != location_col and not pd.isna(row[col]):
|
237 |
+
additional_info += f"<br><b>{col}:</b> {row[col]}"
|
238 |
+
|
239 |
+
# If we have latitude and longitude directly
|
240 |
+
if 'latitude' in df.columns and 'longitude' in df.columns and not pd.isna(row['latitude']) and not pd.isna(row['longitude']):
|
241 |
+
point = (row['latitude'], row['longitude'])
|
242 |
|
243 |
+
popup_content = f"""
|
244 |
+
<div style="min-width: 200px; max-width: 300px">
|
245 |
+
<h4 style="font-family: 'Source Sans Pro', sans-serif; margin-bottom: 5px;">{location}</h4>
|
246 |
+
<div style="font-family: 'Source Sans Pro', sans-serif; font-size: 14px;">
|
247 |
+
{additional_info}
|
248 |
+
</div>
|
249 |
+
</div>
|
250 |
+
"""
|
251 |
|
252 |
+
folium.Marker(
|
253 |
+
location=point,
|
254 |
+
popup=folium.Popup(popup_content, max_width=300),
|
255 |
+
tooltip=location,
|
256 |
+
icon=folium.Icon(color="blue", icon="info-sign")
|
257 |
+
).add_to(marker_cluster)
|
258 |
|
259 |
+
coords.append(point)
|
260 |
+
processed_count += 1
|
261 |
+
|
262 |
+
if coords:
|
263 |
+
m.fit_bounds(coords)
|
264 |
+
|
265 |
+
custom_css = """
|
266 |
+
<style>
|
267 |
+
@import url('https://fonts.googleapis.com/css2?family=Source+Sans+Pro:wght@400;600&display=swap');
|
268 |
+
.leaflet-container {
|
269 |
+
font-family: 'Source Sans Pro', sans-serif;
|
270 |
+
}
|
271 |
+
.leaflet-popup-content {
|
272 |
+
font-family: 'Source Sans Pro', sans-serif;
|
273 |
+
}
|
274 |
+
.leaflet-popup-content h4 {
|
275 |
+
font-weight: 600;
|
276 |
+
margin-bottom: 8px;
|
277 |
+
}
|
278 |
+
</style>
|
279 |
+
"""
|
280 |
+
m.get_root().header.add_child(folium.Element(custom_css))
|
281 |
+
|
282 |
+
return m._repr_html_(), processed_count
|
283 |
|
284 |
# Corrected model loading function based on official usage example
|
285 |
|