#!/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 import html # --- 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: 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)]) 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() base_name = os.path.splitext(v_name.strip())[0] clean_id = re.sub(r'[^a-zA-Z0-9]', '', base_name) video_id = f"vid_{clean_id}" desktop_file = f"../videos/{base_name}.mp4" mobile_file = f"../videos/{base_name}_mobile.mp4" # Ajout 'autoplay muted' pour le lancement au clic # Ajout 'preload=none' pour ne pas charger les autres v_pop = ( f'
' f'
' f'{v_name.strip()}' f'
' f'720p' f'1080p' f'
' f'
' f'
' ) 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"))