Enhance report generation by adding turbine reference map functionality. Introduce new functions for calculating content dimensions and rendering turbine reference images. Update report layout to include turbine reference maps when applicable, improving visualization of turbine data. Refactor related string resources for better localization and clarity.
This commit is contained in:
parent
8152e76d05
commit
3dbd94a044
@ -38,7 +38,15 @@ from src.utils import (
|
|||||||
get_analysis_radius_m,
|
get_analysis_radius_m,
|
||||||
)
|
)
|
||||||
from src.analysis.geospatial import haversine_distance, haversine_distance_vectorized
|
from src.analysis.geospatial import haversine_distance, haversine_distance_vectorized
|
||||||
from src.visualization.maps import plot_cloud_to_ground_coordinate_plane, plot_intercloud_coordinate_plane
|
from src.visualization.maps import (
|
||||||
|
plot_cloud_to_ground_coordinate_plane,
|
||||||
|
plot_intercloud_coordinate_plane,
|
||||||
|
plot_turbine_reference_map,
|
||||||
|
figure_layout_aspect,
|
||||||
|
render_coordinate_plane_png,
|
||||||
|
render_turbine_reference_png,
|
||||||
|
turbines_need_reference_map,
|
||||||
|
)
|
||||||
from src.visualization.storm_cells import (
|
from src.visualization.storm_cells import (
|
||||||
create_storm_cells_map,
|
create_storm_cells_map,
|
||||||
create_storm_cells_summary,
|
create_storm_cells_summary,
|
||||||
@ -83,6 +91,20 @@ def _content_width_inches(doc: Document) -> float:
|
|||||||
return section.page_width.inches - section.left_margin.inches - section.right_margin.inches
|
return section.page_width.inches - section.left_margin.inches - section.right_margin.inches
|
||||||
|
|
||||||
|
|
||||||
|
def _content_height_inches(doc: Document) -> float:
|
||||||
|
section = doc.sections[0]
|
||||||
|
return section.page_height.inches - section.top_margin.inches - section.bottom_margin.inches
|
||||||
|
|
||||||
|
|
||||||
|
def _main_map_height_inches(
|
||||||
|
content_width: float,
|
||||||
|
content_height: float,
|
||||||
|
fig,
|
||||||
|
) -> float:
|
||||||
|
aspect = figure_layout_aspect(fig)
|
||||||
|
return min(content_width * aspect, max(content_height - 0.75, content_width * 0.5))
|
||||||
|
|
||||||
|
|
||||||
def _add_header_logo(doc: Document) -> None:
|
def _add_header_logo(doc: Document) -> None:
|
||||||
logo_path = _resolve_logo_path("iklim.png")
|
logo_path = _resolve_logo_path("iklim.png")
|
||||||
if not os.path.isfile(logo_path):
|
if not os.path.isfile(logo_path):
|
||||||
@ -379,8 +401,42 @@ def _add_risk_color_legend(doc: Document, size_pt: int = 10, lang: str | None =
|
|||||||
r.font.size = Pt(size_pt)
|
r.font.size = Pt(size_pt)
|
||||||
|
|
||||||
|
|
||||||
def _add_image_from_bytes(doc: Document, png_bytes: bytes, width_inches: float) -> None:
|
def _add_turbine_reference_map_if_needed(
|
||||||
|
doc: Document,
|
||||||
|
turbine_df: pd.DataFrame,
|
||||||
|
content_width: float,
|
||||||
|
s,
|
||||||
|
lang,
|
||||||
|
height_inches: float | None = None,
|
||||||
|
) -> None:
|
||||||
|
if not turbines_need_reference_map(turbine_df):
|
||||||
|
return
|
||||||
|
ref_fig = plot_turbine_reference_map(turbine_df, lang=lang)
|
||||||
|
if ref_fig is None:
|
||||||
|
return
|
||||||
|
doc.add_paragraph("")
|
||||||
|
_add_paragraph(doc, s.map_turbine_reference_title, size_pt=11, bold=True)
|
||||||
|
_add_paragraph(doc, s.map_turbine_reference_desc, size_pt=10)
|
||||||
|
if height_inches is None:
|
||||||
|
height_inches = content_width * figure_layout_aspect(ref_fig)
|
||||||
|
_add_image_from_bytes(
|
||||||
|
doc,
|
||||||
|
render_turbine_reference_png(ref_fig),
|
||||||
|
content_width,
|
||||||
|
height_inches,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _add_image_from_bytes(
|
||||||
|
doc: Document,
|
||||||
|
png_bytes: bytes,
|
||||||
|
width_inches: float,
|
||||||
|
height_inches: float | None = None,
|
||||||
|
) -> None:
|
||||||
stream = io.BytesIO(png_bytes)
|
stream = io.BytesIO(png_bytes)
|
||||||
|
if height_inches is not None:
|
||||||
|
doc.add_picture(stream, width=Inches(width_inches), height=Inches(height_inches))
|
||||||
|
return
|
||||||
doc.add_picture(stream, width=Inches(width_inches))
|
doc.add_picture(stream, width=Inches(width_inches))
|
||||||
|
|
||||||
|
|
||||||
@ -695,6 +751,7 @@ def create_docx_report(
|
|||||||
|
|
||||||
# Turbine information
|
# Turbine information
|
||||||
_add_title(doc, s.turbine_information, size_pt=16, align=WD_ALIGN_PARAGRAPH.LEFT)
|
_add_title(doc, s.turbine_information, size_pt=16, align=WD_ALIGN_PARAGRAPH.LEFT)
|
||||||
|
_add_turbine_reference_map_if_needed(doc, turbine_df, content_width, s, lang)
|
||||||
_add_paragraph(doc, s.turbine_information_desc, size_pt=10)
|
_add_paragraph(doc, s.turbine_information_desc, size_pt=10)
|
||||||
_add_table(doc, build_turbine_information_table_data(turbine_df, lang))
|
_add_table(doc, build_turbine_information_table_data(turbine_df, lang))
|
||||||
|
|
||||||
@ -905,7 +962,8 @@ def create_docx_report(
|
|||||||
if start_date and end_date:
|
if start_date and end_date:
|
||||||
_add_paragraph(doc, f"{start_date} - {end_date}", size_pt=10)
|
_add_paragraph(doc, f"{start_date} - {end_date}", size_pt=10)
|
||||||
cg_fig = plot_cloud_to_ground_coordinate_plane(centroid_row, lightning_df, turbine_df, storm_data, precomputed=pre, lang=lang)
|
cg_fig = plot_cloud_to_ground_coordinate_plane(centroid_row, lightning_df, turbine_df, storm_data, precomputed=pre, lang=lang)
|
||||||
_add_image_from_bytes(doc, cg_fig.to_image(format="png", width=1200, height=900, scale=2, engine="kaleido"), content_width)
|
map_height = _main_map_height_inches(content_width, _content_height_inches(doc), cg_fig)
|
||||||
|
_add_image_from_bytes(doc, render_coordinate_plane_png(cg_fig), content_width, map_height)
|
||||||
doc.add_page_break()
|
doc.add_page_break()
|
||||||
|
|
||||||
_add_title(doc, s.cloud_to_ground_current_vs_distance, size_pt=14)
|
_add_title(doc, s.cloud_to_ground_current_vs_distance, size_pt=14)
|
||||||
@ -943,7 +1001,8 @@ def create_docx_report(
|
|||||||
if start_date and end_date:
|
if start_date and end_date:
|
||||||
_add_paragraph(doc, f"{start_date} - {end_date}", size_pt=10)
|
_add_paragraph(doc, f"{start_date} - {end_date}", size_pt=10)
|
||||||
ic_fig = plot_intercloud_coordinate_plane(centroid_row, lightning_df, turbine_df, storm_data, precomputed=pre, lang=lang)
|
ic_fig = plot_intercloud_coordinate_plane(centroid_row, lightning_df, turbine_df, storm_data, precomputed=pre, lang=lang)
|
||||||
_add_image_from_bytes(doc, ic_fig.to_image(format="png", width=1200, height=900, scale=2, engine="kaleido"), content_width)
|
map_height = _main_map_height_inches(content_width, _content_height_inches(doc), ic_fig)
|
||||||
|
_add_image_from_bytes(doc, render_coordinate_plane_png(ic_fig), content_width, map_height)
|
||||||
doc.add_page_break()
|
doc.add_page_break()
|
||||||
|
|
||||||
_add_title(doc, s.intercloud_current_vs_distance, size_pt=14)
|
_add_title(doc, s.intercloud_current_vs_distance, size_pt=14)
|
||||||
@ -1166,10 +1225,12 @@ def create_docx_report(
|
|||||||
_add_title(doc, s.storm_cells, size_pt=14)
|
_add_title(doc, s.storm_cells, size_pt=14)
|
||||||
_add_paragraph(doc, s.storm_cells_daily_viz, size_pt=10)
|
_add_paragraph(doc, s.storm_cells_daily_viz, size_pt=10)
|
||||||
storm_fig = create_storm_cells_map(storm_data, turbine_df, lang=lang)
|
storm_fig = create_storm_cells_map(storm_data, turbine_df, lang=lang)
|
||||||
|
map_height = _main_map_height_inches(content_width, _content_height_inches(doc), storm_fig)
|
||||||
_add_image_from_bytes(
|
_add_image_from_bytes(
|
||||||
doc,
|
doc,
|
||||||
storm_fig.to_image(format="png", width=1200, height=900, scale=2, engine="kaleido"),
|
render_coordinate_plane_png(storm_fig),
|
||||||
content_width,
|
content_width,
|
||||||
|
map_height,
|
||||||
)
|
)
|
||||||
doc.add_page_break()
|
doc.add_page_break()
|
||||||
|
|
||||||
|
|||||||
@ -216,8 +216,13 @@ class ReportStrings:
|
|||||||
map_storm_cells: str
|
map_storm_cells: str
|
||||||
map_cg_lightning: str
|
map_cg_lightning: str
|
||||||
map_ic_lightning: str
|
map_ic_lightning: str
|
||||||
|
map_lightning_ring_range: str
|
||||||
|
map_lightning_ring_beyond: str
|
||||||
map_distance_ring: str
|
map_distance_ring: str
|
||||||
map_severity_legend: str
|
map_severity_legend: str
|
||||||
|
map_turbine_reference_title: str
|
||||||
|
map_turbine_reference_desc: str
|
||||||
|
map_turbine_reference_plane_title: str
|
||||||
storm_severity_names: dict[str, str]
|
storm_severity_names: dict[str, str]
|
||||||
hist_minutes_from_start: str
|
hist_minutes_from_start: str
|
||||||
hist_lightning_count: str
|
hist_lightning_count: str
|
||||||
@ -439,8 +444,13 @@ _STRINGS_EN = ReportStrings(
|
|||||||
map_storm_cells="Storm Cells",
|
map_storm_cells="Storm Cells",
|
||||||
map_cg_lightning="Cloud-to-Ground Lightning",
|
map_cg_lightning="Cloud-to-Ground Lightning",
|
||||||
map_ic_lightning="Intercloud Lightning",
|
map_ic_lightning="Intercloud Lightning",
|
||||||
|
map_lightning_ring_range="{name} ({lower:.0f}-{upper:.0f} km)",
|
||||||
|
map_lightning_ring_beyond="{name} (>{outer:.0f} km)",
|
||||||
map_distance_ring="{radius:.1f}km Distance Ring",
|
map_distance_ring="{radius:.1f}km Distance Ring",
|
||||||
map_severity_legend="{severity} Severity",
|
map_severity_legend="{severity} Severity",
|
||||||
|
map_turbine_reference_title="Turbine Reference Map",
|
||||||
|
map_turbine_reference_desc="Zoomed view of turbine names and locations.",
|
||||||
|
map_turbine_reference_plane_title="Turbine Layout",
|
||||||
storm_severity_names={"high": "High", "medium": "Medium", "low": "Low", "unknown": "Unknown"},
|
storm_severity_names={"high": "High", "medium": "Medium", "low": "Low", "unknown": "Unknown"},
|
||||||
hist_minutes_from_start="Minutes from start",
|
hist_minutes_from_start="Minutes from start",
|
||||||
hist_lightning_count="Lightning Count",
|
hist_lightning_count="Lightning Count",
|
||||||
@ -671,8 +681,13 @@ _STRINGS_TR = ReportStrings(
|
|||||||
map_storm_cells="Fırtına Hücreleri",
|
map_storm_cells="Fırtına Hücreleri",
|
||||||
map_cg_lightning="Yıldırım",
|
map_cg_lightning="Yıldırım",
|
||||||
map_ic_lightning="Şimşek",
|
map_ic_lightning="Şimşek",
|
||||||
|
map_lightning_ring_range="{name} ({lower:.0f}-{upper:.0f} km)",
|
||||||
|
map_lightning_ring_beyond="{name} (>{outer:.0f} km)",
|
||||||
map_distance_ring="{radius:.1f} km Mesafe Halkası",
|
map_distance_ring="{radius:.1f} km Mesafe Halkası",
|
||||||
map_severity_legend="{severity} Şiddet",
|
map_severity_legend="{severity} Şiddet",
|
||||||
|
map_turbine_reference_title="Türbin Referans Haritası",
|
||||||
|
map_turbine_reference_desc="Türbin adları ve konumlarının yakınlaştırılmış görünümü.",
|
||||||
|
map_turbine_reference_plane_title="Türbin Yerleşimi",
|
||||||
storm_severity_names={"high": "Yüksek", "medium": "Orta", "low": "Düşük", "unknown": "Bilinmeyen"},
|
storm_severity_names={"high": "Yüksek", "medium": "Orta", "low": "Düşük", "unknown": "Bilinmeyen"},
|
||||||
hist_minutes_from_start="Başlangıçtan itibaren geçen dakika",
|
hist_minutes_from_start="Başlangıçtan itibaren geçen dakika",
|
||||||
hist_lightning_count="Yıldırım Sayısı",
|
hist_lightning_count="Yıldırım Sayısı",
|
||||||
|
|||||||
@ -13,6 +13,387 @@ COORDINATE_PLANE_LIGHTNING_SIZE_MAX = 24
|
|||||||
COORDINATE_PLANE_LIGHTNING_CURRENT_SCALE = 800
|
COORDINATE_PLANE_LIGHTNING_CURRENT_SCALE = 800
|
||||||
COORDINATE_PLANE_TURBINE_NAME_FONT_SIZE = 9
|
COORDINATE_PLANE_TURBINE_NAME_FONT_SIZE = 9
|
||||||
COORDINATE_PLANE_TURBINE_TEXT_POSITION = 'middle center'
|
COORDINATE_PLANE_TURBINE_TEXT_POSITION = 'middle center'
|
||||||
|
TURBINE_REFERENCE_NAME_FONT_SIZE = 11
|
||||||
|
TURBINE_REFERENCE_MARKER_SIZE = 22
|
||||||
|
TURBINE_REFERENCE_MIN_TURBINES = 4
|
||||||
|
TURBINE_REFERENCE_MIN_SEPARATION_M = 800.0
|
||||||
|
COORDINATE_PLANE_FIGURE_WIDTH = 1400
|
||||||
|
COORDINATE_PLANE_EXPORT_SCALE = 2
|
||||||
|
COORDINATE_PLANE_BOUNDS_PADDING = 1.06
|
||||||
|
COORDINATE_PLANE_MARGIN_L = 20
|
||||||
|
COORDINATE_PLANE_MARGIN_R = 20
|
||||||
|
COORDINATE_PLANE_MARGIN_T = 50
|
||||||
|
COORDINATE_PLANE_MARGIN_B = 88
|
||||||
|
|
||||||
|
|
||||||
|
def figure_layout_aspect(fig: go.Figure) -> float:
|
||||||
|
width = float(fig.layout.width or COORDINATE_PLANE_FIGURE_WIDTH)
|
||||||
|
height = float(fig.layout.height or width)
|
||||||
|
return height / width
|
||||||
|
|
||||||
|
|
||||||
|
def coordinate_plane_figure_aspect() -> float:
|
||||||
|
return 1.05
|
||||||
|
|
||||||
|
|
||||||
|
def turbine_reference_figure_aspect() -> float:
|
||||||
|
return 0.78
|
||||||
|
|
||||||
|
|
||||||
|
def render_coordinate_plane_png(fig: go.Figure) -> bytes:
|
||||||
|
width = int(fig.layout.width or COORDINATE_PLANE_FIGURE_WIDTH)
|
||||||
|
height = int(fig.layout.height or width)
|
||||||
|
return fig.to_image(
|
||||||
|
format='png',
|
||||||
|
width=width,
|
||||||
|
height=height,
|
||||||
|
scale=COORDINATE_PLANE_EXPORT_SCALE,
|
||||||
|
engine='kaleido',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def render_turbine_reference_png(fig: go.Figure) -> bytes:
|
||||||
|
width = int(fig.layout.width or COORDINATE_PLANE_FIGURE_WIDTH)
|
||||||
|
height = int(fig.layout.height or width)
|
||||||
|
return fig.to_image(
|
||||||
|
format='png',
|
||||||
|
width=width,
|
||||||
|
height=height,
|
||||||
|
scale=COORDINATE_PLANE_EXPORT_SCALE,
|
||||||
|
engine='kaleido',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _geographic_yaxis_scaleratio(center_lat: float) -> float:
|
||||||
|
cos_lat = float(np.cos(np.radians(center_lat)))
|
||||||
|
if cos_lat < 1e-6:
|
||||||
|
cos_lat = 1e-6
|
||||||
|
return 1.0 / cos_lat
|
||||||
|
|
||||||
|
|
||||||
|
def _coordinate_plane_bounds(
|
||||||
|
center_lat: float,
|
||||||
|
center_lon: float,
|
||||||
|
max_radius_m: float,
|
||||||
|
padding_factor: float = COORDINATE_PLANE_BOUNDS_PADDING,
|
||||||
|
) -> tuple[float, float, float, float]:
|
||||||
|
cos_lat = max(float(np.cos(np.radians(center_lat))), 1e-6)
|
||||||
|
lat_pad = (max_radius_m / 111_000.0) * padding_factor
|
||||||
|
lon_pad = (max_radius_m / (111_000.0 * cos_lat)) * padding_factor
|
||||||
|
return (
|
||||||
|
center_lat - lat_pad,
|
||||||
|
center_lat + lat_pad,
|
||||||
|
center_lon - lon_pad,
|
||||||
|
center_lon + lon_pad,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _compute_figure_dimensions(
|
||||||
|
center_lat: float,
|
||||||
|
lon_min: float,
|
||||||
|
lon_max: float,
|
||||||
|
lat_min: float,
|
||||||
|
lat_max: float,
|
||||||
|
figure_width: int = COORDINATE_PLANE_FIGURE_WIDTH,
|
||||||
|
) -> tuple[int, int]:
|
||||||
|
lon_span = max(lon_max - lon_min, 1e-9)
|
||||||
|
lat_span = max(lat_max - lat_min, 1e-9)
|
||||||
|
scaleratio = _geographic_yaxis_scaleratio(center_lat)
|
||||||
|
plot_aspect = (lat_span * scaleratio) / lon_span
|
||||||
|
plot_width = figure_width - COORDINATE_PLANE_MARGIN_L - COORDINATE_PLANE_MARGIN_R
|
||||||
|
plot_height = max(int(round(plot_width * plot_aspect)), 1)
|
||||||
|
figure_height = plot_height + COORDINATE_PLANE_MARGIN_T + COORDINATE_PLANE_MARGIN_B
|
||||||
|
return figure_width, figure_height
|
||||||
|
|
||||||
|
|
||||||
|
def _apply_coordinate_plane_layout(
|
||||||
|
fig: go.Figure,
|
||||||
|
title: str,
|
||||||
|
s,
|
||||||
|
center_lat: float,
|
||||||
|
lon_min: float,
|
||||||
|
lon_max: float,
|
||||||
|
lat_min: float,
|
||||||
|
lat_max: float,
|
||||||
|
) -> None:
|
||||||
|
figure_width, figure_height = _compute_figure_dimensions(
|
||||||
|
center_lat,
|
||||||
|
lon_min,
|
||||||
|
lon_max,
|
||||||
|
lat_min,
|
||||||
|
lat_max,
|
||||||
|
)
|
||||||
|
fig.update_layout(
|
||||||
|
title=dict(text=title, font=dict(size=28)),
|
||||||
|
xaxis=dict(
|
||||||
|
title=dict(text=s.map_longitude, font=dict(size=28)),
|
||||||
|
tickfont=dict(size=22),
|
||||||
|
range=[lon_min, lon_max],
|
||||||
|
showgrid=False,
|
||||||
|
zeroline=False,
|
||||||
|
),
|
||||||
|
yaxis=dict(
|
||||||
|
title=dict(text=s.map_latitude, font=dict(size=28)),
|
||||||
|
tickfont=dict(size=22),
|
||||||
|
range=[lat_min, lat_max],
|
||||||
|
scaleanchor='x',
|
||||||
|
scaleratio=_geographic_yaxis_scaleratio(center_lat),
|
||||||
|
showgrid=False,
|
||||||
|
zeroline=False,
|
||||||
|
),
|
||||||
|
plot_bgcolor='white',
|
||||||
|
paper_bgcolor='white',
|
||||||
|
showlegend=True,
|
||||||
|
legend=dict(
|
||||||
|
title=dict(text=s.map_legend, font=dict(size=22)),
|
||||||
|
font=dict(size=18),
|
||||||
|
orientation='h',
|
||||||
|
x=0.5,
|
||||||
|
xanchor='center',
|
||||||
|
y=-0.08,
|
||||||
|
yanchor='top',
|
||||||
|
bgcolor='rgba(255, 255, 255, 0.8)',
|
||||||
|
bordercolor='black',
|
||||||
|
borderwidth=1,
|
||||||
|
itemsizing='constant',
|
||||||
|
itemwidth=30,
|
||||||
|
),
|
||||||
|
width=figure_width,
|
||||||
|
height=figure_height,
|
||||||
|
margin=dict(
|
||||||
|
l=COORDINATE_PLANE_MARGIN_L,
|
||||||
|
r=COORDINATE_PLANE_MARGIN_R,
|
||||||
|
t=COORDINATE_PLANE_MARGIN_T,
|
||||||
|
b=COORDINATE_PLANE_MARGIN_B,
|
||||||
|
),
|
||||||
|
font=dict(size=18),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _lightning_ring_indices_for_strikes(
|
||||||
|
turbine_lat: float,
|
||||||
|
turbine_lon: float,
|
||||||
|
strike_df: pd.DataFrame,
|
||||||
|
full_lightning_df: pd.DataFrame,
|
||||||
|
precomputed: dict | None,
|
||||||
|
type_mask: np.ndarray,
|
||||||
|
) -> np.ndarray:
|
||||||
|
if len(strike_df) == 0:
|
||||||
|
return np.array([], dtype=int)
|
||||||
|
if (
|
||||||
|
precomputed is not None
|
||||||
|
and 'ring_idx' in precomputed
|
||||||
|
and len(precomputed['ring_idx']) == len(full_lightning_df)
|
||||||
|
):
|
||||||
|
return precomputed['ring_idx'][type_mask].astype(int)
|
||||||
|
|
||||||
|
indices: list[int] = []
|
||||||
|
for _, lightning in strike_df.iterrows():
|
||||||
|
d = haversine_distance(turbine_lat, turbine_lon, lightning['lat'], lightning['lng'])
|
||||||
|
ring_i = len(config.distance_rings) - 1
|
||||||
|
for i, ring in enumerate(config.distance_rings):
|
||||||
|
if d <= ring:
|
||||||
|
ring_i = i
|
||||||
|
break
|
||||||
|
indices.append(ring_i)
|
||||||
|
return np.array(indices, dtype=int)
|
||||||
|
|
||||||
|
|
||||||
|
def _lightning_ring_legend_label(base_legend_name: str, ring_i: int, s) -> str:
|
||||||
|
lower_km = 0.0 if ring_i == 0 else config.distance_rings[ring_i - 1] / 1000.0
|
||||||
|
upper_km = config.distance_rings[ring_i] / 1000.0
|
||||||
|
return s.map_lightning_ring_range.format(
|
||||||
|
name=base_legend_name,
|
||||||
|
lower=lower_km,
|
||||||
|
upper=upper_km,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _lightning_ring_beyond_legend_label(base_legend_name: str, s) -> str:
|
||||||
|
outer_km = config.distance_rings[-1] / 1000.0
|
||||||
|
return s.map_lightning_ring_beyond.format(name=base_legend_name, outer=outer_km)
|
||||||
|
|
||||||
|
|
||||||
|
def _add_ring_colored_lightning_traces(
|
||||||
|
fig: go.Figure,
|
||||||
|
strike_df: pd.DataFrame,
|
||||||
|
ring_indices: np.ndarray,
|
||||||
|
sizes: np.ndarray,
|
||||||
|
base_legend_name: str,
|
||||||
|
s,
|
||||||
|
) -> None:
|
||||||
|
if len(strike_df) == 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
lngs = strike_df['lng'].values
|
||||||
|
lats = strike_df['lat'].values
|
||||||
|
n_rings = len(config.distance_rings)
|
||||||
|
|
||||||
|
for ring_i in range(n_rings):
|
||||||
|
mask = ring_indices == ring_i
|
||||||
|
if not np.any(mask):
|
||||||
|
continue
|
||||||
|
color = config.ring_colors[ring_i]
|
||||||
|
fig.add_trace(go.Scatter(
|
||||||
|
x=lngs[mask],
|
||||||
|
y=lats[mask],
|
||||||
|
mode='markers',
|
||||||
|
marker=dict(
|
||||||
|
size=sizes[mask],
|
||||||
|
color=color,
|
||||||
|
opacity=0.9,
|
||||||
|
symbol='circle',
|
||||||
|
sizemin=COORDINATE_PLANE_LIGHTNING_SIZE_MIN,
|
||||||
|
line=dict(width=1, color='white'),
|
||||||
|
),
|
||||||
|
name=_lightning_ring_legend_label(base_legend_name, ring_i, s),
|
||||||
|
showlegend=True,
|
||||||
|
))
|
||||||
|
|
||||||
|
outside = ring_indices >= n_rings
|
||||||
|
if np.any(outside):
|
||||||
|
fig.add_trace(go.Scatter(
|
||||||
|
x=lngs[outside],
|
||||||
|
y=lats[outside],
|
||||||
|
mode='markers',
|
||||||
|
marker=dict(
|
||||||
|
size=sizes[outside],
|
||||||
|
color='gray',
|
||||||
|
opacity=0.9,
|
||||||
|
symbol='circle',
|
||||||
|
sizemin=COORDINATE_PLANE_LIGHTNING_SIZE_MIN,
|
||||||
|
line=dict(width=1, color='white'),
|
||||||
|
),
|
||||||
|
name=_lightning_ring_beyond_legend_label(base_legend_name, s),
|
||||||
|
showlegend=True,
|
||||||
|
))
|
||||||
|
|
||||||
|
|
||||||
|
def _turbine_bounds_with_padding(
|
||||||
|
turbine_df: pd.DataFrame,
|
||||||
|
padding_ratio: float = 0.18,
|
||||||
|
min_pad_deg: float = 0.0015,
|
||||||
|
) -> tuple[float, float, float, float]:
|
||||||
|
lat_min = float(turbine_df['lat'].min())
|
||||||
|
lat_max = float(turbine_df['lat'].max())
|
||||||
|
lon_min = float(turbine_df['lng'].min())
|
||||||
|
lon_max = float(turbine_df['lng'].max())
|
||||||
|
center_lat = (lat_min + lat_max) / 2.0
|
||||||
|
cos_lat = max(float(np.cos(np.radians(center_lat))), 1e-6)
|
||||||
|
lat_pad = max((lat_max - lat_min) * padding_ratio, min_pad_deg)
|
||||||
|
lon_pad = max((lon_max - lon_min) * padding_ratio, min_pad_deg / cos_lat)
|
||||||
|
return lat_min - lat_pad, lat_max + lat_pad, lon_min - lon_pad, lon_max + lon_pad
|
||||||
|
|
||||||
|
|
||||||
|
def turbines_need_reference_map(turbine_df: pd.DataFrame) -> bool:
|
||||||
|
if turbine_df is None or len(turbine_df) < TURBINE_REFERENCE_MIN_TURBINES:
|
||||||
|
return False
|
||||||
|
|
||||||
|
lats = turbine_df['lat'].to_numpy(dtype=float)
|
||||||
|
lngs = turbine_df['lng'].to_numpy(dtype=float)
|
||||||
|
min_dist_m = float('inf')
|
||||||
|
for i in range(len(lats)):
|
||||||
|
for j in range(i + 1, len(lats)):
|
||||||
|
min_dist_m = min(
|
||||||
|
min_dist_m,
|
||||||
|
haversine_distance(lats[i], lngs[i], lats[j], lngs[j]),
|
||||||
|
)
|
||||||
|
|
||||||
|
if min_dist_m <= TURBINE_REFERENCE_MIN_SEPARATION_M:
|
||||||
|
return True
|
||||||
|
|
||||||
|
span_deg = max(float(lats.max() - lats.min()), float(lngs.max() - lngs.min()), 1e-9)
|
||||||
|
return len(turbine_df) >= 8 and span_deg < 0.08
|
||||||
|
|
||||||
|
|
||||||
|
def plot_turbine_reference_map(
|
||||||
|
turbine_df: pd.DataFrame,
|
||||||
|
lang: ReportLanguage | None = None,
|
||||||
|
) -> go.Figure | None:
|
||||||
|
if turbine_df is None or turbine_df.empty:
|
||||||
|
return None
|
||||||
|
|
||||||
|
s = get_strings(lang or get_report_language())
|
||||||
|
lat_min, lat_max, lon_min, lon_max = _turbine_bounds_with_padding(turbine_df)
|
||||||
|
center_lat = (lat_min + lat_max) / 2.0
|
||||||
|
|
||||||
|
if 'risk_log' in turbine_df.columns:
|
||||||
|
from src.utils import get_turbine_colors_by_fixed_intervals
|
||||||
|
turbine_colors = get_turbine_colors_by_fixed_intervals(turbine_df['risk_log'].tolist())
|
||||||
|
else:
|
||||||
|
turbine_colors = ['red'] * len(turbine_df)
|
||||||
|
|
||||||
|
fig = go.Figure()
|
||||||
|
fig.add_trace(go.Scatter(
|
||||||
|
x=turbine_df['lng'],
|
||||||
|
y=turbine_df['lat'],
|
||||||
|
mode='markers+text',
|
||||||
|
marker=dict(
|
||||||
|
size=TURBINE_REFERENCE_MARKER_SIZE,
|
||||||
|
color=turbine_colors,
|
||||||
|
symbol='triangle-down',
|
||||||
|
opacity=1,
|
||||||
|
line=dict(color='black', width=1),
|
||||||
|
),
|
||||||
|
text=turbine_df['name'].tolist(),
|
||||||
|
textfont=dict(size=TURBINE_REFERENCE_NAME_FONT_SIZE, color='black'),
|
||||||
|
textposition=COORDINATE_PLANE_TURBINE_TEXT_POSITION,
|
||||||
|
name=s.map_wind_turbines,
|
||||||
|
showlegend=True,
|
||||||
|
))
|
||||||
|
|
||||||
|
add_satellite_basemap(fig, lon_min, lon_max, lat_min, lat_max)
|
||||||
|
figure_width, figure_height = _compute_figure_dimensions(
|
||||||
|
center_lat,
|
||||||
|
lon_min,
|
||||||
|
lon_max,
|
||||||
|
lat_min,
|
||||||
|
lat_max,
|
||||||
|
)
|
||||||
|
fig.update_layout(
|
||||||
|
title=dict(text=s.map_turbine_reference_plane_title, font=dict(size=24)),
|
||||||
|
xaxis=dict(
|
||||||
|
title=dict(text=s.map_longitude, font=dict(size=22)),
|
||||||
|
tickfont=dict(size=18),
|
||||||
|
range=[lon_min, lon_max],
|
||||||
|
showgrid=False,
|
||||||
|
zeroline=False,
|
||||||
|
),
|
||||||
|
yaxis=dict(
|
||||||
|
title=dict(text=s.map_latitude, font=dict(size=22)),
|
||||||
|
tickfont=dict(size=18),
|
||||||
|
range=[lat_min, lat_max],
|
||||||
|
scaleanchor='x',
|
||||||
|
scaleratio=_geographic_yaxis_scaleratio(center_lat),
|
||||||
|
showgrid=False,
|
||||||
|
zeroline=False,
|
||||||
|
),
|
||||||
|
plot_bgcolor='white',
|
||||||
|
paper_bgcolor='white',
|
||||||
|
showlegend=True,
|
||||||
|
legend=dict(
|
||||||
|
title=dict(text=s.map_legend, font=dict(size=20)),
|
||||||
|
font=dict(size=16),
|
||||||
|
orientation='h',
|
||||||
|
x=0.5,
|
||||||
|
xanchor='center',
|
||||||
|
y=-0.08,
|
||||||
|
yanchor='top',
|
||||||
|
bgcolor='rgba(255, 255, 255, 0.8)',
|
||||||
|
bordercolor='black',
|
||||||
|
borderwidth=1,
|
||||||
|
),
|
||||||
|
width=figure_width,
|
||||||
|
height=figure_height,
|
||||||
|
margin=dict(
|
||||||
|
l=COORDINATE_PLANE_MARGIN_L,
|
||||||
|
r=COORDINATE_PLANE_MARGIN_R,
|
||||||
|
t=COORDINATE_PLANE_MARGIN_T,
|
||||||
|
b=COORDINATE_PLANE_MARGIN_B,
|
||||||
|
),
|
||||||
|
font=dict(size=16),
|
||||||
|
)
|
||||||
|
return fig
|
||||||
|
|
||||||
def plot_turbine_map(turbine_row: pd.Series, lightning_df: pd.DataFrame, turbine_df: pd.DataFrame) -> go.Figure:
|
def plot_turbine_map(turbine_row: pd.Series, lightning_df: pd.DataFrame, turbine_df: pd.DataFrame) -> go.Figure:
|
||||||
turbine_lat = turbine_row['lat']
|
turbine_lat = turbine_row['lat']
|
||||||
@ -500,7 +881,7 @@ def plot_intercloud_coordinate_plane(turbine_row: pd.Series, lightning_df: pd.Da
|
|||||||
line=dict(color=color, width=COORDINATE_PLANE_RING_LINE_WIDTH),
|
line=dict(color=color, width=COORDINATE_PLANE_RING_LINE_WIDTH),
|
||||||
opacity=0.6,
|
opacity=0.6,
|
||||||
name=s.map_distance_ring.format(radius=radius / 1000),
|
name=s.map_distance_ring.format(radius=radius / 1000),
|
||||||
showlegend=True
|
showlegend=False
|
||||||
))
|
))
|
||||||
|
|
||||||
# Add turbines (middle layer)
|
# Add turbines (middle layer)
|
||||||
@ -557,99 +938,45 @@ def plot_intercloud_coordinate_plane(turbine_row: pd.Series, lightning_df: pd.Da
|
|||||||
ic_lightning_df = lightning_df[ic_mask]
|
ic_lightning_df = lightning_df[ic_mask]
|
||||||
|
|
||||||
if len(ic_lightning_df) > 0:
|
if len(ic_lightning_df) > 0:
|
||||||
# Plot intercloud lightnings (foreground layer - always on top)
|
ring_indices = _lightning_ring_indices_for_strikes(
|
||||||
lightning_colors = []
|
turbine_lat,
|
||||||
if precomputed is not None and 'ring_idx' in precomputed and len(precomputed['ring_idx']) == len(lightning_df):
|
turbine_lon,
|
||||||
mask_ic = (lightning_df['p_type'].astype(str) != '0').values
|
ic_lightning_df,
|
||||||
ring_idx = precomputed['ring_idx'][mask_ic]
|
lightning_df,
|
||||||
for ri in ring_idx:
|
precomputed,
|
||||||
if 0 <= int(ri) < len(config.ring_colors):
|
ic_mask.values if hasattr(ic_mask, 'values') else ic_mask,
|
||||||
lightning_colors.append(config.ring_colors[int(ri)])
|
)
|
||||||
else:
|
|
||||||
lightning_colors.append('gray')
|
|
||||||
else:
|
|
||||||
for _, lightning in ic_lightning_df.iterrows():
|
|
||||||
d = haversine_distance(turbine_lat, turbine_lon, lightning['lat'], lightning['lng'])
|
|
||||||
color = 'gray'
|
|
||||||
for ring, ring_color in zip(config.distance_rings, config.ring_colors):
|
|
||||||
if d <= ring:
|
|
||||||
color = ring_color
|
|
||||||
break
|
|
||||||
lightning_colors.append(color)
|
|
||||||
|
|
||||||
lightning_sizes = np.clip(
|
lightning_sizes = np.clip(
|
||||||
ic_lightning_df['current_abs'] / COORDINATE_PLANE_LIGHTNING_CURRENT_SCALE,
|
ic_lightning_df['current_abs'] / COORDINATE_PLANE_LIGHTNING_CURRENT_SCALE,
|
||||||
COORDINATE_PLANE_LIGHTNING_SIZE_MIN,
|
COORDINATE_PLANE_LIGHTNING_SIZE_MIN,
|
||||||
COORDINATE_PLANE_LIGHTNING_SIZE_MAX,
|
COORDINATE_PLANE_LIGHTNING_SIZE_MAX,
|
||||||
)
|
)
|
||||||
|
_add_ring_colored_lightning_traces(
|
||||||
fig.add_trace(go.Scatter(
|
fig,
|
||||||
x=ic_lightning_df['lng'], # X-axis = Longitude
|
ic_lightning_df,
|
||||||
y=ic_lightning_df['lat'], # Y-axis = Latitude
|
ring_indices,
|
||||||
mode='markers',
|
lightning_sizes,
|
||||||
marker=dict(
|
s.map_ic_lightning,
|
||||||
size=lightning_sizes,
|
s,
|
||||||
color=lightning_colors,
|
)
|
||||||
opacity=0.9,
|
|
||||||
symbol='circle',
|
|
||||||
sizemin=COORDINATE_PLANE_LIGHTNING_SIZE_MIN,
|
|
||||||
line=dict(width=1, color='white'),
|
|
||||||
),
|
|
||||||
name=s.map_ic_lightning,
|
|
||||||
showlegend=True
|
|
||||||
))
|
|
||||||
|
|
||||||
# Calculate axis limits
|
# Calculate axis limits
|
||||||
max_radius_deg = max(config.distance_rings) / 111000
|
lat_min, lat_max, lon_min, lon_max = _coordinate_plane_bounds(
|
||||||
|
turbine_lat,
|
||||||
lat_min = turbine_lat - max_radius_deg * 1.5
|
turbine_lon,
|
||||||
lat_max = turbine_lat + max_radius_deg * 1.5
|
max(config.distance_rings),
|
||||||
lon_min = turbine_lon - max_radius_deg * 1.5
|
)
|
||||||
lon_max = turbine_lon + max_radius_deg * 1.5
|
|
||||||
|
|
||||||
add_satellite_basemap(fig, lon_min, lon_max, lat_min, lat_max)
|
add_satellite_basemap(fig, lon_min, lon_max, lat_min, lat_max)
|
||||||
|
_apply_coordinate_plane_layout(
|
||||||
fig.update_layout(
|
fig,
|
||||||
title=s.map_ic_plane_title.format(name=turbine_row["name"]),
|
s.map_ic_plane_title.format(name=turbine_row["name"]),
|
||||||
xaxis=dict(
|
s,
|
||||||
title=dict(text=s.map_longitude, font=dict(size=28)), # x-axis title font size
|
turbine_lat,
|
||||||
tickfont=dict(size=22), # x-axis tick label font size
|
lon_min,
|
||||||
range=[lon_min, lon_max],
|
lon_max,
|
||||||
showgrid=False,
|
lat_min,
|
||||||
gridwidth=1,
|
lat_max,
|
||||||
gridcolor='lightgray',
|
|
||||||
zeroline=False
|
|
||||||
),
|
|
||||||
yaxis=dict(
|
|
||||||
title=dict(text=s.map_latitude, font=dict(size=28)), # y-axis title font size
|
|
||||||
tickfont=dict(size=22), # y-axis tick label font size
|
|
||||||
range=[lat_min, lat_max],
|
|
||||||
showgrid=False,
|
|
||||||
gridwidth=1,
|
|
||||||
gridcolor='lightgray',
|
|
||||||
zeroline=False
|
|
||||||
),
|
|
||||||
plot_bgcolor='white',
|
|
||||||
paper_bgcolor='white',
|
|
||||||
showlegend=True,
|
|
||||||
legend=dict(
|
|
||||||
title=dict(text=s.map_legend, font=dict(size=24)), # legend title font size
|
|
||||||
font=dict(size=20), # legend item font size
|
|
||||||
orientation='h',
|
|
||||||
x=0.5,
|
|
||||||
xanchor='center',
|
|
||||||
y=-0.22,
|
|
||||||
yanchor='top',
|
|
||||||
bgcolor='rgba(255, 255, 255, 0.8)',
|
|
||||||
bordercolor='black',
|
|
||||||
borderwidth=1,
|
|
||||||
itemsizing='constant',
|
|
||||||
itemwidth=30,
|
|
||||||
),
|
|
||||||
width=950,
|
|
||||||
height=950,
|
|
||||||
margin=dict(l=40, r=40, t=80, b=220),
|
|
||||||
font=dict(size=18), # global chart font size
|
|
||||||
)
|
)
|
||||||
|
|
||||||
return fig
|
return fig
|
||||||
@ -673,7 +1000,7 @@ def plot_cloud_to_ground_coordinate_plane(turbine_row: pd.Series, lightning_df:
|
|||||||
line=dict(color=color, width=COORDINATE_PLANE_RING_LINE_WIDTH),
|
line=dict(color=color, width=COORDINATE_PLANE_RING_LINE_WIDTH),
|
||||||
opacity=0.6,
|
opacity=0.6,
|
||||||
name=s.map_distance_ring.format(radius=radius / 1000),
|
name=s.map_distance_ring.format(radius=radius / 1000),
|
||||||
showlegend=True
|
showlegend=False
|
||||||
))
|
))
|
||||||
|
|
||||||
# Add turbines (middle layer)
|
# Add turbines (middle layer)
|
||||||
@ -730,99 +1057,45 @@ def plot_cloud_to_ground_coordinate_plane(turbine_row: pd.Series, lightning_df:
|
|||||||
cg_lightning_df = lightning_df[cg_mask]
|
cg_lightning_df = lightning_df[cg_mask]
|
||||||
|
|
||||||
if len(cg_lightning_df) > 0:
|
if len(cg_lightning_df) > 0:
|
||||||
# Plot cloud-to-ground lightnings (foreground layer - always on top)
|
ring_indices = _lightning_ring_indices_for_strikes(
|
||||||
lightning_colors = []
|
turbine_lat,
|
||||||
if precomputed is not None and 'ring_idx' in precomputed and len(precomputed['ring_idx']) == len(lightning_df):
|
turbine_lon,
|
||||||
mask_cg = (lightning_df['p_type'].astype(str) == '0').values
|
cg_lightning_df,
|
||||||
ring_idx = precomputed['ring_idx'][mask_cg]
|
lightning_df,
|
||||||
for ri in ring_idx:
|
precomputed,
|
||||||
if 0 <= int(ri) < len(config.ring_colors):
|
cg_mask.values if hasattr(cg_mask, 'values') else cg_mask,
|
||||||
lightning_colors.append(config.ring_colors[int(ri)])
|
)
|
||||||
else:
|
|
||||||
lightning_colors.append('gray')
|
|
||||||
else:
|
|
||||||
for _, lightning in cg_lightning_df.iterrows():
|
|
||||||
d = haversine_distance(turbine_lat, turbine_lon, lightning['lat'], lightning['lng'])
|
|
||||||
color = 'gray'
|
|
||||||
for ring, ring_color in zip(config.distance_rings, config.ring_colors):
|
|
||||||
if d <= ring:
|
|
||||||
color = ring_color
|
|
||||||
break
|
|
||||||
lightning_colors.append(color)
|
|
||||||
|
|
||||||
lightning_sizes = np.clip(
|
lightning_sizes = np.clip(
|
||||||
cg_lightning_df['current_abs'] / COORDINATE_PLANE_LIGHTNING_CURRENT_SCALE,
|
cg_lightning_df['current_abs'] / COORDINATE_PLANE_LIGHTNING_CURRENT_SCALE,
|
||||||
COORDINATE_PLANE_LIGHTNING_SIZE_MIN,
|
COORDINATE_PLANE_LIGHTNING_SIZE_MIN,
|
||||||
COORDINATE_PLANE_LIGHTNING_SIZE_MAX,
|
COORDINATE_PLANE_LIGHTNING_SIZE_MAX,
|
||||||
)
|
)
|
||||||
|
_add_ring_colored_lightning_traces(
|
||||||
fig.add_trace(go.Scatter(
|
fig,
|
||||||
x=cg_lightning_df['lng'], # X-axis = Longitude
|
cg_lightning_df,
|
||||||
y=cg_lightning_df['lat'], # Y-axis = Latitude
|
ring_indices,
|
||||||
mode='markers',
|
lightning_sizes,
|
||||||
marker=dict(
|
s.map_cg_lightning,
|
||||||
size=lightning_sizes,
|
s,
|
||||||
color=lightning_colors,
|
)
|
||||||
opacity=0.9,
|
|
||||||
symbol='circle',
|
|
||||||
sizemin=COORDINATE_PLANE_LIGHTNING_SIZE_MIN,
|
|
||||||
line=dict(width=1, color='white'),
|
|
||||||
),
|
|
||||||
name=s.map_cg_lightning,
|
|
||||||
showlegend=True
|
|
||||||
))
|
|
||||||
|
|
||||||
# Calculate axis limits
|
# Calculate axis limits
|
||||||
max_radius_deg = max(config.distance_rings) / 111000
|
lat_min, lat_max, lon_min, lon_max = _coordinate_plane_bounds(
|
||||||
|
turbine_lat,
|
||||||
lat_min = turbine_lat - max_radius_deg * 1.5
|
turbine_lon,
|
||||||
lat_max = turbine_lat + max_radius_deg * 1.5
|
max(config.distance_rings),
|
||||||
lon_min = turbine_lon - max_radius_deg * 1.5
|
)
|
||||||
lon_max = turbine_lon + max_radius_deg * 1.5
|
|
||||||
|
|
||||||
add_satellite_basemap(fig, lon_min, lon_max, lat_min, lat_max)
|
add_satellite_basemap(fig, lon_min, lon_max, lat_min, lat_max)
|
||||||
|
_apply_coordinate_plane_layout(
|
||||||
fig.update_layout(
|
fig,
|
||||||
title=s.map_cg_plane_title.format(name=turbine_row["name"]),
|
s.map_cg_plane_title.format(name=turbine_row["name"]),
|
||||||
xaxis=dict(
|
s,
|
||||||
title=dict(text=s.map_longitude, font=dict(size=28)), # x-axis title font size
|
turbine_lat,
|
||||||
tickfont=dict(size=22), # x-axis tick label font size
|
lon_min,
|
||||||
range=[lon_min, lon_max],
|
lon_max,
|
||||||
showgrid=False,
|
lat_min,
|
||||||
gridwidth=1,
|
lat_max,
|
||||||
gridcolor='lightgray',
|
|
||||||
zeroline=False
|
|
||||||
),
|
|
||||||
yaxis=dict(
|
|
||||||
title=dict(text=s.map_latitude, font=dict(size=28)), # y-axis title font size
|
|
||||||
tickfont=dict(size=22), # y-axis tick label font size
|
|
||||||
range=[lat_min, lat_max],
|
|
||||||
showgrid=False,
|
|
||||||
gridwidth=1,
|
|
||||||
gridcolor='lightgray',
|
|
||||||
zeroline=False
|
|
||||||
),
|
|
||||||
plot_bgcolor='white',
|
|
||||||
paper_bgcolor='white',
|
|
||||||
showlegend=True,
|
|
||||||
legend=dict(
|
|
||||||
title=dict(text=s.map_legend, font=dict(size=24)), # legend title font size
|
|
||||||
font=dict(size=20), # legend item font size
|
|
||||||
orientation='h',
|
|
||||||
x=0.5,
|
|
||||||
xanchor='center',
|
|
||||||
y=-0.22,
|
|
||||||
yanchor='top',
|
|
||||||
bgcolor='rgba(255, 255, 255, 0.8)',
|
|
||||||
bordercolor='black',
|
|
||||||
borderwidth=1,
|
|
||||||
itemsizing='constant',
|
|
||||||
itemwidth=30,
|
|
||||||
),
|
|
||||||
width=950,
|
|
||||||
height=950,
|
|
||||||
margin=dict(l=40, r=40, t=80, b=220),
|
|
||||||
font=dict(size=18), # global chart font size
|
|
||||||
)
|
)
|
||||||
|
|
||||||
return fig
|
return fig
|
||||||
|
|||||||
@ -14,8 +14,14 @@ from src.reporting.strings import ReportLanguage, get_report_language, get_strin
|
|||||||
from src.utils import parse_period_string_to_datetime
|
from src.utils import parse_period_string_to_datetime
|
||||||
from src.visualization.basemap import add_satellite_basemap
|
from src.visualization.basemap import add_satellite_basemap
|
||||||
from src.visualization.maps import (
|
from src.visualization.maps import (
|
||||||
|
COORDINATE_PLANE_MARGIN_B,
|
||||||
|
COORDINATE_PLANE_MARGIN_L,
|
||||||
|
COORDINATE_PLANE_MARGIN_R,
|
||||||
|
COORDINATE_PLANE_MARGIN_T,
|
||||||
COORDINATE_PLANE_TURBINE_NAME_FONT_SIZE,
|
COORDINATE_PLANE_TURBINE_NAME_FONT_SIZE,
|
||||||
COORDINATE_PLANE_TURBINE_TEXT_POSITION,
|
COORDINATE_PLANE_TURBINE_TEXT_POSITION,
|
||||||
|
_compute_figure_dimensions,
|
||||||
|
_geographic_yaxis_scaleratio,
|
||||||
)
|
)
|
||||||
|
|
||||||
def format_datetime_for_display(datetime_str: str) -> str:
|
def format_datetime_for_display(datetime_str: str) -> str:
|
||||||
@ -286,8 +292,10 @@ def create_storm_cells_coordinate_plane(storm_data: List[Dict], turbine_df: pd.D
|
|||||||
lat_max = max(bounds_lats)
|
lat_max = max(bounds_lats)
|
||||||
lon_min = min(bounds_lons)
|
lon_min = min(bounds_lons)
|
||||||
lon_max = max(bounds_lons)
|
lon_max = max(bounds_lons)
|
||||||
|
map_center_lat = (lat_min + lat_max) / 2.0
|
||||||
|
cos_lat = max(float(np.cos(np.radians(map_center_lat))), 1e-6)
|
||||||
lat_pad = max((lat_max - lat_min) * 0.15, 0.01)
|
lat_pad = max((lat_max - lat_min) * 0.15, 0.01)
|
||||||
lon_pad = max((lon_max - lon_min) * 0.15, 0.01)
|
lon_pad = max((lon_max - lon_min) * 0.15, 0.01 / cos_lat)
|
||||||
lat_min -= lat_pad
|
lat_min -= lat_pad
|
||||||
lat_max += lat_pad
|
lat_max += lat_pad
|
||||||
lon_min -= lon_pad
|
lon_min -= lon_pad
|
||||||
@ -298,50 +306,59 @@ def create_storm_cells_coordinate_plane(storm_data: List[Dict], turbine_df: pd.D
|
|||||||
lat_max = center_lat + pad
|
lat_max = center_lat + pad
|
||||||
lon_min = center_lon - pad
|
lon_min = center_lon - pad
|
||||||
lon_max = center_lon + pad
|
lon_max = center_lon + pad
|
||||||
|
map_center_lat = center_lat
|
||||||
|
|
||||||
add_satellite_basemap(fig, lon_min, lon_max, lat_min, lat_max)
|
add_satellite_basemap(fig, lon_min, lon_max, lat_min, lat_max)
|
||||||
|
figure_width, figure_height = _compute_figure_dimensions(
|
||||||
|
map_center_lat,
|
||||||
|
lon_min,
|
||||||
|
lon_max,
|
||||||
|
lat_min,
|
||||||
|
lat_max,
|
||||||
|
)
|
||||||
|
|
||||||
fig.update_layout(
|
fig.update_layout(
|
||||||
font=dict(size=18),
|
font=dict(size=18),
|
||||||
title=dict(text=s.storm_cells, font=dict(size=28)),
|
title=dict(text=s.storm_cells, font=dict(size=28)),
|
||||||
xaxis_title=s.map_longitude,
|
|
||||||
yaxis_title=s.map_latitude,
|
|
||||||
xaxis=dict(
|
xaxis=dict(
|
||||||
|
title=dict(text=s.map_longitude, font=dict(size=28)),
|
||||||
|
tickfont=dict(size=22),
|
||||||
range=[lon_min, lon_max],
|
range=[lon_min, lon_max],
|
||||||
showgrid=False,
|
showgrid=False,
|
||||||
gridwidth=1,
|
|
||||||
gridcolor='lightgray',
|
|
||||||
zeroline=False,
|
zeroline=False,
|
||||||
tickfont=dict(size=22),
|
|
||||||
title_font=dict(size=28),
|
|
||||||
),
|
),
|
||||||
yaxis=dict(
|
yaxis=dict(
|
||||||
range=[lat_min, lat_max],
|
title=dict(text=s.map_latitude, font=dict(size=28)),
|
||||||
showgrid=False,
|
|
||||||
gridwidth=1,
|
|
||||||
gridcolor='lightgray',
|
|
||||||
zeroline=False,
|
|
||||||
tickfont=dict(size=22),
|
tickfont=dict(size=22),
|
||||||
title_font=dict(size=28),
|
range=[lat_min, lat_max],
|
||||||
|
scaleanchor='x',
|
||||||
|
scaleratio=_geographic_yaxis_scaleratio(map_center_lat),
|
||||||
|
showgrid=False,
|
||||||
|
zeroline=False,
|
||||||
),
|
),
|
||||||
plot_bgcolor='white',
|
plot_bgcolor='white',
|
||||||
paper_bgcolor='white',
|
paper_bgcolor='white',
|
||||||
showlegend=True,
|
showlegend=True,
|
||||||
legend=dict(
|
legend=dict(
|
||||||
title=dict(text=s.map_legend, font=dict(size=24)),
|
title=dict(text=s.map_legend, font=dict(size=22)),
|
||||||
font=dict(size=20),
|
font=dict(size=18),
|
||||||
orientation='h',
|
orientation='h',
|
||||||
x=0.5,
|
x=0.5,
|
||||||
xanchor='center',
|
xanchor='center',
|
||||||
y=-0.18,
|
y=-0.08,
|
||||||
yanchor='top',
|
yanchor='top',
|
||||||
bgcolor='rgba(255, 255, 255, 0.8)',
|
bgcolor='rgba(255, 255, 255, 0.8)',
|
||||||
bordercolor='black',
|
bordercolor='black',
|
||||||
borderwidth=1,
|
borderwidth=1,
|
||||||
),
|
),
|
||||||
width=800,
|
width=figure_width,
|
||||||
height=900,
|
height=figure_height,
|
||||||
margin=dict(l=70, r=40, t=50, b=130),
|
margin=dict(
|
||||||
|
l=COORDINATE_PLANE_MARGIN_L,
|
||||||
|
r=COORDINATE_PLANE_MARGIN_R,
|
||||||
|
t=COORDINATE_PLANE_MARGIN_T,
|
||||||
|
b=COORDINATE_PLANE_MARGIN_B,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
return fig
|
return fig
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user