Change architecture, Main functionality, Little practise HTML template

This commit is contained in:
shadowik 2024-10-16 01:05:48 +04:00
parent 20379f8d37
commit 6e8ec4f0f1
26 changed files with 376 additions and 167 deletions

View File

@ -1,8 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4"> <module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager"> <component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" /> <content url="file://$MODULE_DIR$">
<orderEntry type="inheritedJdk" /> <sourceFolder url="file://$MODULE_DIR$/server/floris_module/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/server/src" isTestSource="false" />
<excludeFolder url="file://$MODULE_DIR$/server/floris_module/.venv" />
</content>
<orderEntry type="jdk" jdkName="Python 3.12 (EvaluationEfficiencyOptimizationWind)" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" /> <orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="bootstrap" level="application" />
<orderEntry type="library" name="@popperjs/core" level="application" />
</component>
<component name="TemplatesService">
<option name="TEMPLATE_CONFIGURATION" value="Jinja2" />
<option name="TEMPLATE_FOLDERS">
<list>
<option value="$MODULE_DIR$/server/src/templates" />
</list>
</option>
</component> </component>
</module> </module>

View File

@ -1,4 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.9" project-jdk-type="Python SDK" /> <component name="Black">
<option name="sdkName" value="Python 3.12 (EvaluationEfficiencyOptimizationWind) (2)" />
</component>
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.12 (EvaluationEfficiencyOptimizationWind)" project-jdk-type="Python SDK" />
</project> </project>

6
.idea/other.xml Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="PySciProjectComponent">
<option name="PY_INTERACTIVE_PLOTS_SUGGESTED" value="true" />
</component>
</project>

View File

@ -5,7 +5,7 @@ import TsconfigPathsPlugin from 'tsconfig-paths-webpack-plugin';
const __dirname = path.resolve(); const __dirname = path.resolve();
const config = { const config = {
entry: './src/index.tsx', entry: './routers/index.tsx',
output: { output: {
path: path.resolve(__dirname, 'dist'), path: path.resolve(__dirname, 'dist'),
filename: 'main.js', filename: 'main.js',

View File

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

View File

@ -1,4 +1,14 @@
import os.path
import sys
import uuid
from pathlib import Path
from floris import FlorisModel 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] = { _wind_direction_to_val: dict[str, float] = {
@ -31,13 +41,34 @@ def _check_wind_direction_definition(wind_direction: str) -> bool:
class FlorisULSTU(FlorisModel): class FlorisULSTU(FlorisModel):
def __init__(self, url_yaml): def __init__(
self,
url_yaml: Path = Path(__file__).parent / "gch.yaml"
):
super().__init__(url_yaml) super().__init__(url_yaml)
def set(self, **kwargs): 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"]) kwargs["wind_directions"] = _convert_winds_list_direction_definitions(kwargs["wind_directions"])
super().set(**kwargs) 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

View File

@ -0,0 +1,3 @@
from .FlorisULSTU import FlorisULSTU
__all__ = ['FlorisULSTU']

View File

@ -149,9 +149,9 @@ flow_field:
wake: wake:
### ###
# Select the models to use for the simulation. # Select the data to use for the simulation.
# See :py:mod:`~.wake` for a list # See :py:mod:`~.wake` for a list
# of available models and their descriptions. # of available data and their descriptions.
model_strings: model_strings:
### ###
@ -190,7 +190,7 @@ wake:
# Configure the parameters for the wake deflection model # Configure the parameters for the wake deflection model
# selected above. # selected above.
# Additional blocks can be provided for # 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. # must have a corresponding parameter block.
wake_deflection_parameters: wake_deflection_parameters:
gauss: gauss:
@ -210,7 +210,7 @@ wake:
# Configure the parameters for the wake velocity deficit model # Configure the parameters for the wake velocity deficit model
# selected above. # selected above.
# Additional blocks can be provided for # 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. # must have a corresponding parameter block.
wake_velocity_parameters: wake_velocity_parameters:
cc: cc:
@ -234,7 +234,7 @@ wake:
# Configure the parameters for the wake turbulence model # Configure the parameters for the wake turbulence model
# selected above. # selected above.
# Additional blocks can be provided for # 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. # must have a corresponding parameter block.
wake_turbulence_parameters: wake_turbulence_parameters:
crespo_hernandez: crespo_hernandez:

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

View File

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

View File

@ -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 from datetime import datetime
DATABASE_URL = "mysql+pymysql://wind:wind@193.124.203.110:3306/wind_towers" from sqlalchemy.orm import Mapped, mapped_column, DeclarativeBase
from sqlalchemy.dialects.mysql import TIMESTAMP, TIME, VARCHAR
engine = create_engine(DATABASE_URL)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base() class Base(DeclarativeBase):
pass
class User(Base):
__tablename__ = "user" # Убедитесь, что эта таблица существует
id = Column(Integer, primary_key=True, index=True)
name = Column(String(50), index=True)
class Weather(Base): class Weather(Base):
__tablename__ = "weather" __tablename__ = "weather_data"
id: Mapped[int] = mapped_column(primary_key=True) id: Mapped[int] = mapped_column(primary_key=True)
BarTrend: Mapped[str] = mapped_column(VARCHAR(255)) BarTrend: Mapped[str] = mapped_column(VARCHAR(255))
@ -46,3 +33,4 @@ class Weather(Base):
WindDir: Mapped[str] = mapped_column(VARCHAR(50)) WindDir: Mapped[str] = mapped_column(VARCHAR(50))
WindSpeed: Mapped[float] WindSpeed: Mapped[float]
WindSpeed10Min: Mapped[float] WindSpeed10Min: Mapped[float]

View File

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

View File

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

View File

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

22
server/src/main.py Normal file
View File

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

View File

@ -1 +0,0 @@
st

View File

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

View File

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

View File

@ -0,0 +1,85 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Image Viewer</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/4.6.1/css/bootstrap.min.css" integrity="sha512-T584yQ/tdRR5QwOpfvDfVQUidzfgc2339Lc8uBDtcp/wYu80d7jwBgAxbyMh0a9YM9F8N3tdErpFI8iaGx6x5g==" crossorigin="anonymous" referrerpolicy="no-referrer">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/4.6.1/js/bootstrap.min.js" integrity="sha512-UR25UO94eTnCVwjbXozyeVd6ZqpaAE9naiEUBK/A+QDbfSTQFhPGj5lOR6d8tsgbBk84Ggb5A3EkjsOgPRPcKA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
</head>
<body>
<div class="row m-5">
<form class="col" method="post">
<div class="row">
<div class="col">
<div class="btn-group" data-toggle="buttons">
<table class="table">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">Wind Direction</th>
<th scope="col">Wind Speed</th>
</tr>
</thead>
<tbody>
{% for row in weather %}
<tr>
<th>
<label class="btn btn-outline-primary">{{ row.id }}
<input type="radio" class="btn-check"
name="options" id="option_{{ row.id }}" value="{{ row.id }}" autocomplete="off">
</label>
</th>
<td>{{ row.WindDir }}</td>
<td>{{ row.WindSpeed }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<div class="col">
<div class="input-group flex-nowrap">
<span class="input-group-text" id="addon-wrapping">X</span>
<input name="x" type="text" class="form-control" placeholder="X axis" aria-label="X axis" aria-describedby="addon-wrapping">
</div>
<div class="input-group flex-nowrap">
<span class="input-group-text" id="addon-wrapping">Y</span>
<input name="y" type="text" class="form-control" placeholder="Y axis" aria-label="Y axis" aria-describedby="addon-wrapping">
</div>
</div>
</div>
<button type="submit" class="btn btn-primary w-100">Submit</button>
</form>
<div class="col">
{{ data }}
{% if data is defined %}
<img id="display-image" src="/public/floris/{{ file_name }}" alt="Selected Image">
{% endif %}
</div>
</div>
<script>
function selectImage(imageName) {
const imgElement = document.getElementById('display-image');
imgElement.src = `/public/floris/${imageName}`;
}
function generateImage() {
alert("Image generation functionality not implemented yet.");
}
function downloadImage() {
const imgElement = document.getElementById('display-image');
const imageName = imgElement.src.split('/').pop();
window.location.href = `/floris/download_image/${imageName}`;
}
</script>
</body>
</html>

View File

@ -1,43 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Image Viewer</title>
<link rel="stylesheet" href="/static/style.css">
</head>
<body>
<div class="container">
<div class="image-viewer">
<img id="display-image" src="/public/floris/Yaw_example.png" alt="Selected Image">
<button onclick="generateImage()">Generate Image</button>
<button onclick="downloadImage()">Download Image</button>
</div>
<div class="image-list">
<h3>Available Images</h3>
<ul id="image-list">
{% for image in images %}
<li onclick="selectImage('{{ image }}')">{{ image }}</li>
{% endfor %}
</ul>
</div>
</div>
<script>
function selectImage(imageName) {
const imgElement = document.getElementById('display-image');
imgElement.src = `/public/floris/${imageName}`;
}
function generateImage() {
alert("Image generation functionality not implemented yet.");
}
function downloadImage() {
const imgElement = document.getElementById('display-image');
const imageName = imgElement.src.split('/').pop();
window.location.href = `/floris/download_image/${imageName}`;
}
</script>
</body>
</html>