diff --git a/server/floris_module/src/FlorisULSTU.py b/server/floris_module/src/FlorisULSTU.py index d0971ac..7883d97 100644 --- a/server/floris_module/src/FlorisULSTU.py +++ b/server/floris_module/src/FlorisULSTU.py @@ -1,7 +1,9 @@ +import math import os.path import sys import uuid from pathlib import Path +from typing import Set from floris import FlorisModel from floris.flow_visualization import visualize_cut_plane @@ -10,7 +12,6 @@ import floris.layout_visualization as layoutviz import matplotlib.pyplot as plt - _wind_direction_to_val: dict[str, float] = { "N": 0.0, "E": 90.0, @@ -53,22 +54,90 @@ class FlorisULSTU(FlorisModel): super().set(**kwargs) - def visualisation(self): + def visualization( + self, + interest_plots: Set[str] + ): + dct_functions = { + "horizontal_plane": self._horizontal_plane_visualization, + "vertical_plane": self._vertical_plane_visualization, + } + res = {} - self.reset_operation() + for param in interest_plots: + if param not in dct_functions: + res[param] = "Not found" + continue + res[param] = dct_functions[param]() - horizontal_plane = self.calculate_horizontal_plane(height=90.0) - visualize_cut_plane( - horizontal_plane, - ) + return res + + def _horizontal_plane_visualization(self): + return self._plane_visualization(self.calculate_horizontal_plane, {"height": 90.0}) + + def _vertical_plane_visualization(self): + return self._plane_visualization(self.calculate_y_plane, + {"crossstream_dist": 0.0, + "x_resolution": 200, + "z_resolution": 100}) + + def _plane_visualization(self, plane_func, func_params: dict[str, float] = {}): + wind_dirs = self.core.flow_field.wind_directions + wind_speeds = self.core.flow_field.wind_speeds + + experiment_counts = len(wind_dirs) + near_square = math.ceil(math.sqrt(experiment_counts)) + fig, ax_arr = plt.subplots(near_square, near_square, figsize=(15, 8)) + ax_arr = ax_arr.flatten() + + for experiment_count in range(experiment_counts): + self.reset_operation() + self.set(wind_speeds=[wind_speeds[experiment_count]], wind_directions=[wind_dirs[experiment_count]], + turbulence_intensities=[0.05]) + ax = ax_arr[experiment_count] + plane = plane_func(**func_params) + visualize_cut_plane( + plane, + ax=ax, + title=f"Wind flow day {experiment_count + 1}", + ) - # plot_turbine_labels(self, axarr[0, 0]) filename = str(uuid.uuid4()) + ".png" plt.savefig(Path(__file__).parent.parent.parent / f"public/floris/{filename}") plt.close() + self.set(wind_speeds=wind_speeds, wind_directions=wind_dirs, turbulence_intensities=[0.05] * len(wind_dirs)) + return filename + def _cross_visualization(self, plane_func, func_params: dict[str, float] = {}): + wind_dirs = self.core.flow_field.wind_directions + wind_speeds = self.core.flow_field.wind_speeds + + experiment_counts = len(wind_dirs) + near_square = math.ceil(math.sqrt(experiment_counts)) + fig, ax_arr = plt.subplots(near_square, near_square, figsize=(15, 8)) + ax_arr = ax_arr.flatten() + + for experiment_count in range(experiment_counts): + self.reset_operation() + self.set(wind_speeds=[wind_speeds[experiment_count]], wind_directions=[wind_dirs[experiment_count]], + turbulence_intensities=[0.05]) + ax = ax_arr[experiment_count] + plane = plane_func(**func_params) + visualize_cut_plane( + plane, + ax=ax, + title=f"Wind flow day {experiment_count + 1}", + ) + + filename = str(uuid.uuid4()) + ".png" + plt.savefig(Path(__file__).parent.parent.parent / f"public/floris/{filename}") + plt.close() + + self.set(wind_speeds=wind_speeds, wind_directions=wind_dirs, turbulence_intensities=[0.05] * len(wind_dirs)) + + return filename \ No newline at end of file diff --git a/server/floris_module/src/OpenMeteoClient.py b/server/floris_module/src/OpenMeteoClient.py index deddd5d..9a9a5c4 100644 --- a/server/floris_module/src/OpenMeteoClient.py +++ b/server/floris_module/src/OpenMeteoClient.py @@ -17,7 +17,7 @@ class OpenMeteoClient: params = { "latitude": latitude, "longitude": longitude, - "hourly": ["wind_speed_10m", "wind_direction_10m"], + "daily": ["wind_speed_10m_max", "wind_direction_10m_dominant"], "timezone": self.timezone, "start_date": start_date, "end_date": end_date @@ -27,13 +27,13 @@ class OpenMeteoClient: def process_response(self, response): # Process hourly data - hourly = response.Hourly() - hourly_wind_speed_10m = hourly.Variables(0).ValuesAsNumpy() - hourly_wind_direction_10m = hourly.Variables(1).ValuesAsNumpy() + daily = response.Daily() + daily_wind_speed_10m = daily.Variables(0).ValuesAsNumpy() + daily_wind_direction_10m = daily.Variables(1).ValuesAsNumpy() - return hourly_wind_speed_10m.tolist(), hourly_wind_direction_10m.tolist() + return daily_wind_speed_10m.tolist(), daily_wind_direction_10m.tolist() - def get_weather_info(self, latitude, longitude, start_date, end_date): + def get_weather_info(self, start_date, end_date, latitude=54.35119762746125, longitude=48.389356992149345): responses = self.fetch_weather_data(latitude, longitude, start_date, end_date) response = responses[0] self.process_response(response) diff --git a/server/src/data/schemas.py b/server/src/data/schemas.py index 36825f9..09ae068 100644 --- a/server/src/data/schemas.py +++ b/server/src/data/schemas.py @@ -14,13 +14,15 @@ class SWeatherInfo(BaseModel): class SFlorisInputParams(BaseModel): layout_x: list[float] = Field(Query([])) layout_y: list[float] = Field(Query([])) + yaw_angle: list[float] = Field(Query([])) + plots: set[str] = Field(Query(["horizontal_plane"])) date_start: date date_end: date class SFlorisOutputData(BaseModel): - file_name: str - data: list[float] + file_name: object + data: object class WindTurbineTypeBase(BaseModel): @@ -108,4 +110,4 @@ class WindTurbineWithParkDetailsResponse(BaseModel): comment: Optional[str] class Config: - orm_mode = True \ No newline at end of file + orm_mode = True diff --git a/server/src/routers/floris_router.py b/server/src/routers/floris_router.py index ddccce5..cb68762 100644 --- a/server/src/routers/floris_router.py +++ b/server/src/routers/floris_router.py @@ -8,12 +8,14 @@ from fastapi.responses import FileResponse from data.repository import WeatherRepository from data.schemas import SFlorisInputParams, SFlorisOutputData, SWeatherInfo +import numpy as np sys.path.append(str(Path(__file__).parent.parent.parent)) from floris_module.src import FlorisULSTU from floris_module.src.OpenMeteoClient import OpenMeteoClient + FLORIS_IMAGES_PATH = Path(__file__).parent.parent.parent / "public" / "floris" router = APIRouter( @@ -26,23 +28,20 @@ router = APIRouter( async def get_windmill_data( data: Annotated[SFlorisInputParams, Depends()] ): - if len(data.layout_x) != len(data.layout_y): + if len(data.layout_x) != len(data.layout_y) and len(data.layout_x) != len(data.yaw_angle): raise HTTPException( status_code=HTTPStatus.BAD_REQUEST, - detail="Length of layout x and y must be the same", + detail="Length of layout x and y and yaw_angle must be the same", ) + fmodel = FlorisULSTU() client = OpenMeteoClient() - wind_directions = list() - wind_speeds = list() - - for i in range(len(data.layout_x)): - i_result = client.get_weather_info(data.layout_x[i], data.layout_y[i], data.date_start, data.date_end) - wind_speeds.extend(i_result[0]) - wind_directions.extend(i_result[1]) + climate_info = client.get_weather_info(data.date_start, data.date_end) + wind_speeds = climate_info[0] + wind_directions = climate_info[1] fmodel.set( layout_x=data.layout_x, @@ -52,14 +51,22 @@ async def get_windmill_data( turbulence_intensities=[0.1] * len(wind_directions) ) + yaw_angles = np.zeros((len(wind_directions), len(data.layout_x))) + for i in range(len(data.layout_x)): + yaw_angles[:, i] = data.yaw_angle[i] + + fmodel.set( + yaw_angles=yaw_angles, + ) + fmodel.run() - data = fmodel.get_turbine_powers()[0].tolist() - file_name = fmodel.visualisation() + res = fmodel.get_turbine_powers().tolist() + file_names = fmodel.visualization(data.plots) return SFlorisOutputData( - file_name=file_name, - data=data + file_name=file_names, + data=res )