floris_design #3

Merged
shadowik merged 10 commits from floris_design into main 2024-11-02 16:39:43 +04:00
30 changed files with 1173 additions and 66 deletions

View File

@ -1,8 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<content url="file://$MODULE_DIR$">
<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="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>
</module>

12
.idea/dataSources.xml Normal file
View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
<data-source source="LOCAL" name="Wind" uuid="401b0fa3-45f0-4ed2-9524-fd6acc19f73e">
<driver-ref>mariadb</driver-ref>
<synchronize>true</synchronize>
<jdbc-driver>org.mariadb.jdbc.Driver</jdbc-driver>
<jdbc-url>jdbc:mariadb://193.124.203.110:3306/wind_towers</jdbc-url>
<working-dir>$ProjectFileDir$</working-dir>
</data-source>
</component>
</project>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JavaScriptLibraryMappings">
<file url="file://$PROJECT_DIR$" libraries="{@popperjs/core, bootstrap}" />
</component>
</project>

View File

@ -1,4 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<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>

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>

14
.idea/webResources.xml Normal file
View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="WebResourcesPaths">
<contentEntries>
<entry url="file://$PROJECT_DIR$">
<entryData>
<resourceRoots>
<path value="file://$PROJECT_DIR$/server/src/static" />
</resourceRoots>
</entryData>
</entry>
</contentEntries>
</component>
</project>

View File

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

View File

@ -21,7 +21,12 @@
pip install -r requirements.txt
```
4. Start FastAPI process
4. Enter src directory
```zsh
fastapi dev main.py
cd src
```
5. Start FastAPI process
```zsh
uvicorn main:app --reload
```

View File

@ -1,21 +0,0 @@
from sqlalchemy import create_engine, Column, Integer, String
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
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()
class User(Base):
__tablename__ = "user" # Убедитесь, что эта таблица существует
id = Column(Integer, primary_key=True, index=True)
name = Column(String(50), index=True)

241
server/floris_module/.gitignore vendored Normal file
View File

@ -0,0 +1,241 @@
### JetBrains template
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/**/usage.statistics.xml
.idea/**/dictionaries
.idea/**/shelf
# AWS User-specific
.idea/**/aws.xml
# Generated files
.idea/**/contentModel.xml
# Sensitive or high-churn files
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
.idea/**/dbnavigator.xml
# Gradle
.idea/**/gradle.xml
.idea/**/libraries
# Gradle and Maven with auto-import
# When using Gradle or Maven with auto-import, you should exclude module files,
# since they will be recreated, and may cause churn. Uncomment if using
# auto-import.
# .idea/artifacts
# .idea/compiler.xml
# .idea/jarRepositories.xml
# .idea/modules.xml
# .idea/*.iml
# .idea/modules
# *.iml
# *.ipr
# CMake
cmake-build-*/
# Mongo Explorer plugin
.idea/**/mongoSettings.xml
# File-based project format
*.iws
# IntelliJ
out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Cursive Clojure plugin
.idea/replstate.xml
# SonarLint plugin
.idea/sonarlint/
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
# Editor-based Rest Client
.idea/httpRequests
# Android studio 3.1+ serialized cache file
.idea/caches/build_file_checksums.ser
### Python template
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock
# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
# in version control.
# https://pdm.fming.dev/#use-with-ide
.pdm.toml
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/

View File

@ -0,0 +1,46 @@
# Заметки по Floris
## Описание
Здесь я буду описывать общую информацию для всех,
чтобы остальным не копать глубоко как пользоваться библиотекой
## Инициализация и концепты библиотеки
1) Для работы Floris **требуется** специальный конфигурационный yaml файл.
В нем хранятся базовые данные о ветряках - их расположения, сила ветра и тому подобное.
Базовое его содержимое не так важно, так как впоследствии их можно будет изменить напрямую в процессе работы программы
(Буду постепенно искать интересующие нас параметры)
2) Для инициализации используется класс `FlorisModel(yaml_path)` в библиотеке Floris. Объявляем переменную этим классом,
и передаем в него путь к нашему Yaml файлу
[Пример](https://nrel.github.io/floris/intro_concepts.html#build-the-model)
3) Для изменения параметров заданных в yaml файле, используется метод set
[Пример](https://nrel.github.io/floris/intro_concepts.html#run-the-floris-wake-calculation)
4) Запуск симуляции происходит с помощью ```fmodel.run()```
[Пример](https://nrel.github.io/floris/intro_concepts.html#run-the-floris-wake-calculation)
5) Получаем мощность турбин с помощью ```fmodel.get_turbine_powers()``` в ваттах в виде массива Numpy.
[Пример](https://nrel.github.io/floris/intro_concepts.html#get-turbine-power)
6) Для каждого ветряка можно задать Yaw угол (Что такое Yaw угол показано на картинке)
[Пример](https://nrel.github.io/floris/intro_concepts.html#applying-yaw-angles)
![Yaw_example.png](docs/Yaw_example.png)
## Параметры
1) farm - Общие настройки расположения турбин (Кол-во элементов, задает кол-во ветряков. Можно поставить 1 элемент,
в таком случае данные у всех ветряков будут одинаковые)
1) layout_x - Список float координат турбин по оси X
2) layout_y - Список float координат турбин по оси Y
3) turbine_type - Список типов турбин
2) flow_field - Общие настройки атмосферных параметров (Кол-во элементов, задает кол-во экспериментов)
1) wind_directions - Список float Направление ветра в градусах (при этом север принимается за 0° или 360°,
восток за 90°, юг за 180°, а запад за 270°)
2) turbulence_intensities - Список float силы? ветряков
3) wind_speeds - Cписок скорости ветра (Наверное в м/с)

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

View File

@ -0,0 +1,22 @@
attrs==24.2.0
coloredlogs==15.0.1
contourpy==1.3.0
cycler==0.12.1
FLORIS==4.1.1
fonttools==4.54.1
humanfriendly==10.0
kiwisolver==1.4.7
matplotlib==3.9.2
numexpr==2.10.1
numpy==1.26.4
packaging==24.1
pandas==2.2.3
pillow==10.4.0
pyparsing==3.2.0
python-dateutil==2.9.0.post0
pytz==2024.2
PyYAML==6.0.2
scipy==1.14.1
shapely==2.0.6
six==1.16.0
tzdata==2024.2

View File

@ -0,0 +1,74 @@
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] = {
"N": 0.0,
"E": 90.0,
"S": 180.0,
"W": 270.0,
}
def _convert_winds_list_direction_definitions(
wind_directions: list[str],
) -> list[float]:
return list(map(lambda x: _convert_wind_direction_definition(x.upper()), wind_directions))
def _convert_wind_direction_definition(
wind_direction: str
) -> float:
if not _check_wind_direction_definition(wind_direction):
raise ValueError(f"Wind direction {wind_direction} is not valid")
return sum(map(lambda x: _wind_direction_to_val[x], wind_direction)) / len(wind_direction)
def _check_wind_direction_definition(wind_direction: str) -> bool:
if len(set(wind_direction) | set(_wind_direction_to_val)) > len(_wind_direction_to_val):
return False
return True
class FlorisULSTU(FlorisModel):
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 (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

View File

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

View File

@ -0,0 +1,244 @@
###
# A name for this input file.
# This is not currently only for the user's reference.
name: GCH
###
# A description of the contents of this input file.
# This is not currently only for the user's reference.
description: Three turbines using Gauss Curl Hybrid model
###
# The earliest verion of FLORIS this input file supports.
# This is not currently only for the user's reference.
floris_version: v4
###
# Configure the logging level and where to show the logs.
logging:
###
# Settings for logging to the console (i.e. terminal).
console:
###
# Can be "true" or "false".
enable: true
###
# Set the severity to show output. Messages at this level or higher will be shown.
# Can be one of "CRITICAL", "ERROR", "WARNING", "INFO", "DEBUG".
level: WARNING
###
# Settings for logging to a file.
file:
###
# Can be "true" or "false".
enable: false
###
# Set the severity to show output. Messages at this level or higher will be shown.
# Can be one of "CRITICAL", "ERROR", "WARNING", "INFO", "DEBUG".
level: WARNING
###
# Configure the solver for the type of simulation.
solver:
###
# Select the solver type.
# Can be one of: "turbine_grid", "flow_field_grid", "flow_field_planar_grid".
type: turbine_grid
###
# Options for the turbine type selected above. See the solver documentation for available parameters.
turbine_grid_points: 3
###
# Configure the turbine types and their placement within the wind farm.
farm:
###
# Coordinates for the turbine locations in the x-direction which is typically considered
# to be the streamwise direction (left, right) when the wind is out of the west.
# The order of the coordinates here corresponds to the index of the turbine in the primary
# data structures.
layout_x:
- 0.0
- 630.0
- 1260.0
###
# Coordinates for the turbine locations in the y-direction which is typically considered
# to be the spanwise direction (up, down) when the wind is out of the west.
# The order of the coordinates here corresponds to the index of the turbine in the primary
# data structures.
layout_y:
- 0.0
- 0.0
- 0.0
###
# Listing of turbine types for placement at the x and y coordinates given above.
# The list length must be 1 or the same as ``layout_x`` and ``layout_y``. If it is a
# single value, all turbines are of the same type. Otherwise, the turbine type
# is mapped to the location at the same index in ``layout_x`` and ``layout_y``.
# The types can be either a name included in the turbine_library or
# a full definition of a wind turbine directly.
turbine_type:
- nrel_5MW
###
# Configure the atmospheric conditions.
flow_field:
###
# Air density.
air_density: 1.225
###
# The height to consider the "center" of the vertical wind speed profile
# due to shear. With a shear exponent not 1, the wind speed at this height
# will be the value given in ``wind_speeds``. Above and below this height,
# the wind speed will change according to the shear profile; see
# :py:meth:`.FlowField.initialize_velocity_field`.
# For farms consisting of one wind turbine type, use ``reference_wind_height: -1``
# to use the hub height of the wind turbine definition. For multiple wind turbine
# types, the reference wind height must be given explicitly.
reference_wind_height: -1
###
# The turbulence intensities to include in the simulation, specified as a decimal.
turbulence_intensities:
- 0.06
###
# The wind directions to include in the simulation.
# 0 is north and 270 is west.
wind_directions:
- 270.0
###
# The exponent used to model the wind shear profile; see
# :py:meth:`.FlowField.initialize_velocity_field`.
wind_shear: 0.12
###
# The wind speeds to include in the simulation.
wind_speeds:
- 8.0
###
# The wind veer as a constant value for all points in the grid.
wind_veer: 0.0
###
# The conditions that are specified for use with the multi-dimensional Cp/Ct capbility.
# These conditions are external to FLORIS and specified by the user. They are used internally
# through a nearest-neighbor selection process to choose the correct Cp/Ct interpolants
# to use.
multidim_conditions:
Tp: 2.5
Hs: 3.01
###
# Configure the wake model.
wake:
###
# Select the data to use for the simulation.
# See :py:mod:`~.wake` for a list
# of available data and their descriptions.
model_strings:
###
# Select the wake combination model.
combination_model: sosfs
###
# Select the wake deflection model.
deflection_model: gauss
###
# Select the wake turbulence model.
turbulence_model: crespo_hernandez
###
# Select the wake velocity deficit model.
velocity_model: gauss
###
# Can be "true" or "false".
enable_secondary_steering: true
###
# Can be "true" or "false".
enable_yaw_added_recovery: true
###
# Can be "true" or "false".
enable_active_wake_mixing: false
###
# Can be "true" or "false".
enable_transverse_velocities: true
###
# Configure the parameters for the wake deflection model
# selected above.
# Additional blocks can be provided for
# data that are not enabled, but the enabled model
# must have a corresponding parameter block.
wake_deflection_parameters:
gauss:
ad: 0.0
alpha: 0.58
bd: 0.0
beta: 0.077
dm: 1.0
ka: 0.38
kb: 0.004
jimenez:
ad: 0.0
bd: 0.0
kd: 0.05
###
# Configure the parameters for the wake velocity deficit model
# selected above.
# Additional blocks can be provided for
# data that are not enabled, but the enabled model
# must have a corresponding parameter block.
wake_velocity_parameters:
cc:
a_s: 0.179367259
b_s: 0.0118889215
c_s1: 0.0563691592
c_s2: 0.13290157
a_f: 3.11
b_f: -0.68
c_f: 2.41
alpha_mod: 1.0
gauss:
alpha: 0.58
beta: 0.077
ka: 0.38
kb: 0.004
jensen:
we: 0.05
###
# Configure the parameters for the wake turbulence model
# selected above.
# Additional blocks can be provided for
# data that are not enabled, but the enabled model
# must have a corresponding parameter block.
wake_turbulence_parameters:
crespo_hernandez:
initial: 0.1
constant: 0.5
ai: 0.8
downstream: -0.32

View File

@ -0,0 +1,55 @@
import numpy as np
import matplotlib.pyplot as plt
from FlorisULSTU import FlorisULSTU
# yaw_angles = np.zeros((4, 4))
# print("Yaw angle array initialized with 0's")
# print(yaw_angles)
#
# print("First turbine yawed 25 degrees for every atmospheric condition")
# yaw_angles[:, 0] = 25
# print(yaw_angles)
#
# fmodel.set(yaw_angles=yaw_angles)
if __name__ == "__main__":
fmodel = FlorisULSTU("gch.yaml")
layout_x = list(map(float, input(f"Please enter x coordinates for turbines\n").split()))
layout_y = list(map(float, input(f"Please enter y coordinates for turbines\n").split()))
fmodel.set(
layout_x=layout_x, layout_y=layout_y,
)
wind_directions = list(map(float, input(f"Please enter wind_directions for experiments\n").split()))
wind_speeds = list(map(float, input(f"Please enter wind_speed for experiments\n").split()))
fmodel.set(
wind_directions=wind_directions, wind_speeds=wind_speeds, turbulence_intensities=[0.1] * len(wind_directions),
)
fmodel.run()
powers = fmodel.get_turbine_powers() / 1000.0
print("Dimensions of `powers`")
print(np.shape(powers))
N_TURBINES = fmodel.core.farm.n_turbines
print()
print("Turbine powers for 8 m/s")
for i in range(2):
print(f"Wind condition {i}")
for j in range(N_TURBINES):
print(f" Turbine {j} - {powers[i, j]:7,.2f} kW")
print()
print("Turbine powers for all turbines at all wind conditions")
print(powers)

View File

@ -1,38 +0,0 @@
from fastapi import FastAPI, Depends
from fastapi.responses import RedirectResponse
from sqlalchemy.orm import Session
from sqlalchemy.future import select
from database import SessionLocal, User, Base, engine
Base.metadata.create_all(bind=engine)
app = FastAPI()
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
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="localhost", port=8080) # Изменено на localhost

View File

@ -24,6 +24,7 @@ PyYAML==6.0.2
rich==13.8.1
shellingham==1.5.4
sniffio==1.3.1
SQLAlchemy==2.0.35
starlette==0.38.6
typer==0.12.5
typing_extensions==4.12.2

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)

36
server/src/data/models.py Normal file
View File

@ -0,0 +1,36 @@
from datetime import datetime
from sqlalchemy.orm import Mapped, mapped_column, DeclarativeBase
from sqlalchemy.dialects.mysql import TIMESTAMP, TIME, VARCHAR
class Base(DeclarativeBase):
pass
class Weather(Base):
__tablename__ = "weather_data"
id: Mapped[int] = mapped_column(primary_key=True)
BarTrend: Mapped[str] = mapped_column(VARCHAR(255))
CRC: Mapped[str] = mapped_column(VARCHAR(255))
DateStamp: Mapped[datetime] = mapped_column(TIMESTAMP)
DewPoint: Mapped[float]
HeatIndex: Mapped[float]
ETDay: Mapped[float]
HumIn: Mapped[float]
HumOut: Mapped[float]
Pressure: Mapped[float]
RainDay: Mapped[float]
RainMonth: Mapped[float]
RainRate: Mapped[float]
RainStorm: Mapped[float]
RainYear: Mapped[float]
SunRise: Mapped[datetime] = mapped_column(TIME)
SunSet:Mapped[datetime] = mapped_column(TIME)
TempIn: Mapped[float]
TempOut: Mapped[float]
WindDir: Mapped[str] = mapped_column(VARCHAR(50))
WindSpeed: Mapped[float]
WindSpeed10Min: Mapped[float]

View File

@ -0,0 +1,27 @@
from sqlalchemy import select
from data.database import session_maker
from data.models import Weather
from data.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]

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

@ -0,0 +1,21 @@
import sys
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
from routers.weather_router import router as weather_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)
app.include_router(weather_router)

View File

@ -0,0 +1,76 @@
import sys
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
sys.path.append(str(Path(__file__).parent.parent.parent))
from floris_module.src import FlorisULSTU
FLORIS_IMAGES_PATH = Path(__file__).parent.parent.parent / "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,13 @@
from fastapi import APIRouter
from data.repository import WeatherRepository
router = APIRouter(
prefix="/api/weather",
tags=["Weather Api"],
)
@router.get("/")
async def get_weather():
return WeatherRepository().get_all()

67
server/static/style.css Normal file
View File

@ -0,0 +1,67 @@
body {
font-family: Arial, sans-serif;
background-color: #f3f4f6;
color: #333;
}
.container {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
padding: 20px;
}
.image-viewer {
background-color: #fff5e6;
border-radius: 8px;
padding: 20px;
margin-right: 20px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}
.image-viewer img {
max-width: 400px;
max-height: 400px;
display: block;
margin-bottom: 10px;
}
.image-list {
background-color: #e6f7ff;
padding: 20px;
border-radius: 8px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}
.image-list ul {
list-style-type: none;
padding: 0;
}
.image-list li {
margin: 5px 0;
padding: 5px;
cursor: pointer;
background-color: #fff;
border-radius: 4px;
transition: background-color 0.3s;
}
.image-list li:hover {
background-color: #cceeff;
}
button {
background-color: #82b1ff;
border: none;
color: white;
padding: 10px 15px;
border-radius: 5px;
cursor: pointer;
margin-top: 10px;
}
button:hover {
background-color: #6699ff;
}

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>