Spaces:
Running
on
Zero
Running
on
Zero
import gradio as gr | |
import pandas as pd | |
import folium | |
from geopy.geocoders import Nominatim | |
from geopy.extra.rate_limiter import RateLimiter | |
import tempfile | |
from typing import Optional, Tuple | |
import warnings | |
# Suppress warnings | |
warnings.filterwarnings("ignore") | |
# Historical Tile Providers | |
HISTORICAL_TILES = { | |
"David Rumsey (1790)": { | |
"url": "https://map1.davidrumsey.com/tiles/rumsey/SDSC1790/{z}/{x}/{y}.png", | |
"attr": "David Rumsey Map Collection", | |
"min_year": 1700, | |
"max_year": 1800 | |
}, | |
"David Rumsey (1860)": { | |
"url": "https://map1.davidrumsey.com/tiles/rumsey/SDSC1860/{z}/{x}/{y}.png", | |
"attr": "David Rumsey Map Collection", | |
"min_year": 1801, | |
"max_year": 1900 | |
}, | |
"Stamen (1915)": { | |
"url": "https://stamen-tiles.a.ssl.fastly.net/toner-lite/{z}/{x}/{y}.png", | |
"attr": "Stamen Maps", | |
"min_year": 1901, | |
"max_year": 1920 | |
}, | |
"OpenHistoricalMap": { | |
"url": "https://tile.openhistoricalmap.org/{z}/{x}/{y}.png", | |
"attr": "OpenHistoricalMap", | |
"min_year": 1700, | |
"max_year": 2023 | |
} | |
} | |
class Geocoder: | |
def __init__(self): | |
self.geolocator = Nominatim(user_agent="historical_mapper", timeout=10) | |
self.geocode = RateLimiter(self.geolocator.geocode, min_delay_seconds=1) | |
self.cache = {} | |
def get_coords(self, location: str) -> Optional[Tuple[float, float]]: | |
if not location or pd.isna(location): | |
return None | |
if location in self.cache: | |
return self.cache[location] | |
try: | |
result = self.geocode(location) | |
if result: | |
coords = (result.latitude, result.longitude) | |
self.cache[location] = coords | |
return coords | |
except Exception as e: | |
print(f"Geocoding failed for '{location}': {str(e)}") | |
self.cache[location] = None | |
return None | |
def get_tile_layer(year: int): | |
"""Select the most appropriate tile layer for the given year""" | |
for name, config in HISTORICAL_TILES.items(): | |
if config["min_year"] <= year <= config["max_year"]: | |
return folium.TileLayer( | |
tiles=config["url"], | |
attr=config["attr"], | |
name=name, | |
overlay=False | |
) | |
return folium.TileLayer("OpenStreetMap") | |
def create_historical_map(df: pd.DataFrame, location_col: str, year: int = 1900) -> str: | |
geocoder = Geocoder() | |
# Create map with historical base layer | |
base_layer = get_tile_layer(year) | |
m = folium.Map(location=[40, -10], zoom_start=2, control_scale=True) | |
base_layer.add_to(m) | |
# Add all other historical layers as options | |
for name, config in HISTORICAL_TILES.items(): | |
if config["url"] != base_layer.tiles: | |
folium.TileLayer( | |
tiles=config["url"], | |
attr=config["attr"], | |
name=f"{name} ({config['min_year']}-{config['max_year']})", | |
overlay=False | |
).add_to(m) | |
# Add markers with historical styling | |
coords_list = [] | |
for loc in df[location_col].dropna().unique(): | |
coords = geocoder.get_coords(str(loc)) | |
if coords: | |
folium.Marker( | |
location=coords, | |
popup=f"<b>{loc}</b><br>Year: {year}", | |
icon=folium.Icon( | |
color="red", | |
icon="info-sign", | |
prefix="fa" | |
) | |
).add_to(m) | |
coords_list.append(coords) | |
# Add layer control and fit bounds | |
folium.LayerControl().add_to(m) | |
if coords_list: | |
m.fit_bounds(coords_list) | |
return m._repr_html_() | |
def process_file(file_obj, location_col, year): | |
try: | |
# Read input file | |
df = pd.read_excel(file_obj.name) | |
# Validate column exists | |
if location_col not in df.columns: | |
return None, f"Column '{location_col}' not found", None | |
# Create historical map | |
map_html = create_historical_map(df, location_col, year) | |
# Save processed data | |
with tempfile.NamedTemporaryFile(suffix=".xlsx", delete=False) as tmp: | |
df.to_excel(tmp.name, index=False) | |
processed_path = tmp.name | |
# Generate stats | |
stats = ( | |
f"Total locations: {len(df)}\n" | |
f"Unique places: {df[location_col].nunique()}\n" | |
f"Map year: {year}" | |
) | |
return ( | |
f"<div style='width:100%; height:70vh'>{map_html}</div>", | |
stats, | |
processed_path | |
) | |
except Exception as e: | |
return None, f"Error: {str(e)}", None | |
# Gradio Interface | |
with gr.Blocks(title="Historical Map Explorer", theme=gr.themes.Soft()) as app: | |
gr.Markdown("# Historical Location Mapper") | |
with gr.Row(): | |
with gr.Column(): | |
file_input = gr.File( | |
label="Upload Excel File", | |
file_types=[".xlsx", ".xls"], | |
type="filepath" | |
) | |
location_col = gr.Textbox( | |
label="Location Column Name", | |
value="locations", | |
placeholder="Enter exact column name with locations" | |
) | |
year = gr.Slider( | |
minimum=1700, | |
maximum=2023, | |
value=1900, | |
step=1, | |
label="Map Year" | |
) | |
map_btn = gr.Button("Generate Historical Map", variant="primary") | |
with gr.Column(): | |
map_display = gr.HTML( | |
label="Historical Map", | |
value="<div style='text-align:center;padding:20px;'>" | |
"Map will appear here after processing</div>" | |
) | |
stats_output = gr.Textbox(label="Statistics") | |
download_output = gr.File(label="Download Processed Data") | |
map_btn.click( | |
process_file, | |
inputs=[file_input, location_col, year], | |
outputs=[map_display, stats_output, download_output] | |
) | |
if __name__ == "__main__": | |
app.launch() |