Compare commits

..

No commits in common. "front-2-fix-gen" and "main" have entirely different histories.

167 changed files with 76 additions and 1239 deletions

View File

@ -1,10 +1,11 @@
from PyWeather.weather.stations.davis import VantagePro
import logging
import time
import mariadb
import serial.tools.list_ports
import gc
import time
from pprint import pprint
from PyWeather.weather.stations.davis import VantagePro
logging.basicConfig(filename="Stations.log",
format='%(asctime)s %(message)s',
@ -12,10 +13,37 @@ logging.basicConfig(filename="Stations.log",
logger = logging.getLogger('davis_api')
logger.setLevel(logging.DEBUG)
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.DEBUG)
console_handler.setFormatter(logging.Formatter('%(asctime)s %(message)s'))
logger.addHandler(console_handler)
def write_data(device, station, send=True):
try:
#device.parse()
data = device.fields
print(data)
if len(data) < 1:
return
else:
print(data)
fields = ['BarTrend', 'CRC', 'DateStamp', 'DewPoint', 'HeatIndex', 'ETDay', 'HeatIndex',
'HumIn', 'HumOut', 'Pressure', 'RainDay', 'RainMonth', 'RainRate', 'RainStorm',
'RainYear', 'SunRise', 'SunSet', 'TempIn', 'TempOut', 'WindDir', 'WindSpeed',
'WindSpeed10Min']
if send:
placeholders = ', '.join(['%s'] * len(fields))
field_names = ', '.join(fields)
sql = f"INSERT INTO weather_data ({field_names}) VALUES ({placeholders})"
values = [data[field] for field in fields]
cursor.execute(sql, values)
conn.commit()
else:
pprint(data)
del data
del fields
gc.collect()
except Exception as e:
logger.error(str(e))
raise e
try:
conn = mariadb.connect(
@ -29,25 +57,23 @@ try:
except mariadb.Error as e:
logger.error('DB_ERR: ' + str(e))
raise e
while True:
try:
ports = serial.tools.list_ports.comports()
available_ports = {}
for port in ports:
if port.serial_number == '0001':
available_ports[port.name] = port.vid
try:
ports = serial.tools.list_ports.comports()
available_ports = {}
devices = [VantagePro(port) for port in available_ports.keys()]
while True:
for i in range(1):
if len(devices) != 0:
logger.info(devices)
else:
raise Exception('Can`t connect to device')
time.sleep(60)
except Exception as e:
logger.error('Device_error' + str(e))
time.sleep(60)
for port in ports:
if port.serial_number == '0001':
available_ports[port.name] = port.vid
# todo переписать под influx, для линухи приколы сделать
devices = [VantagePro(port) for port in available_ports.keys()]
print(available_ports)
while True:
for i in range(len(devices)):
print(devices[i].fields)
#write_data(devices[i], 'st' + str(available_ports[list(available_ports.keys())[i]]), True)
time.sleep(1)
except Exception as e:
logger.error('Device_error: ' + str(e))
raise e

View File

@ -1,200 +0,0 @@
from datetime import datetime
from pathlib import Path
import metpy.calc
import numpy as np
import requests
import torch
import xarray as xr
from aurora import AuroraSmall, Batch, Metadata
from metpy.units import units
def get_download_paths(date):
"""Создает список путей для загрузки данных."""
download_path = Path("~/downloads/hres_0.1").expanduser()
downloads = {}
var_nums = {
"2t": "167", "10u": "165", "10v": "166", "msl": "151", "t": "130",
"u": "131", "v": "132", "q": "133", "z": "129", "slt": "043", "lsm": "172",
}
for v in ["2t", "10u", "10v", "msl", "z", "slt", "lsm"]:
downloads[download_path / date.strftime(f"surf_{v}_%Y-%m-%d.grib")] = (
f"https://data.rda.ucar.edu/ds113.1/"
f"ec.oper.an.sfc/{date.year}{date.month:02d}/ec.oper.an.sfc.128_{var_nums[v]}_{v}."
f"regn1280sc.{date.year}{date.month:02d}{date.day:02d}.grb"
)
for v in ["z", "t", "u", "v", "q"]:
for hour in [0, 6, 12, 18]:
prefix = "uv" if v in {"u", "v"} else "sc"
downloads[download_path / date.strftime(f"atmos_{v}_%Y-%m-%d_{hour:02d}.grib")] = (
f"https://data.rda.ucar.edu/ds113.1/"
f"ec.oper.an.pl/{date.year}{date.month:02d}/ec.oper.an.pl.128_{var_nums[v]}_{v}."
f"regn1280{prefix}.{date.year}{date.month:02d}{date.day:02d}{hour:02d}.grb"
)
return downloads, download_path
def download_data(downloads):
"""Скачивает файлы, если они отсутствуют в целевой директории."""
for target, source in downloads.items():
if not target.exists():
print(f"Downloading {source}")
target.parent.mkdir(parents=True, exist_ok=True)
response = requests.get(source)
response.raise_for_status()
with open(target, "wb") as f:
f.write(response.content)
print("Downloads finished!")
def load_surf(v, v_in_file, download_path, date):
"""Загружает переменные поверхностного уровня или статические переменные."""
ds = xr.open_dataset(download_path / date.strftime(f"surf_{v}_%Y-%m-%d.grib"), engine="cfgrib")
data = ds[v_in_file].values[:2]
data = data[None]
return torch.from_numpy(data)
def load_atmos(v, download_path, date, levels):
"""Загружает атмосферные переменные для заданных уровней давления."""
ds_00 = xr.open_dataset(
download_path / date.strftime(f"atmos_{v}_%Y-%m-%d_00.grib"), engine="cfgrib"
)
ds_06 = xr.open_dataset(
download_path / date.strftime(f"atmos_{v}_%Y-%m-%d_06.grib"), engine="cfgrib"
)
ds_00 = ds_00[v].sel(isobaricInhPa=list(levels))
ds_06 = ds_06[v].sel(isobaricInhPa=list(levels))
data = np.stack((ds_00.values, ds_06.values), axis=0)
data = data[None]
return torch.from_numpy(data)
def create_batch(date, levels, downloads, download_path):
"""Создает объект Batch с данными для модели."""
ds = xr.open_dataset(next(iter(downloads.keys())), engine="cfgrib")
batch = Batch(
surf_vars={
"2t": load_surf("2t", "t2m", download_path, date),
"10u": load_surf("10u", "u10", download_path, date),
"10v": load_surf("10v", "v10", download_path, date),
"msl": load_surf("msl", "msl", download_path, date),
},
static_vars={
"z": load_surf("z", "z", download_path, date)[0, 0],
"slt": load_surf("slt", "slt", download_path, date)[0, 0],
"lsm": load_surf("lsm", "lsm", download_path, date)[0, 0],
},
atmos_vars={
"t": load_atmos("t", download_path, date, levels),
"u": load_atmos("u", download_path, date, levels),
"v": load_atmos("v", download_path, date, levels),
"q": load_atmos("q", download_path, date, levels),
"z": load_atmos("z", download_path, date, levels),
},
metadata=Metadata(
lat=torch.from_numpy(ds.latitude.values),
lon=torch.from_numpy(ds.longitude.values),
time=(date.replace(hour=6),),
atmos_levels=levels,
),
)
return batch.regrid(res=0.1)
def create_batch_random(levels: tuple[int], date: tuple):
"""Создает объект Batch с рандомными данными для модели."""
return Batch(
surf_vars={k: torch.randn(1, 2, 17, 32) for k in ("2t", "10u", "10v", "msl")},
static_vars={k: torch.randn(17, 32) for k in ("lsm", "z", "slt")},
atmos_vars={k: torch.randn(1, 2, 4, 17, 32) for k in ("z", "u", "v", "t", "q")},
metadata=Metadata(
lat=torch.linspace(90, -90, 17),
lon=torch.linspace(0, 360, 32 + 1)[:-1],
time=date,
atmos_levels=levels,
),
)
def run_model(batch):
"""Инициализирует модель AuroraSmall и выполняет предсказание."""
model = AuroraSmall()
model.load_checkpoint("microsoft/aurora", "aurora-0.25-small-pretrained.ckpt")
model.eval()
model = model.to("cpu")
with torch.inference_mode():
prediction = model.forward(batch)
return prediction
def get_wind_speed_and_direction(prediction, batch: Batch, lat: float, lon: float):
target_lat = lat
target_lon = lon
lat_idx = torch.abs(batch.metadata.lat - target_lat).argmin()
lon_idx = torch.abs(batch.metadata.lon - target_lon).argmin()
u_values = prediction.atmos_vars["u"][:, :, :, lat_idx, lon_idx]
v_values = prediction.atmos_vars["v"][:, :, :, lat_idx, lon_idx]
wind_speeds=[]
wind_directions=[]
for i in range(u_values.numel()):
u_scalar = u_values.view(-1)[i].item() # Разворачиваем тензор в одномерный и берем элемент
v_scalar = v_values.view(-1)[i].item()
print("u value:", u_scalar)
print("v value:", v_scalar)
u_with_units = u_scalar * units("m/s")
v_with_units = v_scalar * units("m/s")
# Рассчитайте направление и скорость ветра
wind_dir = metpy.calc.wind_direction(u_with_units, v_with_units)
wind_speed = metpy.calc.wind_speed(u_with_units, v_with_units)
wind_speeds.append(wind_speed.magnitude.item())
wind_directions.append(wind_dir.magnitude.item())
return wind_speeds,wind_directions
def wind_direction_to_text(wind_dir_deg):
directions = [
"север", "северо-восток", "восток", "юго-восток",
"юг", "юго-запад", "запад", "северо-запад"
]
idx = int((wind_dir_deg + 22.5) // 45) % 8
return directions[idx]
def get_weather_predict(
dates: tuple[datetime],
latitude: float,
longitude: float,
):
levels = (100,)
batch_actual = create_batch_random(levels, dates)
prediction_actual = run_model(batch_actual)
return get_wind_speed_and_direction(prediction_actual, batch_actual, latitude, longitude)
def main():
levels = (100,)
date1 = datetime(2024, 11, 27, 12)
date2 = datetime(2024, 11, 28, 12)
date_tuple = (date1, date2,)
# downloads, download_path = get_download_paths(date)
# download_data(downloads) # Скачиваем данные, если их нет
# batch_actual = create_batch(date, levels, downloads, download_path)
batch_actual = create_batch_random(levels, date_tuple)
prediction_actual = run_model(batch_actual)
wind_speed_and_direction = get_wind_speed_and_direction(prediction_actual, batch_actual, 50, 20)
return wind_speed_and_direction
if __name__ == "__main__":
main()
print("Prediction completed!")

View File

@ -1,7 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 26.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 13 13" style="enable-background:new 0 0 13 13;" xml:space="preserve">
<path d="M9.5,6H7V3.5C7,3.22,6.78,3,6.5,3S6,3.22,6,3.5V6H3.5C3.22,6,3,6.22,3,6.5S3.22,7,3.5,7H6v2.5C6,9.78,6.22,10,6.5,10
S7,9.78,7,9.5V7h2.5C9.78,7,10,6.78,10,6.5S9.78,6,9.5,6z"/>
</svg>

Before

Width:  |  Height:  |  Size: 541 B

View File

@ -1,16 +0,0 @@
import { FlorisPlot } from './types';
export const FLORIS_ENDPOINTS = {
getWindmillData: 'api/floris/get_windmill_data',
};
export const FLORIS_PLOTS: Record<string, FlorisPlot> = {
horizontalPlane: {
name: 'horizontal_plane',
label: 'Horizontal Plane',
},
verticalPlane: {
name: 'vertical_plane',
label: 'Vertical Plane',
},
};

View File

@ -1,2 +0,0 @@
export * from './constants';
export * from './service';

View File

@ -1,14 +0,0 @@
import { api } from '@api/api';
import { FlorisFormValues } from '@components/ux/floris-form/types';
import { FLORIS_ENDPOINTS } from './constants';
import { WindmillData } from './types';
import { getWindmillDataRequestParams } from './utils';
export const getWindmillData = (formValues: Partial<FlorisFormValues>) => {
const { park } = formValues;
const params = getWindmillDataRequestParams(formValues);
const parkPath = park ? `/${park.id}/` : '';
const url = `${FLORIS_ENDPOINTS.getWindmillData}${parkPath}?${params}`;
return api.get<WindmillData>(url);
};

View File

@ -1,9 +0,0 @@
export type FlorisPlot = {
name: string;
label: string;
};
export type WindmillData = {
data: number[][];
fileName: Record<string, string>;
};

View File

@ -1,29 +0,0 @@
import { FlorisFormValues } from '@components/ux/floris-form/types';
import { FLORIS_PLOTS } from './constants';
export const getWindmillDataRequestParams = (
formValues: Partial<FlorisFormValues>,
) => {
let params = '';
if (formValues.turbines) {
const layoutX = formValues.turbines
?.map((row) => `layout_x=${row.x}`)
.join('&');
const layoutY = formValues.turbines
?.map((row) => `layout_y=${row.y}`)
.join('&');
const yawAngle = formValues.turbines
?.map((row) => `yaw_angle=${row.angle}`)
.join('&');
params += `${layoutX}&${layoutY}&${yawAngle}`;
}
const plots = Object.values(FLORIS_PLOTS)
.filter((_, i) => formValues.plots?.[i])
.map((p) => `plots=${p.name}`)
.join('&');
const dateStart = `date_start=${formValues.dateFrom?.substring(0, 10)}`;
const dateEnd = `date_end=${formValues.dateTo?.substring(0, 10)}`;
params += `&${plots}&${dateStart}&${dateEnd}`;
return params;
};

View File

@ -8,7 +8,6 @@ import {
TurbineTypePage,
TurbineTypesPage,
} from '@components/pages';
import { FlorisPage } from '@components/pages/floris-page/component';
import { ROUTES } from '@utils/route';
import React from 'react';
import { BrowserRouter, Navigate, Route, Routes } from 'react-router-dom';
@ -25,7 +24,6 @@ export function App() {
<Route path={ROUTES.turbineType.path} element={<TurbineTypePage />} />
<Route path={ROUTES.parks.path} element={<ParksPage />} />
<Route path={ROUTES.park.path} element={<ParkPage />} />
<Route path={ROUTES.floris.path} element={<FlorisPage />} />
</Route>
<Route
path="*"

View File

@ -1,32 +0,0 @@
import { WindmillData } from '@api/floris/types';
import { Heading } from '@components/ui';
import { FlorisForm, FlorisPlots, PowerSection } from '@components/ux';
import { useRoute } from '@utils/route';
import React, { useState } from 'react';
import styles from './styles.module.scss';
export function FlorisPage() {
const [data, setData] = useState<WindmillData>(null);
const [dateFrom, setDateFrom] = useState<string>(null);
const route = useRoute();
const handleFormSuccess = (data: WindmillData, dateFrom: string) => {
setData(data);
console.log(data);
setDateFrom(dateFrom);
};
return (
<div className={styles.page}>
<Heading tag="h1">{route.title}</Heading>
<FlorisForm onSuccess={handleFormSuccess} onFail={() => {}} />
{data && (
<>
<PowerSection power={data.data} dateFrom={dateFrom} />
<FlorisPlots filenames={data.fileName} />
</>
)}
</div>
);
}

View File

@ -1,7 +0,0 @@
.page {
display: grid;
padding: 40px 20px;
gap: 30px;
grid-template-columns: minmax(0, 1fr);
grid-template-rows: auto auto auto auto 1fr;
}

View File

@ -8,8 +8,8 @@ import { CheckboxGroupProps } from './types';
export function CheckboxGroup<T>({
name,
value,
items,
value = items.map(() => false),
onChange,
getItemKey,
getItemLabel,
@ -19,7 +19,7 @@ export function CheckboxGroup<T>({
const classNames = clsx(styles.checkBoxGroup, styles[scale]);
const handleChange = (index: number) => {
onChange?.(value.with(index, !value[index]));
onChange(value.with(index, !value[index]));
};
return (

View File

@ -2,9 +2,9 @@ import { Scale } from '../types';
export type CheckboxGroupProps<T> = {
name: string;
value?: boolean[];
value: boolean[];
items: T[];
onChange?: (value: boolean[]) => void;
onChange: (value: boolean[]) => void;
getItemKey: (item: T) => React.Key;
getItemLabel: (item: T) => string;
scale?: Scale;

View File

@ -1,144 +0,0 @@
import { FLORIS_PLOTS, getWindmillData } from '@api/floris';
import { getParks, Park } from '@api/wind';
import {
Autocomplete,
Button,
Checkbox,
CheckboxGroup,
DateInput,
Heading,
} from '@components/ui';
import { Controller, useForm } from '@utils/form';
import clsx from 'clsx';
import React, { useEffect, useState } from 'react';
import { FlorisTable } from '../floris-table/component';
import styles from './styles.module.scss';
import { FlorisFormProps, FlorisFormValues } from './types';
export function FlorisForm({
onSuccess,
onFail,
className,
...props
}: FlorisFormProps) {
const [pending, setPending] = useState<boolean>(false);
const [parks, setParks] = useState<Park[]>([]);
const [isManualEntry, setIsManualEntry] = useState<boolean>(false);
const { control, reset, getValues } = useForm<FlorisFormValues>({});
const fetchParks = async () => {
const res = await getParks();
setParks(res.data);
};
useEffect(() => {
fetchParks();
}, []);
const validate = (values: Partial<FlorisFormValues>) => {
console.log(values);
return true;
};
const handleSubmit = async (event: React.FormEvent) => {
event.preventDefault();
const values = getValues();
if (!validate(values)) {
return;
}
setPending(true);
const { data, error } = await getWindmillData(values);
if (data) {
onSuccess(data, values.dateFrom);
} else {
onFail(error.message);
}
setPending(false);
};
const handleResetButtonClick = () => {
reset({});
};
const handleManulEntryCheckboxChange = (
event: React.ChangeEvent<HTMLInputElement>,
) => {
const { checked } = event.target;
setIsManualEntry(checked);
if (checked) {
reset({ ...getValues(), park: undefined });
} else {
reset({ ...getValues(), turbines: undefined });
}
};
return (
<form
onSubmit={handleSubmit}
className={clsx(className, styles.form)}
{...props}
>
<Heading tag="h3">Turbines properties</Heading>
<div className={styles.content}>
<div className={styles.part}>
<div className={styles.dateRangeBox}>
<Controller
{...control('dateFrom')}
render={(params) => <DateInput placeholder="from" {...params} />}
/>
<Controller
{...control('dateTo')}
render={(params) => <DateInput placeholder="to" {...params} />}
/>
</div>
<Checkbox
label={{ text: 'Manual entry', position: 'right' }}
onChange={handleManulEntryCheckboxChange}
/>
{isManualEntry && (
<Controller
{...control('turbines')}
render={(params) => <FlorisTable {...params} />}
/>
)}
{!isManualEntry && (
<Controller
{...control('park')}
render={(params) => (
<Autocomplete
options={parks}
getOptionKey={(p) => p.id}
getOptionLabel={(p) => p.name}
{...params}
/>
)}
/>
)}
</div>
<div>
<Controller
{...control('plots')}
render={(params) => (
<CheckboxGroup
items={Object.values(FLORIS_PLOTS)}
getItemKey={(i) => i.name}
getItemLabel={(i) => i.label}
label="Plots"
{...params}
/>
)}
/>
</div>
</div>
<div className={styles.buttonBox}>
<Button type="submit" pending={pending}>
Submit
</Button>
<Button variant="secondary" onClick={handleResetButtonClick}>
Reset
</Button>
</div>
</form>
);
}

View File

@ -1 +0,0 @@
export * from './component';

View File

@ -1,35 +0,0 @@
.form {
display: grid;
padding: 20px;
border-radius: 15px;
background-color: var(--clr-layer-200);
box-shadow: 0px 1px 2px var(--clr-shadow-100);
gap: 20px;
& > * {
width: 100%;
}
}
.content {
display: grid;
gap: 30px;
grid-template-columns: 3fr 2fr;
}
.part {
display: grid;
gap: 10px;
}
.dateRangeBox {
display: grid;
gap: 10px;
grid-template-columns: 1fr 1fr;
}
.buttonBox {
display: flex;
justify-content: end;
gap: 10px;
}

View File

@ -1,17 +0,0 @@
import { WindmillData } from '@api/floris/types';
import { Park } from '@api/wind';
import { FlorisTableTurbine } from '../floris-table/types';
export type FlorisFormValues = {
dateFrom: string;
dateTo: string;
turbines: FlorisTableTurbine[];
plots: boolean[];
park: Park;
};
export type FlorisFormProps = {
onSuccess: (response: WindmillData, dateFrom: string) => void;
onFail: (message: string) => void;
} & React.ComponentProps<'form'>;

View File

@ -1,24 +0,0 @@
import { BASE_URL } from '@api/constants';
import { FLORIS_PLOTS } from '@api/floris';
import { Heading, Span } from '@components/ui';
import React from 'react';
import styles from './styles.module.scss';
import { FlorisPlotsProps } from './types';
export function FlorisPlots({ filenames }: FlorisPlotsProps) {
return (
<div className={styles.plots}>
<Heading tag="h3">Plots</Heading>
{Object?.keys(filenames).map((key) => {
const url = `${BASE_URL}/api/floris/download_image/${filenames[key]}`;
return (
<div className={styles.plot}>
<Span>{FLORIS_PLOTS[key]?.label ?? '???'}</Span>
<img src={url} className={styles.image} alt="Plot" />
</div>
);
})}
</div>
);
}

View File

@ -1 +0,0 @@
export * from './component';

View File

@ -1,21 +0,0 @@
.plots {
display: flex;
flex-direction: column;
padding: 20px;
border-radius: 15px;
background-color: var(--clr-layer-200);
box-shadow: 0px 1px 2px var(--clr-shadow-100);
gap: 20px;
}
.plot {
display: grid;
gap: 10px;
grid-template-columns: minmax(0, 1fr);
grid-template-rows: auto 1fr;
}
.image {
width: 100%;
border-radius: 10px;
}

View File

@ -1,3 +0,0 @@
export type FlorisPlotsProps = {
filenames: Record<string, string>;
};

View File

@ -1,58 +0,0 @@
import { IconButton, Span } from '@components/ui';
import DeleteIcon from '@public/images/svg/delete.svg';
import PlusIcon from '@public/images/svg/plus.svg';
import React, { useState } from 'react';
import { FlorisTableRow } from './components';
import styles from './styles.module.scss';
import { FlorisTableProps, FlorisTableTurbine } from './types';
export function FlorisTable({ value = [], onChange }: FlorisTableProps) {
const [selectedRows, setSelectedRows] = useState<Record<string, boolean>>({});
const handleDeleteButtonClick = () => {
onChange?.(value.filter((_, i) => !selectedRows[i]));
setSelectedRows({});
};
const handlePlusButtonClick = () => {
onChange?.([...value, { x: '', y: '', angle: '' }]);
};
const handleRowChange = (index: number, turbine: FlorisTableTurbine) => {
onChange?.(value.with(index, turbine));
};
const handleRowSelect = (index: number) => {
const checked = !selectedRows[index];
setSelectedRows({ ...selectedRows, [index]: checked });
};
return (
<div className={styles.table}>
<header className={styles.header}>
<Span className={styles.span} />
<Span className={styles.span}>x</Span>
<Span className={styles.span}>y</Span>
<Span className={styles.span}>angle</Span>
</header>
{value.map((v, i) => (
<FlorisTableRow
key={i}
value={v}
onChange={(turbine) => handleRowChange(i, turbine)}
onSelect={() => handleRowSelect(i)}
selected={selectedRows[i] ?? false}
/>
))}
<footer className={styles.footer}>
<IconButton onClick={handleDeleteButtonClick}>
<DeleteIcon />
</IconButton>
<IconButton onClick={handlePlusButtonClick}>
<PlusIcon />
</IconButton>
</footer>
</div>
);
}

View File

@ -1,42 +0,0 @@
import { Checkbox, NumberInput } from '@components/ui';
import React from 'react';
import { FlorisTableTurbine } from '../../types';
import styles from './styles.module.scss';
import { FlorisTableRowProps } from './types';
export function FlorisTableRow({
value,
onChange,
onSelect,
selected,
}: FlorisTableRowProps) {
const handleChange = (number: string, key: keyof FlorisTableTurbine) => {
onChange({ ...value, [key]: number });
};
return (
<div className={styles.row}>
<Checkbox
label={{ className: styles.checkboxLabel }}
onChange={onSelect}
checked={selected}
/>
<NumberInput
value={value.x}
onChange={(number) => handleChange(number, 'x')}
wrapper={{ className: styles.textInput }}
/>
<NumberInput
value={value.y}
onChange={(number) => handleChange(number, 'y')}
wrapper={{ className: styles.textInput }}
/>
<NumberInput
value={value.angle}
onChange={(number) => handleChange(number, 'angle')}
wrapper={{ className: styles.textInput }}
/>
</div>
);
}

View File

@ -1 +0,0 @@
export * from './component';

View File

@ -1,16 +0,0 @@
.row {
display: grid;
grid-template-columns: auto 1fr 1fr 1fr;
}
.checkboxLabel {
width: 46px;
justify-content: center;
border: 1px solid var(--clr-border-200);
}
.textInput {
border-radius: 0;
background-color: var(--clr-layer-200);
box-shadow: none;
}

View File

@ -1,8 +0,0 @@
import { FlorisTableTurbine } from '../../types';
export type FlorisTableRowProps = {
value: FlorisTableTurbine;
onChange: (value: FlorisTableTurbine) => void;
onSelect: () => void;
selected: boolean;
};

View File

@ -1 +0,0 @@
export * from './floris-table-row';

View File

@ -1,32 +0,0 @@
.table {
border-radius: 10px;
background-color: var(--clr-layer-200);
box-shadow: 0px 2px 2px var(--clr-shadow-100);
}
.header {
display: grid;
grid-template-columns: 46px 1fr 1fr 1fr;
}
.span {
padding: 13px;
border: 1px solid var(--clr-border-200);
background-color: var(--clr-layer-300);
text-align: center;
&:first-of-type {
border-top-left-radius: 10px;
}
&:last-of-type {
border-top-right-radius: 10px;
}
}
.footer {
padding: 5px;
border: 1px solid var(--clr-border-200);
border-radius: 0 0 10px 10px;
background-color: var(--clr-layer-300);
}

View File

@ -1,10 +0,0 @@
export type FlorisTableTurbine = {
x: string;
y: string;
angle: string;
};
export type FlorisTableProps = {
value?: FlorisTableTurbine[];
onChange?: (value: FlorisTableTurbine[]) => void;
};

View File

@ -1,8 +1,5 @@
export { FlorisForm } from './floris-form';
export { FlorisPlots } from './floris-plots';
export { Header } from './header';
export { ParkTurbineTable } from './park-turbine-table';
export { ParkTurbines } from './park-turbines';
export { PowerSection } from './power-section';
export { Sidebar } from './sidebar';
export { ThemeSelect } from './theme-select';

View File

@ -3,5 +3,4 @@ import { ROUTES } from '@utils/route';
export const NAVIGATION_LINKS = [
{ path: ROUTES.turbineTypes.path, title: ROUTES.turbineTypes.title },
{ path: ROUTES.parks.path, title: ROUTES.parks.title },
{ path: ROUTES.floris.path, title: ROUTES.floris.title },
];

View File

@ -1,46 +0,0 @@
import { Heading, Span } from '@components/ui';
import clsx from 'clsx';
import React from 'react';
import styles from './style.module.scss';
import { PowerSectionProps } from './types';
export function PowerSection({ power, dateFrom }: PowerSectionProps) {
const gridTemplateColumns = `repeat(${power[0].length + 1}, 1fr)`;
const date = new Date(dateFrom);
return (
<section className={styles.section}>
<Heading tag="h3">Power, watt per hour</Heading>
<div>
<div className={styles.row} style={{ gridTemplateColumns }}>
<Span className={clsx(styles.cell, styles.mainCell)}></Span>
{power[0].map((_, i) => (
<Span className={clsx(styles.cell, styles.mainCell)} key={i}>
{i + 1}
</Span>
))}
</div>
{power.map((row, r) => {
const day = String(date.getDate()).padStart(2, '0');
const month = String(date.getMonth() + 1).padStart(2, '0');
const year = date.getFullYear();
const dateStr = `${day}.${month}.${year}`;
date.setDate(date.getDate() + 1);
return (
<div className={styles.row} style={{ gridTemplateColumns }} key={r}>
<Span className={clsx(styles.cell, styles.mainCell)}>
{dateStr}
</Span>
{row.map((value, c) => (
<Span className={styles.cell} color="t300" key={c}>
{value}
</Span>
))}
</div>
);
})}
</div>
</section>
);
}

View File

@ -1 +0,0 @@
export * from './component';

View File

@ -1,43 +0,0 @@
.section {
display: grid;
padding: 20px;
border-radius: 15px;
background-color: var(--clr-layer-200);
box-shadow: 0px 1px 2px var(--clr-shadow-100);
gap: 20px;
}
.row {
display: grid;
&:first-child {
.cell {
&:first-of-type {
border-top-left-radius: 10px;
}
&:last-of-type {
border-top-right-radius: 10px;
}
}
}
&:last-child {
.cell {
&:first-of-type {
border-bottom-left-radius: 10px;
}
&:last-of-type {
border-bottom-right-radius: 10px;
}
}
}
}
.cell {
padding: 10px;
border: 1px solid var(--clr-border-200);
}
.mainCell {
background-color: var(--clr-layer-300);
}

View File

@ -1,4 +0,0 @@
export type PowerSectionProps = {
power: number[][];
dateFrom: string;
};

View File

@ -7,7 +7,6 @@ export const ROUTES: Record<AppRouteName, AppRoute> = {
turbineType: { path: '/turbine-types/:id', title: 'Turbine Type' },
parks: { path: '/parks', title: 'Parks' },
park: { path: '/parks/:id', title: 'Park' },
floris: { path: '/floris', title: 'Floris' },
};
export const routeArray = Object.values(ROUTES);

View File

@ -1,9 +1,4 @@
export type AppRouteName =
| 'turbineTypes'
| 'turbineType'
| 'parks'
| 'park'
| 'floris';
export type AppRouteName = 'turbineTypes' | 'turbineType' | 'parks' | 'park';
export type AppRoute = {
path: string;

View File

@ -26,8 +26,8 @@ class OpenMeteoClient:
return responses
def process_response(self, response):
# Process hourly data
daily = response.Daily()
daily_wind_speed_10m = daily.Variables(0).ValuesAsNumpy()
daily_wind_direction_10m = daily.Variables(1).ValuesAsNumpy()
@ -36,4 +36,5 @@ class OpenMeteoClient:
def get_weather_info(self, start_date, end_date, latitude=54.35119762746125, longitude=48.389356992149345):
responses = self.fetch_weather_data(latitude, longitude, start_date, end_date)
response = responses[0]
self.process_response(response)
return self.process_response(response)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 125 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 141 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Some files were not shown because too many files have changed in this diff Show More