Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -21,17 +21,9 @@ Run:
|
|
| 21 |
python app.py
|
| 22 |
"""
|
| 23 |
|
| 24 |
-
import os
|
| 25 |
-
import tempfile
|
| 26 |
-
import time
|
| 27 |
-
|
| 28 |
import gradio as gr
|
| 29 |
import folium
|
| 30 |
import openpyxl
|
| 31 |
-
from selenium import webdriver
|
| 32 |
-
from selenium.webdriver.chrome.options import Options as ChromeOptions
|
| 33 |
-
from selenium.webdriver.support.ui import WebDriverWait
|
| 34 |
-
from selenium.common.exceptions import TimeoutException, WebDriverException
|
| 35 |
|
| 36 |
import state_lookup
|
| 37 |
|
|
@@ -45,7 +37,6 @@ COLOR_PALETTE = [
|
|
| 45 |
_cached_filepath = None
|
| 46 |
_cached_section_configs = None
|
| 47 |
_cached_data = None
|
| 48 |
-
_last_map_html = None # full standalone HTML of the most recently generated map, for PNG export
|
| 49 |
|
| 50 |
|
| 51 |
def parse_coords(coord_str):
|
|
@@ -234,6 +225,48 @@ def build_map_legend(section_configs):
|
|
| 234 |
)
|
| 235 |
|
| 236 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 237 |
def build_sidebar_legend(section_configs):
|
| 238 |
if not section_configs:
|
| 239 |
return "<p><em>Upload a file to see the colour legend.</em></p>"
|
|
@@ -387,54 +420,9 @@ def generate_map(
|
|
| 387 |
<span style="color:#555">{stats_text}</span>
|
| 388 |
</div>"""))
|
| 389 |
|
| 390 |
-
|
| 391 |
-
_last_map_html = fig.render()
|
| 392 |
-
return fig._repr_html_()
|
| 393 |
|
| 394 |
-
|
| 395 |
-
def export_map_png():
|
| 396 |
-
"""Render the last generated map with headless Chrome and return a PNG file path."""
|
| 397 |
-
if _last_map_html is None:
|
| 398 |
-
raise gr.Error("Generate a map first, then export it.")
|
| 399 |
-
|
| 400 |
-
tmp_dir = tempfile.mkdtemp(prefix="d2m_")
|
| 401 |
-
html_path = os.path.join(tmp_dir, "map.html")
|
| 402 |
-
png_path = os.path.join(tmp_dir, "map.png")
|
| 403 |
-
with open(html_path, "w", encoding="utf-8") as f:
|
| 404 |
-
f.write(_last_map_html)
|
| 405 |
-
|
| 406 |
-
opts = ChromeOptions()
|
| 407 |
-
opts.add_argument("--headless=new")
|
| 408 |
-
opts.add_argument("--no-sandbox")
|
| 409 |
-
opts.add_argument("--disable-dev-shm-usage")
|
| 410 |
-
opts.add_argument("--hide-scrollbars")
|
| 411 |
-
opts.add_argument("--window-size=1900,1200")
|
| 412 |
-
|
| 413 |
-
try:
|
| 414 |
-
driver = webdriver.Chrome(options=opts)
|
| 415 |
-
except WebDriverException as e:
|
| 416 |
-
raise gr.Error(
|
| 417 |
-
"Could not start headless Chrome for the export. "
|
| 418 |
-
f"Make sure Google Chrome or Chromium is installed. ({e})"
|
| 419 |
-
)
|
| 420 |
-
|
| 421 |
-
try:
|
| 422 |
-
driver.get(f"file://{html_path}")
|
| 423 |
-
try:
|
| 424 |
-
WebDriverWait(driver, 10).until(
|
| 425 |
-
lambda d: d.execute_script(
|
| 426 |
-
"return document.querySelectorAll('.leaflet-tile-loading').length === 0 "
|
| 427 |
-
"&& document.querySelectorAll('.leaflet-tile-loaded').length > 0"
|
| 428 |
-
)
|
| 429 |
-
)
|
| 430 |
-
except TimeoutException:
|
| 431 |
-
pass # export whatever tiles did load rather than failing outright
|
| 432 |
-
time.sleep(0.5)
|
| 433 |
-
driver.save_screenshot(png_path)
|
| 434 |
-
finally:
|
| 435 |
-
driver.quit()
|
| 436 |
-
|
| 437 |
-
return gr.update(value=png_path, visible=True)
|
| 438 |
|
| 439 |
|
| 440 |
# ββββββββββββββββββββββββββββββββββββββββββββββ
|
|
@@ -522,11 +510,9 @@ with gr.Blocks(title="Standorte Karte Deutschland", theme=gr.themes.Soft()) as d
|
|
| 522 |
value="<p style='color:#888;padding:40px;text-align:center;'>"
|
| 523 |
"Upload a file and click <b>Generate Map</b> to begin.</p>",
|
| 524 |
)
|
| 525 |
-
|
| 526 |
-
|
| 527 |
-
|
| 528 |
-
interactive=False,
|
| 529 |
-
visible=False,
|
| 530 |
)
|
| 531 |
|
| 532 |
# ββ Event wiring βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
@@ -574,12 +560,6 @@ with gr.Blocks(title="Standorte Karte Deutschland", theme=gr.themes.Soft()) as d
|
|
| 574 |
outputs=[map_output],
|
| 575 |
)
|
| 576 |
|
| 577 |
-
export_btn.click(
|
| 578 |
-
fn=export_map_png,
|
| 579 |
-
inputs=[],
|
| 580 |
-
outputs=[export_image],
|
| 581 |
-
)
|
| 582 |
-
|
| 583 |
|
| 584 |
if __name__ == "__main__":
|
| 585 |
demo.launch(share=False, server_port=7860, inbrowser=True)
|
|
|
|
| 21 |
python app.py
|
| 22 |
"""
|
| 23 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 24 |
import gradio as gr
|
| 25 |
import folium
|
| 26 |
import openpyxl
|
|
|
|
|
|
|
|
|
|
|
|
|
| 27 |
|
| 28 |
import state_lookup
|
| 29 |
|
|
|
|
| 37 |
_cached_filepath = None
|
| 38 |
_cached_section_configs = None
|
| 39 |
_cached_data = None
|
|
|
|
| 40 |
|
| 41 |
|
| 42 |
def parse_coords(coord_str):
|
|
|
|
| 225 |
)
|
| 226 |
|
| 227 |
|
| 228 |
+
def build_export_button():
|
| 229 |
+
"""A button + inline script that rasterizes the map (tiles, markers, overlays) to a
|
| 230 |
+
downloadable PNG entirely in the visitor's browser via html2canvas β no server-side
|
| 231 |
+
headless browser needed, so it works the same locally and on hosted deployments."""
|
| 232 |
+
return """
|
| 233 |
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js"></script>
|
| 234 |
+
<button id="d2m-export-btn" onclick="d2mExportPNG()" style="
|
| 235 |
+
position:fixed;top:10px;left:50%;transform:translateX(-50%);z-index:1000;
|
| 236 |
+
background:white;border:1px solid #ccc;border-radius:6px;padding:8px 14px;
|
| 237 |
+
font-size:13px;cursor:pointer;box-shadow:2px 2px 6px rgba(0,0,0,0.2);">
|
| 238 |
+
π₯ Download PNG
|
| 239 |
+
</button>
|
| 240 |
+
<script>
|
| 241 |
+
function d2mExportPNG() {
|
| 242 |
+
var btn = document.getElementById('d2m-export-btn');
|
| 243 |
+
var originalText = btn.innerHTML;
|
| 244 |
+
if (typeof html2canvas === 'undefined') {
|
| 245 |
+
alert('Export library failed to load β check your internet connection and try again.');
|
| 246 |
+
return;
|
| 247 |
+
}
|
| 248 |
+
btn.innerHTML = 'β³ Renderingβ¦';
|
| 249 |
+
btn.disabled = true;
|
| 250 |
+
btn.style.visibility = 'hidden';
|
| 251 |
+
html2canvas(document.body, {useCORS: true, backgroundColor: '#ffffff'}).then(function(canvas) {
|
| 252 |
+
var link = document.createElement('a');
|
| 253 |
+
link.download = 'standorte-map.png';
|
| 254 |
+
link.href = canvas.toDataURL('image/png');
|
| 255 |
+
document.body.appendChild(link);
|
| 256 |
+
link.click();
|
| 257 |
+
document.body.removeChild(link);
|
| 258 |
+
}).catch(function(err) {
|
| 259 |
+
alert('Export failed: ' + err);
|
| 260 |
+
}).finally(function() {
|
| 261 |
+
btn.innerHTML = originalText;
|
| 262 |
+
btn.disabled = false;
|
| 263 |
+
btn.style.visibility = 'visible';
|
| 264 |
+
});
|
| 265 |
+
}
|
| 266 |
+
</script>
|
| 267 |
+
"""
|
| 268 |
+
|
| 269 |
+
|
| 270 |
def build_sidebar_legend(section_configs):
|
| 271 |
if not section_configs:
|
| 272 |
return "<p><em>Upload a file to see the colour legend.</em></p>"
|
|
|
|
| 420 |
<span style="color:#555">{stats_text}</span>
|
| 421 |
</div>"""))
|
| 422 |
|
| 423 |
+
m.get_root().html.add_child(folium.Element(build_export_button()))
|
|
|
|
|
|
|
| 424 |
|
| 425 |
+
return fig._repr_html_()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 426 |
|
| 427 |
|
| 428 |
# ββββββββββββββββββββββββββββββββββββββββββββββ
|
|
|
|
| 510 |
value="<p style='color:#888;padding:40px;text-align:center;'>"
|
| 511 |
"Upload a file and click <b>Generate Map</b> to begin.</p>",
|
| 512 |
)
|
| 513 |
+
gr.Markdown(
|
| 514 |
+
"_Use the **π₯ Download PNG** button at the top of the generated map "
|
| 515 |
+
"to save it as an image._"
|
|
|
|
|
|
|
| 516 |
)
|
| 517 |
|
| 518 |
# ββ Event wiring βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
|
|
| 560 |
outputs=[map_output],
|
| 561 |
)
|
| 562 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 563 |
|
| 564 |
if __name__ == "__main__":
|
| 565 |
demo.launch(share=False, server_port=7860, inbrowser=True)
|