Compare commits

..

1 Commits

7 changed files with 150 additions and 19 deletions

View File

@ -26,8 +26,8 @@ class OpenMeteoClient:
return responses return responses
def process_response(self, response): def process_response(self, response):
# Process hourly data
daily = response.Daily() daily = response.Daily()
daily_wind_speed_10m = daily.Variables(0).ValuesAsNumpy() daily_wind_speed_10m = daily.Variables(0).ValuesAsNumpy()
daily_wind_direction_10m = daily.Variables(1).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): 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) responses = self.fetch_weather_data(latitude, longitude, start_date, end_date)
response = responses[0] response = responses[0]
self.process_response(response)
return 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 # TODO Maybe create env file
DATABASE_URL = "mysql+pymysql://wind:wind@193.124.203.110:3306/wind_towers" 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() 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: def get_db() -> Session:

View File

@ -44,7 +44,7 @@ class WindTurbineType(Base):
BladeLength: Mapped[float] = mapped_column(Float) BladeLength: Mapped[float] = mapped_column(Float)
# Связь с WindParkTurbine # Связь с 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): class WindPark(Base):
@ -55,14 +55,14 @@ class WindPark(Base):
CenterLongitude: Mapped[float] = mapped_column(Float) CenterLongitude: Mapped[float] = mapped_column(Float)
# Связь с WindParkTurbine # Связь с 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): class WindParkTurbine(Base):
__tablename__ = 'wind_park_turbine' __tablename__ = 'wind_park_turbine'
wind_park_id: Mapped[int] = mapped_column(Integer, ForeignKey('wind_park.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'), 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) x_offset: Mapped[int] = mapped_column(Integer, nullable=False)
y_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) 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) return SWeatherInfo.model_validate(weather_model, from_attributes=True)
class WindTurbineTypeRepository: class WindTurbineTypeRepository:
@staticmethod @staticmethod
def create(db: Session, turbine_type: WindTurbineTypeCreate): def create(db: Session, turbine_type: WindTurbineTypeCreate):
@ -144,11 +143,13 @@ class WindParkTurbineRepository:
@staticmethod @staticmethod
def get(db: Session, park_id: int, turbine_id: int): 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 @staticmethod
def update(db: Session, park_id: int, turbine_id: int, park_turbine: WindParkTurbineCreate): 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: if db_park_turbine:
for key, value in park_turbine.dict().items(): for key, value in park_turbine.dict().items():
setattr(db_park_turbine, key, value) setattr(db_park_turbine, key, value)
@ -159,7 +160,8 @@ class WindParkTurbineRepository:
@staticmethod @staticmethod
def delete(db: Session, park_id: int, turbine_id: int): 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: if db_park_turbine:
db.delete(db_park_turbine) db.delete(db_park_turbine)
db.commit() db.commit()

View File

@ -20,6 +20,12 @@ class SFlorisInputParams(BaseModel):
date_end: date date_end: date
class SFlorisInputParamsForWindPark(BaseModel):
plots: set[str] = Field(Query(["horizontal_plane"]))
date_start: date
date_end: date
class SFlorisOutputData(BaseModel): class SFlorisOutputData(BaseModel):
file_name: object file_name: object
data: object data: object
@ -99,6 +105,7 @@ class WindTurbineResponse(BaseModel):
class Config: class Config:
orm_mode = True orm_mode = True
class WindTurbineWithParkDetailsResponse(BaseModel): class WindTurbineWithParkDetailsResponse(BaseModel):
Id: int Id: int
Name: str Name: str

View File

@ -1,3 +1,4 @@
import math
import sys import sys
from http import HTTPStatus from http import HTTPStatus
from pathlib import Path from pathlib import Path
@ -6,16 +7,17 @@ from typing import Annotated
from fastapi import APIRouter, HTTPException, Depends from fastapi import APIRouter, HTTPException, Depends
from fastapi.responses import FileResponse from fastapi.responses import FileResponse
from data.repository import WeatherRepository from sqlalchemy.orm import Session
from data.schemas import SFlorisInputParams, SFlorisOutputData, SWeatherInfo
import numpy as np
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)) sys.path.append(str(Path(__file__).parent.parent.parent))
from floris_module.src import FlorisULSTU from floris_module.src import FlorisULSTU
from floris_module.src.OpenMeteoClient import OpenMeteoClient from floris_module.src.OpenMeteoClient import OpenMeteoClient
FLORIS_IMAGES_PATH = Path(__file__).parent.parent.parent / "public" / "floris" FLORIS_IMAGES_PATH = Path(__file__).parent.parent.parent / "public" / "floris"
router = APIRouter( 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", detail="Length of layout x and y and yaw_angle must be the same",
) )
fmodel = FlorisULSTU() fmodel = FlorisULSTU()
client = OpenMeteoClient() client = OpenMeteoClient()
@ -92,4 +93,123 @@ async def download_image(
return FileResponse(image_path, media_type="image/jpeg", filename=image_name) 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