146 lines
4.5 KiB
Python
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()
|