from pathlib import Path
import pydeck as pdk
import streamlit as st
import polars as pl
import geopandas as gpd
def prepare_layer(df: pl.DataFrame) -> pl.DataFrame:
cols = ["lat", "lon", "lat_fmt", "lon_fmt", "altitude", "val_fmt", "fill_color"]
if "is_significant" in df.columns:
cols.append("is_significant")
return df.select(cols)
def fast_to_dicts(df: pl.DataFrame) -> list[dict]:
cols = df.columns
result = []
# Conversion explicite des colonnes en listes Python natives
arrays = {
col: (
df[col].to_list() # pour List ou String ou autre
if df[col].dtype == pl.List
else df[col].to_numpy().tolist()
)
for col in cols
}
n = len(df)
for i in range(n):
row = {col: arrays[col][i] for col in cols}
result.append(row)
return result
def create_layer(df: pl.DataFrame) -> pdk.Layer:
layers = []
df = prepare_layer(df)
if "is_significant" in df.columns:
df_sig = df.filter(pl.col("is_significant"))
df_non_sig = df.filter(~pl.col("is_significant"))
else:
df_sig = pl.DataFrame()
df_non_sig = df
# Points significatifs
if len(df_sig) > 0:
df_sig = df_sig.with_columns(pl.lit("*").alias("star_text"))
layers.append(
pdk.Layer(
"TextLayer",
data=fast_to_dicts(df_sig),
get_position=["lon", "lat"],
get_text="star_text",
get_size=5,
get_color=[0, 0, 0, 255], # noir
get_angle=0,
get_text_anchor="center",
get_alignment_baseline="bottom",
pickable=False
)
)
layers.append(
pdk.Layer(
"GridCellLayer",
data=fast_to_dicts(df_sig),
get_position=["lon", "lat"],
get_fill_color="fill_color",
cell_size=2500,
elevation=0,
elevation_scale=0,
lighting=None,
pickable=True,
opacity=0.2,
extruded=False
)
)
# Points non significatifs
if len(df_non_sig) > 0:
layers.append(
pdk.Layer(
"GridCellLayer",
data=fast_to_dicts(df_non_sig),
get_position=["lon", "lat"],
get_fill_color="fill_color",
cell_size=2500,
elevation=0,
elevation_scale=0,
lighting=None,
pickable=True,
opacity=0.2,
extruded=False
)
)
return layers
def create_scatter_layer(df: pl.DataFrame, radius=1500) -> list[pdk.Layer]:
layers = []
df = prepare_layer(df)
if "is_significant" in df.columns:
df_sig = df.filter(pl.col("is_significant"))
df_non_sig = df.filter(~pl.col("is_significant"))
else:
df_sig = pl.DataFrame()
df_non_sig = df
# Points significatifs avec IconLayer (Triangle non rempli)
if len(df_sig) > 0:
layers.append(
pdk.Layer(
"ScatterplotLayer",
data=fast_to_dicts(df_sig),
get_position=["lon", "lat"],
get_fill_color="fill_color",
get_line_color=[0, 0, 0],
line_width_min_pixels=0.2,
get_radius=radius,
radius_scale=3,
radius_min_pixels=2,
pickable=True,
stroked=False
)
)
# Points non significatifs en ScatterplotLayer classique
if len(df_non_sig) > 0:
layers.append(
pdk.Layer(
"ScatterplotLayer",
data=fast_to_dicts(df_non_sig),
get_position=["lon", "lat"],
get_fill_color="fill_color",
get_line_color=[0, 0, 0],
line_width_min_pixels=0.2,
get_radius=radius,
radius_scale=1,
radius_min_pixels=2,
pickable=True,
stroked=False
)
)
return layers
def create_tooltip(label: str) -> dict:
return {
"html": f"""
({{lat_fmt}}, {{lon_fmt}})
{{altitude}} m
{{val_fmt}} {label}
""",
"style": {
"backgroundColor": "steelblue",
"color": "white"
},
"condition": "altitude !== 'undefined'"
}
def relief():
# Lire et reprojeter le shapefile
gdf = gpd.read_file(Path("data/external/niveaux/selection_courbes_niveau_france.shp").resolve()).to_crs(epsg=4326)
# Extraire les chemins
path_data = []
for _, row in gdf.iterrows():
geom = row.geometry
altitude = row["coordonnees"] # ou la colonne correcte (parfois 'ALTITUDE', à adapter)
if geom.geom_type == "LineString":
path_data.append({"path": list(geom.coords), "altitude": altitude})
elif geom.geom_type == "MultiLineString":
for line in geom.geoms:
path_data.append({"path": list(line.coords), "altitude": altitude})
# Couleur fixe blanc
return pdk.Layer(
"PathLayer",
data=path_data,
get_path="path",
get_color="[0, 0, 0, 100]",
width_scale=1,
width_min_pixels=0.5,
pickable=False
)
def plot_map(layers, view_state, tooltip, activate_relief: bool=False):
if not isinstance(layers, list):
layers = [layers]
# Supprime les couches nulles/indéfinies
layers = [layer for layer in layers if layer is not None]
if activate_relief:
relief_layer = relief()
if relief_layer is not None:
layers.append(relief_layer)
try:
return pdk.Deck(
layers=layers,
initial_view_state=view_state,
tooltip=tooltip,
map_style=None
)
except Exception as e:
st.error(f"Erreur lors de la création de la carte : {e}")
return None