GEODE/encyclopedia-coordinates-viewer
0
1import gradio as gr2from datasets import load_dataset3import pandas as pd4import re5import folium6import numpy as np7 8# --- 1. Initial Data Loading ---9print("Loading datasets...")10dfs = {}11 12try:13 print("- Loading EDDA...")14 dfs["Encyclopédie de Diderot et d'Alembert"] = load_dataset("GEODE/edda-coordinata", split="train").to_pandas()15 16 print("- Loading EB7...")17 dfs["Encyclopædia Britannica 7th edition"] = load_dataset("pnugues/EB7", split="train").to_pandas()18 19 print("- Loading EB9...")20 dfs["Encyclopædia Britannica 9th edition"] = load_dataset("pnugues/EB9", split="train").to_pandas()21 22 print("Loading complete!")23except Exception as e:24 print(f"Error loading datasets: {e}")25 26# --- 2. Utility Functions ---27def parse_coordinate(coord_str, meridian_name=None):28 if not isinstance(coord_str, str): return None, None29 30 pattern = r"(\d+)\s*(?:(\d+)')?\s*(?:(\d+)[\"']{1,2})?\s*([NSEW])"31 matches = re.findall(pattern, coord_str)32 33 lat_val, lon_val = None, None34 is_west, is_east = False, False35 36 for m in matches:37 deg = float(m[0]) if m[0] else 038 minute = float(m[1]) if m[1] else 039 sec = float(m[2]) if m[2] else 040 val = deg + (minute / 60) + (sec / 3600)41 42 direction = m[3]43 if direction in ['N', 'S']:44 lat_val = val if direction == 'N' else -val45 elif direction in ['E', 'W']:46 lon_val = val47 is_west = (direction == 'W')48 is_east = (direction == 'E')49 50 if lat_val is not None and lon_val is not None:51 m_name = meridian_name.strip() if isinstance(meridian_name, str) and meridian_name.strip() else "île de Fer"52 53 if m_name == "Pékin":54 lon_val = 116.39 + lon_val if is_west else 116.39 - lon_val55 else:56 final_lon = lon_val if is_east else -lon_val57 58 if m_name == "Paris":59 lon_val = final_lon + 2.3560 elif m_name == "Lunden": 61 lon_val = final_lon + 13.1962 elif m_name in ["Londres", "London"]:63 lon_val = final_lon + 0.064 elif m_name == "Sydon":65 lon_val = final_lon + 35.3766 else: 67 lon_val = final_lon - 17.6668 69 return lat_val, lon_val70 return None, None71 72def classify_geometry(x):73 if not isinstance(x, (list, np.ndarray)) or len(x) == 0: return "none"74 if len(x) == 1 and isinstance(x[0], (list, np.ndarray)):75 return "point" if len(x[0]) == 1 else "surface"76 if len(x) > 1 and isinstance(x[0], (list, np.ndarray)) and len(x[0]) == 1:77 if x[0][0] in ['subart', 'multsrc', 'pchain', 'misc']: return x[0][0]78 return "unknown"79 80def get_meridian_safely(meridian_list, index):81 if not isinstance(meridian_list, (list, np.ndarray)):82 return "île de Fer"83 if index < len(meridian_list):84 val = meridian_list[index]85 if isinstance(val, str) and val.strip() != "":86 return val.strip()87 return "île de Fer"88 89# --- 3. Search and Mapping Engine ---90def search_and_map(query, search_mode, dataset_choice):91 df = dfs.get(dataset_choice)92 if not query or df is None:93 return pd.DataFrame(), folium.Map(location=[46.2, 2.2], zoom_start=4)._repr_html_()94 95 # Group EB7 and EB9 under the same logic96 is_eb = dataset_choice in ["Encyclopædia Britannica 7th edition", "Encyclopædia Britannica 9th edition"]97 98 # 1. Column management for search99 if is_eb:100 search_col = "texte" if search_mode == "text" else "vedette"101 else:102 search_col = "text" if search_mode == "text" else "headword"103 104 # Filtering105 mask = df[search_col].str.contains(r'\b' + re.escape(query) + r'\b', case=False, na=False)106 results = df[mask].copy()107 108 m = folium.Map(location=[46.2, 2.2], zoom_start=4)109 bounds = []110 111 # 2. Map rendering loop112 for _, row in results.iterrows():113 114 # --- BRITANNICA BRANCH (EB7 & EB9) ---115 if is_eb:116 coords_str = row.get('coords', '')117 texte_val = str(row.get('texte', ''))118 119 headword = row.get('vedette', 'Unknown article')120 snippet = (texte_val[:150] + '...') if len(texte_val) > 150 else texte_val121 122 if isinstance(coords_str, str) and coords_str.strip():123 # Force London meridian for British editions124 lat, lon = parse_coordinate(coords_str, "Londres")125 if lat is not None:126 popup_html = f"<b>{headword}</b><br><i>Meridian: London (Greenwich)</i><br><br>{snippet}"127 folium.Marker([lat, lon], popup=popup_html, tooltip=headword).add_to(m)128 bounds.append([lat, lon])129 130 # --- ENCYCLOPÉDIE BRANCH (EDDA) ---131 else:132 meridian_list = row.get('meridian', [])133 if isinstance(meridian_list, np.ndarray):134 meridian_list = meridian_list.tolist()135 136 coords_raw = row.get('coordinates', [])137 if isinstance(coords_raw, (list, np.ndarray)):138 coords_list = [item.tolist() if isinstance(item, np.ndarray) else item for item in coords_raw]139 else:140 continue141 142 geom_type = classify_geometry(coords_list)143 headword = row.get('headword', 'Unknown')144 145 texte_val = str(row.get('text', ''))146 snippet = (texte_val[:150] + '...') if len(texte_val) > 150 else texte_val147 148 try:149 if geom_type == "point":150 current_meridian = get_meridian_safely(meridian_list, 0)151 lat, lon = parse_coordinate(coords_list[0][0], current_meridian)152 if lat is not None:153 popup_html = f"<b>{headword}</b><br><i>Meridian: {current_meridian}</i><br><br>{snippet}"154 folium.Marker([lat, lon], popup=popup_html, tooltip=headword).add_to(m)155 bounds.append([lat, lon])156 157 elif geom_type == "surface":158 current_meridian = get_meridian_safely(meridian_list, 0)159 p1 = parse_coordinate(coords_list[0][0], current_meridian)160 p2 = parse_coordinate(coords_list[0][1], current_meridian)161 if p1[0] is not None and p2[0] is not None:162 popup_html = f"<b>{headword}</b> (Area)<br><i>Meridian: {current_meridian}</i>"163 folium.Rectangle(bounds=[p1, p2], color="orange", fill=True, popup=popup_html).add_to(m)164 bounds.extend([p1, p2])165 166 elif geom_type in ["subart", "multsrc", "pchain"]:167 points = []168 for i, item in enumerate(coords_list[1:]):169 c_str = item[0] if isinstance(item, (list, np.ndarray)) else item170 current_meridian = get_meridian_safely(meridian_list, i)171 p = parse_coordinate(c_str, current_meridian)172 if p[0] is not None: 173 points.append((p[0], p[1], current_meridian))174 175 if points:176 if geom_type == "pchain":177 coords_only = [[pt[0], pt[1]] for pt in points]178 popup_html = f"<b>{headword}</b> (Path)<br><i>Meridian: {points[0][2]}</i>"179 folium.PolyLine(coords_only, color="blue", weight=3, popup=popup_html).add_to(m)180 bounds.extend(coords_only)181 else:182 for pt in points:183 popup_html = f"<b>{headword}</b><br><i>Meridian: {pt[2]}</i><br><br>{snippet}"184 folium.Marker([pt[0], pt[1]], icon=folium.Icon(color='green'), popup=popup_html, tooltip=headword).add_to(m)185 bounds.extend([[pt[0], pt[1]] for pt in points])186 except Exception as e:187 print(f"EDDA rendering error for {headword}: {e}")188 189 # --- 3. Zoom Logic ---190 if bounds:191 unique_pts = np.unique(bounds, axis=0)192 if len(unique_pts) <= 1:193 m.location = unique_pts[0].tolist()194 m.zoom_start = 5195 else:196 m.fit_bounds(bounds)197 198 # --- 4. Final Dataframe Formatting ---199 if is_eb:200 final_df = results[['vedette', 'coords', 'texte']].head(50).copy()201 final_df['texte'] = final_df['texte'].astype(str).str.slice(0, 280) + '...'202 else:203 final_df = results[['headword', 'coordinates', 'meridian', 'text']].head(50).copy()204 #final_df['coordinates'] = final_df['coordinates'].astype(str)205 final_df['text'] = final_df['text'].astype(str).str.slice(0, 280) + '...'206 207 return final_df, m._repr_html_()208 209# --- 4. Gradio Interface ---210description = """211# 🌍 Historical Encyclopedias Coordinates Explorer212---213 214**Disclaimer:** This application is a demonstration prototype currently under development.215 216This application allows you to explore and compare manually annotated geographical coordinates from several major 18th and 19th-century encyclopedias: 217* The **Encyclopédie** by Diderot and d'Alembert (FR, ~1751): https://huggingface.co/datasets/GEODE/edda-coordinata218* The **Encyclopædia Britannica** 7th edition (EN, ~1842): https://huggingface.co/datasets/pnugues/EB7219* The **Encyclopædia Britannica** 9th edition (EN, ~1875): https://huggingface.co/datasets/pnugues/EB9220 221Select the dataset, then search for any term within the article's text or its title (headword). The corresponding coordinates will be automatically projected onto the interactive map.222 223---224"""225 226with gr.Blocks(title="Historical Encyclopedias Coordinates Explorer") as demo:227 gr.Markdown(description)228 229 with gr.Row():230 with gr.Column(scale=1):231 dataset_dropdown = gr.Dropdown(232 choices=[233 "Encyclopédie de Diderot et d'Alembert", 234 "Encyclopædia Britannica 7th edition",235 "Encyclopædia Britannica 9th edition"236 ],237 value="Encyclopédie de Diderot et d'Alembert",238 label="Choose Dataset"239 )240 search_input = gr.Textbox(label="Search term", placeholder="E.g., Acapulco, Brest, Berlin...")241 search_mode = gr.Radio(choices=["headword", "text"], value="headword", label="Search in:")242 btn = gr.Button("Search on map", variant="primary")243 244 with gr.Column(scale=2):245 map_output = gr.HTML(label="Map Visualization")246 247 table_output = gr.Dataframe(label="Results (max 50)", interactive=False, wrap=True)248 249 inputs = [search_input, search_mode, dataset_dropdown]250 outputs = [table_output, map_output]251 252 btn.click(search_and_map, inputs, outputs)253 search_input.submit(search_and_map, inputs, outputs)254 255if __name__ == "__main__":256 demo.launch()