Compare commits

..

1 Commits

7 changed files with 150 additions and 19 deletions

View File

@ -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)

Binary file not shown.

View File

@ -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:

View File

@ -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)

View File

@ -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()

View File

@ -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

View File

@ -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(
@ -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