Compare commits
1 Commits
main
...
test-wind-
Author | SHA1 | Date | |
---|---|---|---|
f30661c709 |
@ -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.
@ -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:
|
||||||
|
@ -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)
|
||||||
|
@ -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()
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user