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/dataSources.xml b/.idea/dataSources.xml new file mode 100644 index 0000000..7b6529f --- /dev/null +++ b/.idea/dataSources.xml @@ -0,0 +1,12 @@ + + + + + mariadb + true + org.mariadb.jdbc.Driver + jdbc:mariadb://193.124.203.110:3306/wind_towers + $ProjectFileDir$ + + + \ No newline at end of file diff --git a/.idea/jsLibraryMappings.xml b/.idea/jsLibraryMappings.xml new file mode 100644 index 0000000..6fa30af --- /dev/null +++ b/.idea/jsLibraryMappings.xml @@ -0,0 +1,6 @@ + + + + + + \ 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/.idea/webResources.xml b/.idea/webResources.xml new file mode 100644 index 0000000..913f4b6 --- /dev/null +++ b/.idea/webResources.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file 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/server/README.md b/server/README.md index 5e79c49..f69b485 100644 --- a/server/README.md +++ b/server/README.md @@ -20,8 +20,13 @@ ```zsh 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 ``` \ No newline at end of file diff --git a/server/database.py b/server/database.py deleted file mode 100644 index 2776e93..0000000 --- a/server/database.py +++ /dev/null @@ -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) diff --git a/server/floris_module/.gitignore b/server/floris_module/.gitignore new file mode 100644 index 0000000..2d62eb2 --- /dev/null +++ b/server/floris_module/.gitignore @@ -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/ + diff --git a/server/floris_module/NOTES.md b/server/floris_module/NOTES.md new file mode 100644 index 0000000..9a03ec4 --- /dev/null +++ b/server/floris_module/NOTES.md @@ -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писок скорости ветра (Наверное в м/с) \ No newline at end of file diff --git a/server/floris_module/docs/Yaw_example.png b/server/floris_module/docs/Yaw_example.png new file mode 100644 index 0000000..7b4d7b5 Binary files /dev/null and b/server/floris_module/docs/Yaw_example.png differ diff --git a/server/floris_module/requirements.txt b/server/floris_module/requirements.txt new file mode 100644 index 0000000..6a93f4f --- /dev/null +++ b/server/floris_module/requirements.txt @@ -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 diff --git a/server/floris_module/src/FlorisULSTU.py b/server/floris_module/src/FlorisULSTU.py new file mode 100644 index 0000000..d0971ac --- /dev/null +++ b/server/floris_module/src/FlorisULSTU.py @@ -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 + + + 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/server/floris_module/src/gch.yaml b/server/floris_module/src/gch.yaml new file mode 100644 index 0000000..01d727d --- /dev/null +++ b/server/floris_module/src/gch.yaml @@ -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 diff --git a/server/floris_module/src/main.py b/server/floris_module/src/main.py new file mode 100644 index 0000000..51d8e1e --- /dev/null +++ b/server/floris_module/src/main.py @@ -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) + diff --git a/server/main.py b/server/main.py deleted file mode 100644 index afca163..0000000 --- a/server/main.py +++ /dev/null @@ -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 diff --git a/server/src/router.py b/server/public/floris/git_init similarity index 100% rename from server/src/router.py rename to server/public/floris/git_init diff --git a/server/requirements.txt b/server/requirements.txt index 5d438d4..8986ff1 100644 --- a/server/requirements.txt +++ b/server/requirements.txt @@ -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 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/src/data/models.py b/server/src/data/models.py new file mode 100644 index 0000000..3d1aa26 --- /dev/null +++ b/server/src/data/models.py @@ -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] + diff --git a/server/src/data/repository.py b/server/src/data/repository.py new file mode 100644 index 0000000..b9fbc39 --- /dev/null +++ b/server/src/data/repository.py @@ -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) \ 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/main.py b/server/src/main.py new file mode 100644 index 0000000..a4a7788 --- /dev/null +++ b/server/src/main.py @@ -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) diff --git a/server/src/routers/floris_router.py b/server/src/routers/floris_router.py new file mode 100644 index 0000000..7bbca30 --- /dev/null +++ b/server/src/routers/floris_router.py @@ -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) + + + 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/src/routers/weather_router.py b/server/src/routers/weather_router.py new file mode 100644 index 0000000..01703fe --- /dev/null +++ b/server/src/routers/weather_router.py @@ -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() + diff --git a/server/static/style.css b/server/static/style.css new file mode 100644 index 0000000..23851ec --- /dev/null +++ b/server/static/style.css @@ -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; +} 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 %} +
+
+ + + + + + +