From f30661c70935af1841c5a7ab0c8f79dcafe7fd25 Mon Sep 17 00:00:00 2001 From: Danil_Malin Date: Sun, 24 Nov 2024 22:21:23 +0400 Subject: [PATCH] =?UTF-8?q?=D0=9F=D0=BE=D0=BF=D1=8B=D1=82=D0=BA=D0=B0=20?= =?UTF-8?q?=D0=B2=20=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D1=83=20=D1=81=20=D0=BD?= =?UTF-8?q?=D0=BE=D0=B2=D1=8B=D0=BC=D0=B8=20=D1=81=D1=83=D1=89=D0=BD=D0=BE?= =?UTF-8?q?=D1=81=D1=82=D1=8F=D0=BC=D0=B8=20=D0=B8=20=D1=84=D0=BB=D0=BE?= =?UTF-8?q?=D1=80=D0=B8=D1=81.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/floris_module/src/OpenMeteoClient.py | 3 +- server/src/.cache.sqlite | Bin 1703936 -> 1708032 bytes server/src/data/database.py | 9 +- server/src/data/models.py | 8 +- server/src/data/repository.py | 10 +- server/src/data/schemas.py | 7 ++ server/src/routers/floris_router.py | 132 +++++++++++++++++++- 7 files changed, 150 insertions(+), 19 deletions(-) diff --git a/server/floris_module/src/OpenMeteoClient.py b/server/floris_module/src/OpenMeteoClient.py index 9a9a5c4..a6aecf0 100644 --- a/server/floris_module/src/OpenMeteoClient.py +++ b/server/floris_module/src/OpenMeteoClient.py @@ -26,8 +26,8 @@ class OpenMeteoClient: return responses def process_response(self, response): - # Process hourly data daily = response.Daily() + daily_wind_speed_10m = daily.Variables(0).ValuesAsNumpy() daily_wind_direction_10m = daily.Variables(1).ValuesAsNumpy() @@ -36,5 +36,4 @@ class OpenMeteoClient: 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) return self.process_response(response) diff --git a/server/src/.cache.sqlite b/server/src/.cache.sqlite index b9bebe08b77ef50eacfc43dc642258990075d22f..8de4b067f8b9430a36fa7288091364872380479b 100644 GIT binary patch delta 2950 zcmd6pU1(cn7{}k7lQd~^l76M@hc2AT$Oa|heb4tv6K2iDK(}F8@WKn*lbn+-kT#*s z;zlb?>!_4gou+c8h-~0hF9spPyikX*3qc(8Mws}4yReg835DHE>vNLMnry3z`{+x4 zdEfJ#=Xvvn^UwcDsdTb*;pDle9BG7X7vEtI+tQV>kK0^BzU#<}l$rJd<-1;;Z(paX zC%P6r4v1dRsy_yTHa6wFfqWHr*!z=@cS+t&PsY9A8gSn5-0>Ef4i{4nuqDcQyZR9u zbte3D$VQTSilntPmXjLc^mNJ!rgSBxC`80+N|Y2{NLoQo(-dsrw5}6g!J4Yo$~9S# zvB4RdtfZ4TJyw+tU31bYX0hzVS6s|R<~;K@vshinpL!0sXuEAZK2u3;FGiB}MUm3( zF2-J8i@vcIwE+}B19rdxv;a=P1+)TgzymOV7w`dXKs(R@bOK#~ALs`50X;zP<+W(v z%T+f`dl0ki{F?Su*V*5x{Yx>;5qdXaZ~mSo`+M)(&GoZ=esBD%tMCX|0HJO!W&Krh4O7O(!gmyY;KtzWl$F@Jy%;%_{V_B_`DV)-c7{Or-Ls1o; zh@2u263pf%#~W^{gd;M~2_i2ArwiItp<%-iXLjVMgM0UA{%6AD@glVp8>?xMH2tjp zvj#8yuxB;M6IoH^`)Kg{jWg_P$6NnNgZ26+q(MPYi6~0~hvj=UD2NhPB#GlB`3Y$7 ze;>{7sA=%>@}0g@G~)$7p;{J@=h>D8Fvw!d{-vQ5i1S5zbISm6nh!Kc8Vs^Lzu7(a zM;x3JoQxfYeua)4OAKO|8T6Q3(gzOwH=8tnc2GUr=BPx#zpxON6^U1r`v~)v@yemK zxcDGpstxt`3KRC1zVX90KB_<@~N)x(&4cm;Uq*->T`a^KNq`VuVw}oXkn( z%53-G#`5Qi{lw8-^tO}#+w5XPCkC;J@Op%uc*kc%RhF7vaHkK8%46@u594Sx7n~XS E6HH79-T(jq delta 232 zcmWm7yAHun0Eh8&6s?}qoBJh^4k9LzNIFOp-oYjk!orTZ7z|xVU!a2+y2%-g-asOD z7LQ;w_#1x9w|rhW^Ss%{Mt{w3h%Lfq{~O!TKd6VGa$mPd+1 z^t0O3UV2+`v=5uB8}`*!+zM*8Cr-922cbC>PU9v^BOV2E>ng0?n{6X^7r7K-AVC}w ukOUbNNP!9rIB1}Q0VY_Gh74pO2YDz!@e;YEfk;yB6YgWW<`@54T1r1o6i^KS diff --git a/server/src/data/database.py b/server/src/data/database.py index 789eb48..733ba17 100644 --- a/server/src/data/database.py +++ b/server/src/data/database.py @@ -4,12 +4,15 @@ from sqlalchemy.orm import sessionmaker, declarative_base, Session # TODO Maybe create env file DATABASE_URL = "mysql+pymysql://wind:wind@193.124.203.110:3306/wind_towers" -engine = create_engine(DATABASE_URL) -SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) - # Базовый класс для декларативных моделей Base = declarative_base() +engine = create_engine(DATABASE_URL) + +Base.metadata.create_all(engine) + +SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) + # Функция для получения сессии def get_db() -> Session: diff --git a/server/src/data/models.py b/server/src/data/models.py index e85f7eb..0e1100a 100644 --- a/server/src/data/models.py +++ b/server/src/data/models.py @@ -44,7 +44,7 @@ class WindTurbineType(Base): BladeLength: Mapped[float] = mapped_column(Float) # Связь с WindParkTurbine - turbine_parks: Mapped["list[WindParkTurbine]"] = relationship("WindParkTurbine", back_populates="turbine") + turbine_parks: Mapped["list[WindParkTurbine]"] = relationship("WindParkTurbine", back_populates="turbine", cascade='all, delete-orphan') class WindPark(Base): @@ -55,14 +55,14 @@ class WindPark(Base): CenterLongitude: Mapped[float] = mapped_column(Float) # Связь с WindParkTurbine - wind_park_turbines: Mapped["list[WindParkTurbine]"] = relationship("WindParkTurbine", back_populates="wind_park") + wind_park_turbines: Mapped["list[WindParkTurbine]"] = relationship("WindParkTurbine", back_populates="wind_park", cascade='all, delete-orphan') class WindParkTurbine(Base): __tablename__ = 'wind_park_turbine' - wind_park_id: Mapped[int] = mapped_column(Integer, ForeignKey('wind_park.Id'), primary_key=True) - turbine_id: Mapped[int] = mapped_column(Integer, ForeignKey('wind_turbine_type.Id'), primary_key=True) + wind_park_id: Mapped[int] = mapped_column(Integer, ForeignKey('wind_park.Id', ondelete='CASCADE'), primary_key=True) + turbine_id: Mapped[int] = mapped_column(Integer, ForeignKey('wind_turbine_type.Id', ondelete='CASCADE'), primary_key=True) x_offset: Mapped[int] = mapped_column(Integer, nullable=False) y_offset: Mapped[int] = mapped_column(Integer, nullable=False) angle: Mapped[int] = mapped_column(Integer, nullable=True) diff --git a/server/src/data/repository.py b/server/src/data/repository.py index 926c134..9783a82 100644 --- a/server/src/data/repository.py +++ b/server/src/data/repository.py @@ -33,7 +33,6 @@ class WeatherRepository: return SWeatherInfo.model_validate(weather_model, from_attributes=True) - class WindTurbineTypeRepository: @staticmethod def create(db: Session, turbine_type: WindTurbineTypeCreate): @@ -144,11 +143,13 @@ class WindParkTurbineRepository: @staticmethod def get(db: Session, park_id: int, turbine_id: int): - return db.query(WindParkTurbine).filter(WindParkTurbine.wind_park_id == park_id, WindParkTurbine.turbine_id == turbine_id).first() + return db.query(WindParkTurbine).filter(WindParkTurbine.wind_park_id == park_id, + WindParkTurbine.turbine_id == turbine_id).first() @staticmethod def update(db: Session, park_id: int, turbine_id: int, park_turbine: WindParkTurbineCreate): - db_park_turbine = db.query(WindParkTurbine).filter(WindParkTurbine.wind_park_id == park_id, WindParkTurbine.turbine_id == turbine_id).first() + db_park_turbine = db.query(WindParkTurbine).filter(WindParkTurbine.wind_park_id == park_id, + WindParkTurbine.turbine_id == turbine_id).first() if db_park_turbine: for key, value in park_turbine.dict().items(): setattr(db_park_turbine, key, value) @@ -159,7 +160,8 @@ class WindParkTurbineRepository: @staticmethod def delete(db: Session, park_id: int, turbine_id: int): - db_park_turbine = db.query(WindParkTurbine).filter(WindParkTurbine.wind_park_id == park_id, WindParkTurbine.turbine_id == turbine_id).first() + db_park_turbine = db.query(WindParkTurbine).filter(WindParkTurbine.wind_park_id == park_id, + WindParkTurbine.turbine_id == turbine_id).first() if db_park_turbine: db.delete(db_park_turbine) db.commit() diff --git a/server/src/data/schemas.py b/server/src/data/schemas.py index 09ae068..c4a89c5 100644 --- a/server/src/data/schemas.py +++ b/server/src/data/schemas.py @@ -20,6 +20,12 @@ class SFlorisInputParams(BaseModel): date_end: date +class SFlorisInputParamsForWindPark(BaseModel): + plots: set[str] = Field(Query(["horizontal_plane"])) + date_start: date + date_end: date + + class SFlorisOutputData(BaseModel): file_name: object data: object @@ -99,6 +105,7 @@ class WindTurbineResponse(BaseModel): class Config: orm_mode = True + class WindTurbineWithParkDetailsResponse(BaseModel): Id: int Name: str diff --git a/server/src/routers/floris_router.py b/server/src/routers/floris_router.py index cb68762..c0614f5 100644 --- a/server/src/routers/floris_router.py +++ b/server/src/routers/floris_router.py @@ -1,3 +1,4 @@ +import math import sys from http import HTTPStatus from pathlib import Path @@ -6,16 +7,17 @@ from typing import Annotated from fastapi import APIRouter, HTTPException, Depends from fastapi.responses import FileResponse -from data.repository import WeatherRepository -from data.schemas import SFlorisInputParams, SFlorisOutputData, SWeatherInfo -import numpy as np +from sqlalchemy.orm import Session +from data.repository import WeatherRepository, WindParkRepository +from data.schemas import SFlorisInputParams, SFlorisOutputData, SFlorisInputParamsForWindPark +from data.database import get_db +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,7 +28,7 @@ router = APIRouter( @router.get("/get_windmill_data") async def get_windmill_data( - data: Annotated[SFlorisInputParams, Depends()] + data: Annotated[SFlorisInputParams, Depends()] ): if len(data.layout_x) != len(data.layout_y) and len(data.layout_x) != len(data.yaw_angle): raise HTTPException( @@ -34,7 +36,6 @@ async def get_windmill_data( detail="Length of layout x and y and yaw_angle must be the same", ) - fmodel = FlorisULSTU() client = OpenMeteoClient() @@ -92,4 +93,123 @@ async def download_image( return FileResponse(image_path, media_type="image/jpeg", filename=image_name) +@router.get("/get_windmill_data/{park_id}", response_model=SFlorisOutputData) +async def get_windmill_data_by_wind_park( + park_id: int, + data: Annotated[SFlorisInputParamsForWindPark, Depends()], + db: Session = Depends(get_db) +): + fmodel = FlorisULSTU() + + client = OpenMeteoClient() + + park = WindParkRepository.get(db, park_id) + + turbines = WindParkRepository.get_turbines_by_park_id(park_id, db) + + turbine_coordinates = [ + get_new_coordinates(park.CenterLatitude, park.CenterLongitude, turbine.x_offset, turbine.y_offset) + for turbine in turbines + ] + + turbine_yaw_angles = [turbine.angle for turbine in turbines] + + weather_info_list = [ + client.get_weather_info(start_date=data.date_start, end_date=data.date_end, latitude=lat, longitude=lon) + for lat, lon in turbine_coordinates + ] + + park_centerX, park_centerY = get_absolute_coordinates(park.CenterLatitude, park.CenterLongitude) + + turbineX = [ + park_centerX + turbine.x_offset + for turbine in turbines + ] + + turbineY = [ + park_centerY + turbine.y_offset + for turbine in turbines + ] + + wind_speeds = np.array([item[0][0] for item in weather_info_list]) + wind_directions = np.array([item[1][0] for item in weather_info_list]) + + print(wind_directions) + + fmodel.set( + layout_x=turbineX, + layout_y=turbineY, + wind_directions=wind_directions, + wind_speeds=wind_speeds, + turbulence_intensities=[0.1] * len(wind_directions) + ) + + yaw_angles = np.zeros((len(wind_directions), len(turbineX))) + for i in range(len(turbineX)): + yaw_angles[:, i] = turbine_yaw_angles[i] + + fmodel.set( + yaw_angles=yaw_angles, + ) + + fmodel.run() + + res = fmodel.get_turbine_powers().tolist() + file_names = fmodel.visualization(data.plots) + + return SFlorisOutputData( + file_name=file_names, + data=res + ) + + +def get_new_coordinates(lat, lon, dx, dy): + """ + Вычисляет новую широту и долготу, исходя из заданной точки и смещения по осям x и y. + + :param lat: Широта исходной точки (в градусах) + :param lon: Долгота исходной точки (в градусах) + :param dx: Смещение по оси X (в метрах) + :param dy: Смещение по оси Y (в метрах) + :return: (новая_широта, новая_долгота) + """ + # Радиус Земли (в метрах) + R = 6378137 # Средний радиус Земли + + # Преобразуем широту из градусов в радианы + lat_rad = math.radians(lat) + + # Вычисляем смещение в градусах + dlat = dy / R * (180 / math.pi) # Смещение широты + dlon = dx / (R * math.cos(lat_rad)) * (180 / math.pi) # Смещение долготы + + # Вычисляем новые координаты + new_lat = lat + dlat + new_lon = lon + dlon + + return new_lat, new_lon + + +def get_absolute_coordinates(lat, lon): + """ + Конвертирует широту и долготу в абсолютные координаты (x, y) в метрах. + + :param lat: Широта точки (в градусах) + :param lon: Долгота точки (в градусах) + :return: Абсолютные координаты (x, y) в метрах + """ + # Радиус Земли в метрах + R = 6378137 + + # Преобразуем широту и долготу из градусов в радианы + lat_rad = math.radians(lat) + lon_rad = math.radians(lon) + + # Вычисляем координату y (по широте) + y = R * lat_rad # в метрах + + # Вычисляем координату x (по долготе) + x = R * math.cos(lat_rad) * lon_rad # в метрах + + return x, y