diff --git a/.idea/EvaluationEfficiencyOptimizationWind.iml b/.idea/EvaluationEfficiencyOptimizationWind.iml index d0876a7..32cf6f3 100644 --- a/.idea/EvaluationEfficiencyOptimizationWind.iml +++ b/.idea/EvaluationEfficiencyOptimizationWind.iml @@ -1,8 +1,22 @@ - - + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index d56657a..9af92d1 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,4 +1,7 @@ - + + + \ No newline at end of file diff --git a/.idea/other.xml b/.idea/other.xml new file mode 100644 index 0000000..2e75c2e --- /dev/null +++ b/.idea/other.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/floris_module/src/__init__.py b/floris_module/src/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/front/webpack.config.js b/front/webpack.config.js index d840890..a40ac79 100644 --- a/front/webpack.config.js +++ b/front/webpack.config.js @@ -5,7 +5,7 @@ import TsconfigPathsPlugin from 'tsconfig-paths-webpack-plugin'; const __dirname = path.resolve(); const config = { - entry: './src/index.tsx', + entry: './routers/index.tsx', output: { path: path.resolve(__dirname, 'dist'), filename: 'main.js', diff --git a/floris_module/.gitignore b/server/floris_module/.gitignore similarity index 100% rename from floris_module/.gitignore rename to server/floris_module/.gitignore diff --git a/floris_module/NOTES.md b/server/floris_module/NOTES.md similarity index 100% rename from floris_module/NOTES.md rename to server/floris_module/NOTES.md diff --git a/floris_module/docs/Yaw_example.png b/server/floris_module/docs/Yaw_example.png similarity index 100% rename from floris_module/docs/Yaw_example.png rename to server/floris_module/docs/Yaw_example.png diff --git a/floris_module/requirements.txt b/server/floris_module/requirements.txt similarity index 100% rename from floris_module/requirements.txt rename to server/floris_module/requirements.txt diff --git a/floris_module/src/FlorisULSTU.py b/server/floris_module/src/FlorisULSTU.py similarity index 54% rename from floris_module/src/FlorisULSTU.py rename to server/floris_module/src/FlorisULSTU.py index 405a632..d0971ac 100644 --- a/floris_module/src/FlorisULSTU.py +++ b/server/floris_module/src/FlorisULSTU.py @@ -1,4 +1,14 @@ +import os.path +import sys +import uuid +from pathlib import Path + from floris import FlorisModel +from floris.flow_visualization import visualize_cut_plane +from floris.layout_visualization import plot_turbine_labels +import floris.layout_visualization as layoutviz + +import matplotlib.pyplot as plt _wind_direction_to_val: dict[str, float] = { @@ -31,13 +41,34 @@ def _check_wind_direction_definition(wind_direction: str) -> bool: class FlorisULSTU(FlorisModel): - def __init__(self, url_yaml): + def __init__( + self, + url_yaml: Path = Path(__file__).parent / "gch.yaml" + ): super().__init__(url_yaml) def set(self, **kwargs): - if ("wind_directions" in kwargs) and (kwargs["wind_directions"] is list[str]): + if ("wind_directions" in kwargs) and (type(kwargs["wind_directions"][0]) is str): kwargs["wind_directions"] = _convert_winds_list_direction_definitions(kwargs["wind_directions"]) super().set(**kwargs) + def visualisation(self): + + + self.reset_operation() + + horizontal_plane = self.calculate_horizontal_plane(height=90.0) + visualize_cut_plane( + horizontal_plane, + ) + + # 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() + + return filename + + diff --git a/server/floris_module/src/__init__.py b/server/floris_module/src/__init__.py new file mode 100644 index 0000000..4cc26c6 --- /dev/null +++ b/server/floris_module/src/__init__.py @@ -0,0 +1,3 @@ +from .FlorisULSTU import FlorisULSTU + +__all__ = ['FlorisULSTU'] \ No newline at end of file diff --git a/floris_module/src/gch.yaml b/server/floris_module/src/gch.yaml similarity index 95% rename from floris_module/src/gch.yaml rename to server/floris_module/src/gch.yaml index 5c0ea8e..01d727d 100644 --- a/floris_module/src/gch.yaml +++ b/server/floris_module/src/gch.yaml @@ -149,9 +149,9 @@ flow_field: wake: ### - # Select the models to use for the simulation. + # Select the data to use for the simulation. # See :py:mod:`~.wake` for a list - # of available models and their descriptions. + # of available data and their descriptions. model_strings: ### @@ -190,7 +190,7 @@ wake: # Configure the parameters for the wake deflection model # selected above. # Additional blocks can be provided for - # models that are not enabled, but the enabled model + # data that are not enabled, but the enabled model # must have a corresponding parameter block. wake_deflection_parameters: gauss: @@ -210,7 +210,7 @@ wake: # Configure the parameters for the wake velocity deficit model # selected above. # Additional blocks can be provided for - # models that are not enabled, but the enabled model + # data that are not enabled, but the enabled model # must have a corresponding parameter block. wake_velocity_parameters: cc: @@ -234,7 +234,7 @@ wake: # Configure the parameters for the wake turbulence model # selected above. # Additional blocks can be provided for - # models that are not enabled, but the enabled model + # data that are not enabled, but the enabled model # must have a corresponding parameter block. wake_turbulence_parameters: crespo_hernandez: diff --git a/floris_module/src/main.py b/server/floris_module/src/main.py similarity index 100% rename from floris_module/src/main.py rename to server/floris_module/src/main.py diff --git a/server/main.py b/server/main.py deleted file mode 100644 index d523ca6..0000000 --- a/server/main.py +++ /dev/null @@ -1,58 +0,0 @@ -from fastapi import FastAPI, Depends, Request -from fastapi.responses import RedirectResponse, HTMLResponse -from sqlalchemy.orm import Session -from sqlalchemy.future import select -from database import SessionLocal, User, Base, engine -from fastapi.templating import Jinja2Templates -from fastapi.staticfiles import StaticFiles - -from src.floris_router import router as floris_router -from src.floris_router import get_images - -Base.metadata.create_all(bind=engine) - -app = FastAPI() -templates = Jinja2Templates(directory="templates") - -app.mount("/static", StaticFiles(directory="static"), name="static") -app.mount("/public", StaticFiles(directory="public"), name="public") - -app.include_router(floris_router) - - -def get_db(): - db = SessionLocal() - try: - yield db - finally: - db.close() - - -@app.get("/hello") -async def hello(): - return {"message": "Hello, World!"} - - -@app.get("/") -async def redirect_to_docs(): - return RedirectResponse(url="/docs") - - -@app.get("/users") -def read_users(db: Session = Depends(get_db)): - result = db.execute(select(User)) - users = result.scalars().all() # Получаем всех пользователей - return users - - -@app.get("/main", response_class=HTMLResponse) -async def main(request: Request): - params = {"request": request} - params.update(await get_images()) - - return templates.TemplateResponse("main.html", params) - - -if __name__ == "__main__": - import uvicorn - uvicorn.run(app, host="localhost", port=8080) # Изменено на localhost diff --git a/server/public/floris/Yaw_example.png b/server/public/floris/Yaw_example.png deleted file mode 100644 index 7b4d7b5..0000000 Binary files a/server/public/floris/Yaw_example.png and /dev/null differ diff --git a/server/src/data/database.py b/server/src/data/database.py new file mode 100644 index 0000000..01711ae --- /dev/null +++ b/server/src/data/database.py @@ -0,0 +1,11 @@ +from sqlalchemy import create_engine +from sqlalchemy.orm import sessionmaker + +# TODO Maybe create env file +DATABASE_URL = "mysql+pymysql://wind:wind@193.124.203.110:3306/wind_towers" + +engine = create_engine(DATABASE_URL) +session_maker = sessionmaker(autocommit=False, autoflush=False, bind=engine) + + + diff --git a/server/database.py b/server/src/data/models.py similarity index 58% rename from server/database.py rename to server/src/data/models.py index c6f5c78..3d1aa26 100644 --- a/server/database.py +++ b/server/src/data/models.py @@ -1,28 +1,15 @@ -from sqlalchemy import create_engine, Column, Integer, String -from sqlalchemy.ext.declarative import declarative_base -from sqlalchemy.orm import sessionmaker, Mapped, mapped_column -from sqlalchemy.dialects.mysql import TIMESTAMP, TIME, VARCHAR - from datetime import datetime -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) +from sqlalchemy.orm import Mapped, mapped_column, DeclarativeBase +from sqlalchemy.dialects.mysql import TIMESTAMP, TIME, VARCHAR -Base = declarative_base() - - -class User(Base): - __tablename__ = "user" # Убедитесь, что эта таблица существует - - id = Column(Integer, primary_key=True, index=True) - name = Column(String(50), index=True) +class Base(DeclarativeBase): + pass class Weather(Base): - __tablename__ = "weather" + __tablename__ = "weather_data" id: Mapped[int] = mapped_column(primary_key=True) BarTrend: Mapped[str] = mapped_column(VARCHAR(255)) @@ -46,3 +33,4 @@ class Weather(Base): WindDir: Mapped[str] = mapped_column(VARCHAR(50)) WindSpeed: Mapped[float] WindSpeed10Min: Mapped[float] + diff --git a/server/src/data/repository.py b/server/src/data/repository.py new file mode 100644 index 0000000..2411229 --- /dev/null +++ b/server/src/data/repository.py @@ -0,0 +1,27 @@ +from sqlalchemy import select + +from .database import session_maker +from .models import Weather +from .schemas import SWeatherInfo + + +class WeatherRepository: + @classmethod + def get_all(cls): + with session_maker() as session: + query = ( + select(Weather) + ) + res = session.execute(query) + weather_models = res.scalars().all() + return [SWeatherInfo.model_validate(weather, from_attributes=True) for weather in weather_models] + + @classmethod + def get_by_id(cls, id): + with session_maker() as session: + query = ( + select(Weather).where(Weather.id == id) + ) + res = session.execute(query) + weather_model = res.scalars().first() + return SWeatherInfo.model_validate(weather_model, from_attributes=True) \ No newline at end of file diff --git a/server/src/data/schemas.py b/server/src/data/schemas.py new file mode 100644 index 0000000..3388350 --- /dev/null +++ b/server/src/data/schemas.py @@ -0,0 +1,19 @@ +from pydantic import BaseModel, Field +from fastapi import Query + + +class SWeatherInfo(BaseModel): + id: int + WindDir: str + WindSpeed: float + + +class SFlorisInputParams(BaseModel): + weather_id: int + layout_x: list[float] = Field(Query([])) + layout_y: list[float] = Field(Query([])) + + +class SFlorisOutputData(BaseModel): + file_name: str + data: list[float] \ No newline at end of file diff --git a/server/src/floris_router.py b/server/src/floris_router.py deleted file mode 100644 index 906d32d..0000000 --- a/server/src/floris_router.py +++ /dev/null @@ -1,36 +0,0 @@ -from pathlib import Path -from fastapi import APIRouter, Depends, HTTPException -from fastapi.responses import FileResponse - - -STATIC_DIR = Path("public/floris") - -router = APIRouter( - prefix="/floris", - tags=["Static Files from Floris"], -) - - -@router.get("/get_images") -async def get_images(): - try: - images = [file.name for file in STATIC_DIR.iterdir() if file.is_file()] - return {"images": images} - except Exception as e: - raise HTTPException( - status_code=500, - detail=str(e) - ) - - -@router.get("/download_image/{image_name}") -async def download_image( - image_name: str -): - image_path = STATIC_DIR / image_name - if not image_path.exists() or not image_path.is_file(): - raise HTTPException(status_code=404, detail="Image not found") - return FileResponse(image_path, media_type="image/jpeg", filename=image_name) - - - diff --git a/server/src/main.py b/server/src/main.py new file mode 100644 index 0000000..692fdde --- /dev/null +++ b/server/src/main.py @@ -0,0 +1,22 @@ +from pathlib import Path + +from fastapi import FastAPI + +from fastapi.staticfiles import StaticFiles + +from routers.floris_router import router as floris_router +from routers.floris_template_router import router as floris_template_router + + +app = FastAPI() + +app.mount("/static", StaticFiles(directory=Path("../static")), name="static") +app.mount("/public", StaticFiles(directory=Path("../public")), name="public") + +app.include_router(floris_router) +app.include_router(floris_template_router) + + +# if __name__ == "__main__": +# import uvicorn +# uvicorn.run(app, host="localhost", port=8080) # Изменено на localhost diff --git a/server/src/router.py b/server/src/router.py deleted file mode 100644 index 8013b0a..0000000 --- a/server/src/router.py +++ /dev/null @@ -1 +0,0 @@ -st \ No newline at end of file diff --git a/server/src/routers/floris_router.py b/server/src/routers/floris_router.py new file mode 100644 index 0000000..4e6a260 --- /dev/null +++ b/server/src/routers/floris_router.py @@ -0,0 +1,73 @@ +from http import HTTPStatus +from pathlib import Path +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 + +from FlorisULSTU import FlorisULSTU + +FLORIS_IMAGES_PATH = Path("public/floris") + +router = APIRouter( + prefix="/api/floris", + tags=["Floris Api"], +) + + +@router.get("/get_windmill_data") +async def get_windmill_data( + data: Annotated[SFlorisInputParams, Depends()] +): + if len(data.layout_x) != len(data.layout_y): + raise HTTPException( + status_code=HTTPStatus.BAD_REQUEST, + detail="Length of layout x and y must be the same", + ) + + atmosphere_param: SWeatherInfo = WeatherRepository().get_by_id(data.weather_id) + + fmodel = FlorisULSTU() + fmodel.set( + layout_x=data.layout_x, + layout_y=data.layout_y, + wind_directions=[atmosphere_param.WindDir], + wind_speeds=[atmosphere_param.WindSpeed], + ) + fmodel.run() + + data = fmodel.get_turbine_powers()[0].tolist() + file_name = fmodel.visualisation() + + return SFlorisOutputData( + file_name=file_name, + data=data + ) + + +@router.get("/get_images") +async def get_images(): + try: + images = [file.name for file in FLORIS_IMAGES_PATH.iterdir() if file.is_file()] + return {"images": images} + except Exception as e: + raise HTTPException( + status_code=500, + detail=str(e) + ) + + +@router.get("/download_image/{image_name}") +async def download_image( + image_name: str +): + image_path = FLORIS_IMAGES_PATH / image_name + if not image_path.exists() or not image_path.is_file(): + raise HTTPException(status_code=404, detail="Image not found") + return FileResponse(image_path, media_type="image/jpeg", filename=image_name) + + + diff --git a/server/src/routers/floris_template_router.py b/server/src/routers/floris_template_router.py new file mode 100644 index 0000000..1519ef8 --- /dev/null +++ b/server/src/routers/floris_template_router.py @@ -0,0 +1,65 @@ +from pathlib import Path +from typing import Annotated + +from fastapi import APIRouter, Request, Form, Depends +from fastapi.responses import HTMLResponse +from fastapi.templating import Jinja2Templates + +from data.repository import WeatherRepository +from data.schemas import SFlorisInputParams + +from routers.floris_router import get_windmill_data + +templates = Jinja2Templates(directory=Path("../templates/floris_templates")) + +router = APIRouter( + prefix="", + tags=["floris_template"], + include_in_schema=False, +) + + +@router.get("/", response_class=HTMLResponse) +async def get_main(request: Request, context: dict | None = None): + if context is None: + context = {} + + params = {"request": request, "weather": WeatherRepository.get_all()} + + if context is not None: + params.update(context) + + return templates.TemplateResponse("main.html", params) + + +def transform_windmill_data( + options: Annotated[str, Form()], + x: Annotated[str, Form()], + y: Annotated[str, Form()] +) -> SFlorisInputParams | None: + + if not all([options, x, y]): + return None + + return SFlorisInputParams( + weather_id=int(options), + layout_x=list(map(float, x.split())), + layout_y=list(map(float, y.split())), + ) + + +@router.post("/", response_class=HTMLResponse) +async def post_main( + request: Request, + windmill_params: Annotated[SFlorisInputParams | None, Depends(transform_windmill_data)] = None, +): + if windmill_params is None: + return await get_main(request) + + data = await get_windmill_data(windmill_params) + context = { + 'data': data.data, + 'file_name': data.file_name, + } + + return await get_main(request, context) diff --git a/server/templates/floris_templates/main.html b/server/templates/floris_templates/main.html new file mode 100644 index 0000000..9bc3c4b --- /dev/null +++ b/server/templates/floris_templates/main.html @@ -0,0 +1,85 @@ + + + + + + Image Viewer + + + + + + +
+
+
+
+
+ + + + + + + + + + + {% for row in weather %} + + + + + + {% endfor %} + +
#Wind DirectionWind Speed
+ + {{ row.WindDir }}{{ row.WindSpeed }}
+
+
+
+
+ X + +
+
+ Y + +
+
+
+ +
+
+ {{ data }} + {% if data is defined %} + Selected Image + {% endif %} +
+
+ + + + + + + diff --git a/server/templates/main.html b/server/templates/main.html deleted file mode 100644 index 01e50b9..0000000 --- a/server/templates/main.html +++ /dev/null @@ -1,43 +0,0 @@ - - - - - - Image Viewer - - - -
-
- Selected Image - - -
-
-

Available Images

- -
-
- - - -