1
2025-11-18 09:01:34 +01:00

146 lines
4.5 KiB
Python

"""Tracés dédiés aux analyses du vent (roses et vecteurs agrégés)."""
from __future__ import annotations
from pathlib import Path
from typing import Sequence
import matplotlib.dates as mdates
import matplotlib.pyplot as plt
from matplotlib.ticker import FuncFormatter
import numpy as np
import pandas as pd
from .base import export_plot_dataset
__all__ = ['plot_wind_rose', 'plot_wind_vector_series']
def plot_wind_rose(
frequencies: pd.DataFrame,
speed_bin_labels: Sequence[str],
output_path: str | Path,
*,
sector_size_deg: float,
cmap: str = "viridis",
) -> Path:
"""
Trace une rose des vents empilée par classes de vitesses (en % du temps).
"""
output_path = Path(output_path)
output_path.parent.mkdir(parents=True, exist_ok=True)
if frequencies.empty:
fig, ax = plt.subplots(subplot_kw={"projection": "polar"})
ax.text(0.5, 0.5, "Données de vent insuffisantes.", ha="center", va="center")
ax.set_axis_off()
fig.savefig(output_path, dpi=150, bbox_inches="tight")
plt.close(fig)
return output_path.resolve()
dataset = frequencies.copy()
dataset.insert(0, "sector_start_deg", frequencies.index)
dataset.insert(1, "sector_center_deg", frequencies.index + sector_size_deg / 2.0)
export_plot_dataset(dataset, output_path)
fig, ax = plt.subplots(subplot_kw={"projection": "polar"}, figsize=(6, 6))
cmap_obj = plt.get_cmap(cmap, len(speed_bin_labels))
colors = cmap_obj(np.linspace(0.2, 0.95, len(speed_bin_labels)))
angles = np.deg2rad(frequencies.index.to_numpy(dtype=float) + sector_size_deg / 2.0)
width = np.deg2rad(sector_size_deg)
bottom = np.zeros_like(angles, dtype=float)
for label, color in zip(speed_bin_labels, colors):
values = frequencies[label].to_numpy(dtype=float)
bars = ax.bar(
angles,
values,
width=width,
bottom=bottom,
color=color,
edgecolor="white",
linewidth=0.5,
align="center",
)
bottom += values
ax.set_theta_zero_location("N")
ax.set_theta_direction(-1)
ax.set_xticks(np.deg2rad(np.arange(0, 360, 45)))
ax.set_xticklabels(["N", "NE", "E", "SE", "S", "SO", "O", "NO"])
max_radius = np.max(bottom)
ax.set_ylim(0, max(max_radius * 1.1, 1))
ax.yaxis.set_major_formatter(FuncFormatter(lambda val, _pos: f"{val:.0f}%"))
ax.set_title("Rose des vents (fréquence en %)")
legend_handles = [
plt.Line2D([0], [0], color=color, linewidth=6, label=label) for label, color in zip(speed_bin_labels, colors)
]
ax.legend(
handles=legend_handles,
loc="lower center",
bbox_to_anchor=(0.5, -0.15),
ncol=2,
title="Vitesses (km/h)",
)
fig.tight_layout()
fig.savefig(output_path, dpi=150, bbox_inches="tight")
plt.close(fig)
return output_path.resolve()
def plot_wind_vector_series(
vector_df: pd.DataFrame,
output_path: str | Path,
*,
title: str = "Vecteurs moyens du vent",
) -> Path:
"""
Représente les composantes moyennes du vent sous forme de flèches (u/v).
"""
output_path = Path(output_path)
output_path.parent.mkdir(parents=True, exist_ok=True)
if vector_df.empty:
fig, ax = plt.subplots()
ax.text(0.5, 0.5, "Pas de données de vent.", ha="center", va="center")
ax.set_axis_off()
fig.savefig(output_path, dpi=150, bbox_inches="tight")
plt.close(fig)
return output_path.resolve()
export_plot_dataset(vector_df, output_path)
times = vector_df.index
x = mdates.date2num(times)
u = vector_df["u"].to_numpy(dtype=float)
v = vector_df["v"].to_numpy(dtype=float)
speed = vector_df["speed"].to_numpy(dtype=float)
fig, ax = plt.subplots(figsize=(12, 4))
q = ax.quiver(
x,
np.zeros_like(x),
u,
v,
speed,
angles="xy",
scale_units="xy",
scale=1,
cmap="viridis",
)
ax.axhline(0, color="black", linewidth=0.5)
ax.set_ylim(-max(abs(v)) * 1.2 if np.any(v) else -1, max(abs(v)) * 1.2 if np.any(v) else 1)
ax.xaxis.set_major_locator(mdates.AutoDateLocator())
ax.xaxis.set_major_formatter(mdates.ConciseDateFormatter(ax.xaxis.get_major_locator()))
ax.set_ylabel("Composante nord (v)")
ax.set_xlabel("Date")
ax.set_title(title)
cbar = fig.colorbar(q, ax=ax)
cbar.set_label("Vitesse moyenne (km/h)")
fig.tight_layout()
fig.savefig(output_path, dpi=150)
plt.close(fig)
return output_path.resolve()