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"{loc}
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"
{map_html}
", 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="
" "Map will appear here after processing
" ) 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()