Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
@@ -8,133 +8,126 @@ from datetime import datetime
|
|
8 |
|
9 |
warnings.filterwarnings("ignore")
|
10 |
|
11 |
-
# Historical
|
12 |
-
|
13 |
-
"
|
14 |
-
"url": "https://map1.davidrumsey.com/tiles/rumsey/SDSC1790/{z}/{x}/{y}.png",
|
15 |
-
"attr": "Rumsey 1794",
|
16 |
-
"year_range": (1700, 1800),
|
17 |
-
"default_zoom": 2
|
18 |
-
},
|
19 |
-
"1800s": {
|
20 |
"url": "https://map1.davidrumsey.com/tiles/rumsey/SDSC1860/{z}/{x}/{y}.png",
|
21 |
-
"attr": "
|
22 |
-
"
|
23 |
-
"
|
24 |
},
|
25 |
-
"
|
26 |
"url": "https://stamen-tiles.a.ssl.fastly.net/toner-lite/{z}/{x}/{y}.png",
|
27 |
-
"attr": "Stamen
|
28 |
-
"
|
29 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
30 |
}
|
31 |
}
|
32 |
|
33 |
-
class
|
34 |
def __init__(self):
|
35 |
-
self.geolocator = Nominatim(user_agent="
|
36 |
-
self.cache = {}
|
37 |
|
38 |
-
def get_coords(self, location: str
|
39 |
-
"""Get coordinates with simple historical adjustments"""
|
40 |
-
cache_key = f"{location}_{year}"
|
41 |
-
if cache_key in self.cache:
|
42 |
-
return self.cache[cache_key]
|
43 |
-
|
44 |
try:
|
45 |
-
result = self.geolocator.geocode(location
|
46 |
-
if
|
47 |
-
|
48 |
-
|
49 |
-
lat, lon = result.latitude, result.longitude
|
50 |
-
|
51 |
-
# Simple historical adjustments (example only)
|
52 |
-
if year < 1800:
|
53 |
-
# Older maps often had shifted coordinates
|
54 |
-
lon += 0.2
|
55 |
-
lat += 0.1
|
56 |
-
elif year < 1900:
|
57 |
-
lon += 0.1
|
58 |
-
|
59 |
-
self.cache[cache_key] = (lat, lon)
|
60 |
-
return (lat, lon)
|
61 |
-
|
62 |
-
except Exception as e:
|
63 |
-
print(f"Geocoding failed: {str(e)}")
|
64 |
return None
|
65 |
|
66 |
-
def
|
67 |
-
|
68 |
-
|
69 |
-
|
70 |
-
|
71 |
-
|
72 |
-
if bm["year_range"][0] <= year <= bm["year_range"][1]),
|
73 |
-
HISTORICAL_BASEMAPS["1800s"]
|
74 |
)
|
75 |
|
76 |
-
#
|
77 |
-
m = folium.Map(
|
78 |
-
location=[40, 0],
|
79 |
-
zoom_start=basemap["default_zoom"],
|
80 |
-
tiles=basemap["url"],
|
81 |
-
attr=basemap["attr"],
|
82 |
-
control_scale=True
|
83 |
-
)
|
84 |
|
85 |
-
#
|
86 |
-
|
87 |
-
|
88 |
-
|
89 |
-
|
90 |
-
|
91 |
-
|
92 |
-
|
93 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
94 |
|
95 |
# Add markers
|
|
|
96 |
coords = []
|
97 |
for loc in df[location_col].dropna().unique():
|
98 |
-
point = geocoder.get_coords(str(loc)
|
99 |
if point:
|
100 |
folium.Marker(
|
101 |
location=point,
|
102 |
-
popup=f"<b>{loc}</b><br><i>
|
103 |
-
icon=folium.Icon(
|
104 |
-
color="red" if year < 1850 else "blue",
|
105 |
-
icon="history" if year < 1850 else "info-sign",
|
106 |
-
prefix="fa"
|
107 |
-
)
|
108 |
).add_to(m)
|
109 |
coords.append(point)
|
110 |
|
111 |
-
#
|
112 |
folium.LayerControl().add_to(m)
|
113 |
if coords:
|
114 |
m.fit_bounds(coords)
|
115 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
116 |
return m._repr_html_()
|
117 |
|
118 |
-
def
|
119 |
try:
|
120 |
-
# Read file
|
121 |
df = pd.read_excel(file_obj.name)
|
122 |
|
123 |
-
# Validate column
|
124 |
if location_col not in df.columns:
|
125 |
-
return None, f"Column '{location_col}' not found"
|
126 |
|
127 |
-
|
128 |
-
map_html = create_historical_map(df, location_col, year)
|
129 |
|
130 |
-
# Save processed data
|
131 |
with tempfile.NamedTemporaryFile(suffix=".xlsx", delete=False) as tmp:
|
132 |
df.to_excel(tmp.name, index=False)
|
133 |
processed_path = tmp.name
|
134 |
|
|
|
135 |
return (
|
136 |
-
f"<div style='width:100%; height:70vh'>{map_html}</div>",
|
137 |
-
|
138 |
processed_path
|
139 |
)
|
140 |
|
@@ -142,26 +135,26 @@ def process_file(file_obj, location_col, year):
|
|
142 |
return None, f"Error: {str(e)}", None
|
143 |
|
144 |
# Gradio Interface
|
145 |
-
with gr.Blocks(title="Historical
|
146 |
-
gr.Markdown("# Historical
|
147 |
|
148 |
with gr.Row():
|
149 |
with gr.Column():
|
150 |
file_input = gr.File(
|
151 |
label="1. Upload Excel File",
|
152 |
-
file_types=[".xlsx"
|
153 |
type="filepath"
|
154 |
)
|
155 |
location_col = gr.Textbox(
|
156 |
label="2. Location Column Name",
|
157 |
value="location",
|
158 |
-
|
159 |
)
|
160 |
year = gr.Slider(
|
161 |
-
label="3.
|
162 |
minimum=1700,
|
163 |
maximum=1920,
|
164 |
-
value=
|
165 |
step=1
|
166 |
)
|
167 |
btn = gr.Button("Generate Map", variant="primary")
|
@@ -169,14 +162,14 @@ with gr.Blocks(title="Historical Map Explorer", theme=gr.themes.Soft()) as app:
|
|
169 |
with gr.Column():
|
170 |
map_display = gr.HTML(
|
171 |
label="Historical Map",
|
172 |
-
value="<div style='text-align:center;padding:2em;color:gray'>"
|
173 |
-
"Map will appear here</div>"
|
174 |
)
|
175 |
-
stats = gr.Textbox(label="Map
|
176 |
-
download = gr.File(label="Download Processed Data"
|
177 |
|
178 |
btn.click(
|
179 |
-
|
180 |
inputs=[file_input, location_col, year],
|
181 |
outputs=[map_display, stats, download]
|
182 |
)
|
|
|
8 |
|
9 |
warnings.filterwarnings("ignore")
|
10 |
|
11 |
+
# Reliable Historical Tile Providers with fallbacks
|
12 |
+
HISTORICAL_TILES = {
|
13 |
+
"Colton 1865": {
|
|
|
|
|
|
|
|
|
|
|
|
|
14 |
"url": "https://map1.davidrumsey.com/tiles/rumsey/SDSC1860/{z}/{x}/{y}.png",
|
15 |
+
"attr": "David Rumsey Map Collection",
|
16 |
+
"fallback": "https://stamen-tiles.a.ssl.fastly.net/toner-lite/{z}/{x}/{y}.png",
|
17 |
+
"years": (1800, 1900)
|
18 |
},
|
19 |
+
"Stamen 1915": {
|
20 |
"url": "https://stamen-tiles.a.ssl.fastly.net/toner-lite/{z}/{x}/{y}.png",
|
21 |
+
"attr": "Stamen Maps",
|
22 |
+
"fallback": "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
|
23 |
+
"years": (1901, 1920)
|
24 |
+
},
|
25 |
+
"OpenStreetMap": {
|
26 |
+
"url": "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
|
27 |
+
"attr": "OpenStreetMap",
|
28 |
+
"fallback": None,
|
29 |
+
"years": (1700, 2023)
|
30 |
}
|
31 |
}
|
32 |
|
33 |
+
class SafeGeocoder:
|
34 |
def __init__(self):
|
35 |
+
self.geolocator = Nominatim(user_agent="historical_mapper_v6", timeout=10)
|
|
|
36 |
|
37 |
+
def get_coords(self, location: str):
|
|
|
|
|
|
|
|
|
|
|
38 |
try:
|
39 |
+
result = self.geolocator.geocode(location)
|
40 |
+
return (result.latitude, result.longitude) if result else None
|
41 |
+
except:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
42 |
return None
|
43 |
|
44 |
+
def create_reliable_map(df, location_col, year):
|
45 |
+
# Select appropriate tile configuration
|
46 |
+
tile_config = next(
|
47 |
+
(t for t in HISTORICAL_TILES.values()
|
48 |
+
if t["years"][0] <= year <= t["years"][1]),
|
49 |
+
HISTORICAL_TILES["OpenStreetMap"]
|
|
|
|
|
50 |
)
|
51 |
|
52 |
+
# Initialize map with fallback support
|
53 |
+
m = folium.Map(location=[51.5, -0.1], zoom_start=5, control_scale=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
54 |
|
55 |
+
# Try primary tile layer
|
56 |
+
primary_layer = folium.TileLayer(
|
57 |
+
tiles=tile_config["url"],
|
58 |
+
attr=tile_config["attr"],
|
59 |
+
name=f"Primary ({year})",
|
60 |
+
control=False
|
61 |
+
).add_to(m)
|
62 |
+
|
63 |
+
# Add fallback layer
|
64 |
+
if tile_config["fallback"]:
|
65 |
+
folium.TileLayer(
|
66 |
+
tiles=tile_config["fallback"],
|
67 |
+
attr=f"{tile_config['attr']} (Fallback)",
|
68 |
+
name="Fallback Tiles",
|
69 |
+
control=False
|
70 |
+
).add_to(m)
|
71 |
+
|
72 |
+
# Add OpenStreetMap as backup
|
73 |
+
osm_layer = folium.TileLayer(
|
74 |
+
tiles=HISTORICAL_TILES["OpenStreetMap"]["url"],
|
75 |
+
attr=HISTORICAL_TILES["OpenStreetMap"]["attr"],
|
76 |
+
name="OpenStreetMap",
|
77 |
+
control=True
|
78 |
+
).add_to(m)
|
79 |
|
80 |
# Add markers
|
81 |
+
geocoder = SafeGeocoder()
|
82 |
coords = []
|
83 |
for loc in df[location_col].dropna().unique():
|
84 |
+
point = geocoder.get_coords(str(loc))
|
85 |
if point:
|
86 |
folium.Marker(
|
87 |
location=point,
|
88 |
+
popup=f"<b>{loc}</b><br><i>Historical View ({year})</i>",
|
89 |
+
icon=folium.Icon(color="blue")
|
|
|
|
|
|
|
|
|
90 |
).add_to(m)
|
91 |
coords.append(point)
|
92 |
|
93 |
+
# Layer control and bounds
|
94 |
folium.LayerControl().add_to(m)
|
95 |
if coords:
|
96 |
m.fit_bounds(coords)
|
97 |
|
98 |
+
# Force tile reload as workaround for gray tiles
|
99 |
+
m.get_root().html.add_child(folium.Element("""
|
100 |
+
<script>
|
101 |
+
setTimeout(function() {
|
102 |
+
const tiles = document.querySelectorAll('.leaflet-tile-loaded');
|
103 |
+
tiles.forEach(tile => {
|
104 |
+
const src = tile.src;
|
105 |
+
tile.src = '';
|
106 |
+
tile.src = src;
|
107 |
+
});
|
108 |
+
}, 1000);
|
109 |
+
</script>
|
110 |
+
"""))
|
111 |
+
|
112 |
return m._repr_html_()
|
113 |
|
114 |
+
def process_data(file_obj, location_col, year):
|
115 |
try:
|
|
|
116 |
df = pd.read_excel(file_obj.name)
|
117 |
|
|
|
118 |
if location_col not in df.columns:
|
119 |
+
return None, f"Column '{location_col}' not found", None
|
120 |
|
121 |
+
map_html = create_reliable_map(df, location_col, year)
|
|
|
122 |
|
|
|
123 |
with tempfile.NamedTemporaryFile(suffix=".xlsx", delete=False) as tmp:
|
124 |
df.to_excel(tmp.name, index=False)
|
125 |
processed_path = tmp.name
|
126 |
|
127 |
+
stats = f"Displaying {len(df)} locations from {year}"
|
128 |
return (
|
129 |
+
f"<div style='width:100%; height:70vh; border:1px solid #ddd'>{map_html}</div>",
|
130 |
+
stats,
|
131 |
processed_path
|
132 |
)
|
133 |
|
|
|
135 |
return None, f"Error: {str(e)}", None
|
136 |
|
137 |
# Gradio Interface
|
138 |
+
with gr.Blocks(title="Reliable Historical Maps", theme=gr.themes.Soft()) as app:
|
139 |
+
gr.Markdown("# Historical Map Viewer")
|
140 |
|
141 |
with gr.Row():
|
142 |
with gr.Column():
|
143 |
file_input = gr.File(
|
144 |
label="1. Upload Excel File",
|
145 |
+
file_types=[".xlsx"],
|
146 |
type="filepath"
|
147 |
)
|
148 |
location_col = gr.Textbox(
|
149 |
label="2. Location Column Name",
|
150 |
value="location",
|
151 |
+
placeholder="e.g., 'city' or 'address'"
|
152 |
)
|
153 |
year = gr.Slider(
|
154 |
+
label="3. Map Year",
|
155 |
minimum=1700,
|
156 |
maximum=1920,
|
157 |
+
value=1865,
|
158 |
step=1
|
159 |
)
|
160 |
btn = gr.Button("Generate Map", variant="primary")
|
|
|
162 |
with gr.Column():
|
163 |
map_display = gr.HTML(
|
164 |
label="Historical Map",
|
165 |
+
value="<div style='text-align:center;padding:2em;color:gray;border:1px solid #ddd'>"
|
166 |
+
"Map will appear here after generation</div>"
|
167 |
)
|
168 |
+
stats = gr.Textbox(label="Map Information")
|
169 |
+
download = gr.File(label="Download Processed Data")
|
170 |
|
171 |
btn.click(
|
172 |
+
process_data,
|
173 |
inputs=[file_input, location_col, year],
|
174 |
outputs=[map_display, stats, download]
|
175 |
)
|