#!/usr/bin/env python3 import pandas as pd import folium from folium import plugins from folium.plugins import MarkerCluster import os import gpxpy import re import sys import markdown2 import numpy as np # --- 1. CONFIGURATION --- base_dir = sys.argv[1] if len(sys.argv) > 1 else "./" base_dir = os.path.abspath(base_dir) routes_dir = os.path.join(base_dir, "routes") html_output_dir = os.path.join(base_dir, "html") csv_videos = os.path.join(base_dir, "export_videos.csv") csv_photos = os.path.join(base_dir, "export_photos.csv") file_notes = os.path.join(base_dir, "voyage.md") if not os.path.exists(html_output_dir): os.makedirs(html_output_dir) COULEURS_JOURS = ['#FF5733', '#2ECC71', '#3498DB', '#9B59B6', '#F1C40F', '#E67E22', '#1ABC9C', '#34495E'] noms_colonnes = ['Fichier', 'Date_Heure', 'Latitude', 'Longitude', 'Altitude', 'Vitesse'] # --- FONCTIONS UTILES --- def nettoyer_et_trier(df): if df is None or df.empty: return pd.DataFrame(columns=noms_colonnes + ['DT', 'Jour']) df = df[(df['Latitude'].notnull()) & (df['Longitude'].notnull())] df = df[(df['Latitude'] != 0) & (df['Longitude'] != 0)] df['DT'] = pd.to_datetime(df['Date_Heure'].str.split('.').str[0], format='%Y:%m:%d %H:%M:%S', errors='coerce') df = df.dropna(subset=['DT']) df['Jour'] = df['DT'].dt.date return df.sort_values(by='DT') def create_pin(color, icon_name): html_code = f"""
""" return folium.DivIcon(html=html_code, icon_anchor=(12, 40)) def inject_common_assets(m): custom_js = """ """ custom_css = """ """ m.get_root().header.add_child(folium.Element(custom_css + custom_js)) def get_journal_ui(): journal_html = "" if os.path.exists(file_notes): with open(file_notes, 'r', encoding='utf-8') as f: journal_html = markdown2.markdown(f.read()) return f"""
{journal_html}
""" # --- 2. CHARGEMENT --- try: df_v = pd.read_csv(csv_videos, names=noms_colonnes, header=None) if os.path.exists(csv_videos) else pd.DataFrame() df_p = pd.read_csv(csv_photos, names=noms_colonnes, header=None) if os.path.exists(csv_photos) else pd.DataFrame() df_v, df_p = nettoyer_et_trier(df_v), nettoyer_et_trier(df_p) jours_gpx = set() if os.path.exists(routes_dir): for f in os.listdir(routes_dir): match = re.search(r'(\d{4}-\d{2}-\d{2})', f) if match: jours_gpx.add(pd.to_datetime(match.group(1)).date()) tous_les_jours = sorted(list(set(df_v['Jour'].unique()) | set(df_p['Jour'].unique()) | jours_gpx)) except Exception as e: print(f"❌ Erreur : {e}"); sys.exit() # --- 3. GÉNÉRATION --- m_global = folium.Map(tiles=None) folium.TileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', attr='Esri', name='Satellite').add_to(m_global) folium.TileLayer('OpenStreetMap', name='Plan').add_to(m_global) inject_common_assets(m_global) m_mini = folium.Map(tiles='OpenStreetMap', zoom_control=False, control_scale=False, attribution_control=False) all_global_coords, sidebar_links_html = [], "" for i, jour in enumerate(tous_les_jours): day_str = jour.strftime('%Y-%m-%d') file_name = f'carte_{day_str}.html' color_day = COULEURS_JOURS[i % len(COULEURS_JOURS)] sidebar_links_html += f'{day_str}' m_day = folium.Map(tiles=None) folium.TileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', attr='Esri', name='Satellite').add_to(m_day) folium.TileLayer('OpenStreetMap', name='Plan').add_to(m_day) inject_common_assets(m_day) marker_cluster = MarkerCluster(name="Médias").add_to(m_day) day_coords = [] current_gpx_filename = None if os.path.exists(routes_dir): for gpx_file in os.listdir(routes_dir): if gpx_file.startswith(day_str) and gpx_file.endswith('.gpx'): current_gpx_filename = gpx_file try: with open(os.path.join(routes_dir, gpx_file), 'r') as f: gpx = gpxpy.parse(f) for track in gpx.tracks: pts = [] for segment in track.segments: # OPTIMISATION : On ne garde qu'un point sur 5 # pts.extend([[p.latitude, p.longitude] for i, p in enumerate(segment.points) if i % 5 == 0]) pts.extend([[p.latitude, p.longitude] for p in segment.points]) if pts: day_coords.extend(pts); all_global_coords.extend(pts) popup_html = f"
📅 {day_str}

Détails
" folium.PolyLine(pts, color=color_day, weight=7, opacity=0.8, popup=folium.Popup(popup_html, max_width=200)).add_to(m_global) folium.PolyLine(pts, color=color_day, weight=3, opacity=0.8).add_to(m_mini) folium.PolyLine(pts, color="#FF0000", weight=4, opacity=0.8).add_to(m_day) except: pass m_day.get_root().html.add_child(folium.Element('
🏠 Accueil
' + get_journal_ui())) if current_gpx_filename: m_day.get_root().html.add_child(folium.Element(f'
📥 GPX
')) # REGROUPEMENT PHOTOS day_p = df_p[df_p['Jour'] == jour].copy() if not day_p.empty: tol = 0.0002 while not day_p.empty: ref = day_p.iloc[0] masque = (np.abs(day_p['Latitude'] - ref['Latitude']) < tol) & (np.abs(day_p['Longitude'] - ref['Longitude']) < tol) groupe = day_p[masque] fichiers = groupe['Fichier'].tolist() nb = len(fichiers) # slides = "".join([f'
{p.strip()}
' for idx, p in enumerate(fichiers)]) # OPTIMISATION : Ajout de loading="lazy" dans la balise img slides = "".join([f'
{p.strip()}
' for idx, p in enumerate(fichiers)]) btns = f'
1/{nb}
' if nb > 1 else "" folium.Marker(location=[ref['Latitude'], ref['Longitude']], popup=folium.Popup(f'
{slides}
{btns}
', max_width="100%"), icon=create_pin("#FF3B30", "camera" if nb==1 else "images")).add_to(marker_cluster) day_p = day_p[~masque] # Vidéos day_v = df_v[df_v['Jour'] == jour] for v_name, group in day_v.groupby('Fichier'): pts_v = group[['Latitude', 'Longitude']].values.tolist() # v_pop = f'
{v_name.strip()}
' v_pop = f'
{v_name.strip()}
' folium.Marker(location=pts_v[0], popup=folium.Popup(v_pop, max_width="100%"), icon=create_pin("#007AFF", "play")).add_to(marker_cluster) if day_coords: m_day.fit_bounds(day_coords, padding=(50, 50)) folium.LayerControl(position='topright').add_to(m_day) plugins.Fullscreen(position='topright').add_to(m_day) m_day.save(os.path.join(html_output_dir, file_name)) # --- FINALISATION --- if all_global_coords: m_global.fit_bounds(all_global_coords, padding=(50, 50)) m_mini.fit_bounds(all_global_coords, padding=(20, 20)) sidebar_html = f'
⬅ Retour
PAR JOURS
{sidebar_links_html}
' m_global.get_root().html.add_child(folium.Element(sidebar_html + get_journal_ui())) folium.LayerControl().add_to(m_global) m_global.save(os.path.join(base_dir, "index.html")) m_mini.save(os.path.join(base_dir, "mini.html"))