HistorySpace / data.py
oberbics's picture
Update data.py
47c367a verified
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()