import streamlit as st import pandas as pd import numpy as np import h3 import pydeck as pdk import os # --- CONFIG GÉNÉRALE --- st.set_page_config( page_title="Visualisation H3 3D optimisée", layout="wide", initial_sidebar_state="expanded" ) # --- CSS GLOBAL BEAUTÉ --- style = """ """ st.markdown(style, unsafe_allow_html=True) # TITRE titre_centre = """

Analyse territoriale avancée

""" st.html(titre_centre) # --- SIDEBAR --- with st.sidebar: st.header("⚙️ Paramètres") COLONNE_VALEUR = st.selectbox( 'Caractéristique', ['population', 'altitude_moyenne', 'altitude_minimale', 'altitude_maximale', 'superficie_km2', 'densite'], index=0 ) RESOLUTION_H3 = st.slider('Résolution H3', 6, 9, 7) default_seuil = { 'population': 2000, 'altitude_moyenne': 500, 'altitude_minimale': 500, 'altitude_maximale': 500, 'superficie_km2': 100, 'densite': 500 }.get(COLONNE_VALEUR, 100) SEUIL_MIN = st.number_input(f'Seuil minimum ({COLONNE_VALEUR})', value=default_seuil, step=100) style_name = st.selectbox( 'Style de carte', ['Dark Matter', 'Positron (Clair)', 'Voyager (Coloré)'], index=0 ) MAP_STYLES = { 'Dark Matter': 'https://basemaps.cartocdn.com/gl/dark-matter-gl-style/style.json', 'Positron (Clair)': 'https://basemaps.cartocdn.com/gl/positron-gl-style/style.json', 'Voyager (Coloré)': 'https://basemaps.cartocdn.com/gl/voyager-gl-style/style.json' } MAP_STYLE = MAP_STYLES[style_name] # --- CHARGEMENT --- @st.cache_data def load_data(): if not os.path.exists("Communes_all_fr.parquet"): st.error("Fichier manquant !") st.stop() df = pd.read_parquet("Communes_all_fr.parquet") df = df[df['superficie_km2'] < 800].copy() df = df.dropna(subset=['latitude', 'longitude', 'population', 'nom_standard']) df = df[df['population'] > 0] if 'reg_code' in df.columns: df['reg_code'] = df['reg_code'].astype('int8', errors='ignore') if 'dep_code' in df.columns: df['dep_code'] = df['dep_code'].astype('float16', errors='ignore') if 'canton_code' in df.columns: df['canton_code'] = df['canton_code'].astype('float16', errors='ignore') df['population'] = df['population'].astype('int32', errors='ignore') for col in ['superficie_hectare','superficie_km2','densite','altitude_moyenne','altitude_minimale','altitude_maximale']: if col in df.columns: df[col] = df[col].astype('float32', errors='ignore') df['latitude'] = df['latitude'].astype(np.float32) df['longitude'] = df['longitude'].astype(np.float32) return df df = load_data() # --- TRAITEMENT H3 --- @st.cache_data def prepare_h3(_df, resolution, colonne, seuil): df = _df.copy() df['h3'] = df.apply(lambda r: h3.latlng_to_cell(r.latitude, r.longitude, resolution), axis=1) agg = 'sum' if colonne == 'population' else 'mean' grouped = df.groupby('h3').agg( valeur=(colonne, agg), population=('population', 'sum') ).reset_index() grouped = grouped[grouped.valeur >= seuil] if grouped.empty: return [] top_city = df.loc[df.groupby('h3')['population'].idxmax(), ['h3', 'nom_standard']] grouped = grouped.merge(top_city, on='h3', how='left') grouped['ville'] = grouped['nom_standard'].fillna("Inconnue") vmax, vmin = grouped.valeur.max(), grouped.valeur.min() grouped['norm'] = 1.0 if vmax == vmin else (grouped.valeur - vmin) / (vmax - vmin) data = [] for _, row in grouped.iterrows(): boundary = h3.cell_to_boundary(row.h3) polygon = [[lon, lat] for lat, lon in boundary] ratio = row['norm'] if ratio < 0.5: r = int(100 + 310 * ratio) g = int(150 + 210 * ratio) b = int(255 * (1 - ratio * 2)) else: r = 255 g = int(255 * (2 - ratio * 2)) b = 0 color = [r, g, b, 220] data.append({ "polygon": polygon, "ville": str(row.ville), "valeur": round(float(row.valeur), 2), "population": int(row.population), "color": color, "elevation": float(row.valeur) * (10 if colonne == "superficie_km2" else 1) }) return data data = prepare_h3(df, RESOLUTION_H3, COLONNE_VALEUR, SEUIL_MIN) if not data: st.warning("Aucun hexagone – baisser le seuil !") st.stop() LABEL_MAP = { 'population': 'Population', 'altitude_moyenne': 'Altitude moyenne (m)', 'altitude_minimale': 'Altitude min (m)', 'altitude_maximale': 'Altitude max (m)', 'superficie_km2': 'Superficie (km²)', 'densite': 'Densité (hab/km²)' } label = LABEL_MAP.get(COLONNE_VALEUR, COLONNE_VALEUR.replace('_', ' ').title()) tooltip_html = f"{{ville}}
{label} : {{valeur}}" layer = pdk.Layer( "PolygonLayer", data, get_polygon="polygon", get_fill_color="color", get_elevation="elevation", elevation_scale=0.12 if COLONNE_VALEUR == "population" else 1.2, extruded=True, wireframe=True, pickable=True, auto_highlight=True, line_width_min_pixels=1 ) deck = pdk.Deck( layers=[layer], initial_view_state=pdk.ViewState( latitude=46.5, longitude=2.5, zoom=5.5, pitch=50 ), map_style=MAP_STYLE, tooltip={ "html": tooltip_html, "style": { "backgroundColor": "rgba(25,40,70,0.85)", "color": "white", "fontSize": "14px", "padding": "12px", "borderRadius": "10px", "boxShadow": "0 4px 20px rgba(0,0,0,0.5)", } } ) # --- CARTE DANS UN BEL ENCART --- st.markdown("
", unsafe_allow_html=True) st.pydeck_chart(deck, width='stretch', on_select="ignore") st.markdown("
", unsafe_allow_html=True)