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"