geothermie-atlas/app/ews/potenzialberechnung/calculations-menu.tsx
2023-10-19 14:22:20 +02:00

481 lines
16 KiB
TypeScript

'use client';
import {
useState,
useEffect,
PropsWithChildren,
ReactNode,
SetStateAction,
Dispatch,
ChangeEventHandler,
ForwardedRef,
forwardRef,
useRef,
} from 'react';
import { useMediaQuery } from 'react-responsive';
import { updateComputationResultEWS } from '@/redux/computationsEWSSlice';
import { useAppSelector, useAppDispatch } from '@/redux/hooks';
import type Polygon from '@arcgis/core/geometry/Polygon';
import Warning from '@/app/components/warning';
import { initializeCalculationsMenuHandlers } from './potenzialkarte';
import { calculateGrid } from './gridcomputer';
const InputSection = (props: { children: ReactNode }) => {
return <div className="pb-2">{props.children}</div>;
};
type Properties = {
isLoading: Dispatch<SetStateAction<boolean>>;
};
type Resource = { layerId: number; layerName: string; feature: any };
const css =
'peer block min-h-[auto] w-full border px-3 py-1 leading-normal outline-none transition-all duration-200 ease-linear motion-reduce:transition-none';
export default forwardRef(function CalculationsMenu(
{ isLoading }: PropsWithChildren<Properties>,
outerRef: ForwardedRef<HTMLDivElement> | undefined
) {
const isTablet = useMediaQuery({ maxWidth: 1024 });
const [polygon, setPolygon] = useState<Polygon | null>(null);
const [gridSpacing, setGridSpacing] = useState<number>(10);
const [boreDepth, setBoreDepth] = useState<number>(100);
const [BS_HZ, setBS_HZ] = useState<number>(-1);
const [BS_KL, setBS_KL] = useState<number>(-1);
const [P_KL, setP_KL] = useState<number>(-1);
const [P_HZ, setP_HZ] = useState<number>(-1);
const [points, setPoints] = useState<number[][]>([]);
const [heating, setHeating] = useState<number>(35);
const [opened, setOpened] = useState<boolean>(true);
const innerRef = useRef<HTMLDivElement | null>(null);
const cadastralData = useAppSelector((store) => store.cadastre.value);
const resources: Resource[] = useAppSelector((store) => store.resourcesEWS.value);
const dispatch = useAppDispatch();
// run python script with values from layers
const handlePythonCalculation = () => {
if (cadastralData && resources && points.length <= 300 && polygon) {
isLoading(true);
let pointsText = JSON.stringify(points);
const BT = parseFloat(
resources.find((result) => result.layerId === 0)?.feature?.attributes['Classify.Pixel Value']
);
const GT = parseFloat(
resources.find((result) => result.layerId === 1)?.feature?.attributes['Classify.Pixel Value']
);
const WLF = parseFloat(
resources.find((result) => result.layerId === 2)?.feature?.attributes['Classify.Pixel Value']
);
const BS_HZ_Norm = parseInt(
resources.find((result) => result.layerId === 7)?.feature?.attributes['Classify.Pixel Value']
);
const BS_KL_Norm = parseInt(
resources.find((result) => result.layerId === 8)?.feature?.attributes['Classify.Pixel Value']
);
let url = '/api';
const data = {
BT,
GT,
WLF,
BS_HZ_Norm,
BS_KL_Norm,
BS_HZ: BS_HZ === -1 ? 0 : BS_HZ,
BS_KL: BS_KL === -1 ? 0 : BS_KL,
P_HZ: P_HZ === -1 ? 0 : P_HZ,
P_KL: P_KL === -1 ? 0 : P_KL,
boreDepth,
points: pointsText,
heating,
};
if (
Object.values(data).every((x) => typeof x !== 'undefined' && x !== null) &&
!isNaN(BT) &&
!isNaN(GT) &&
!isNaN(WLF) &&
!isNaN(BS_HZ_Norm) &&
!isNaN(BS_KL_Norm)
) {
fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
})
.then((res) => res.json())
.then((data) => {
// user defined input
const calculationMode = data[0];
const P_HZ_user = parseFloat(data[1]);
const P_KL_user = -parseFloat(data[2]);
const E_HZ_user = parseFloat(data[3]) / 1000;
const E_KL_user = -(parseFloat(data[4]) / 1000);
const cover = parseInt(data[5]);
const Pel_heatpump_user = parseFloat(data[6]);
const Pel_chiller_user = -parseFloat(data[7]);
const Eel_heatpump_user = parseFloat(data[8]) / 1000;
const Eel_chiller_user = -parseFloat(data[9]) / 1000;
const COP = parseFloat(data[15]);
const EER = parseFloat(data[16]);
const SCOP = parseFloat(data[17]);
const SEER = parseFloat(data[18]);
const Efactor_user = parseFloat(data[19]);
const imagehash = 'data:image/png;base64,' + data[20];
const imagehashSondenfeld = 'data:image/png;base64,' + data[21];
const GTcalc = parseFloat(data[22]);
const heizleistung = P_HZ_user + Pel_heatpump_user;
const heizarbeit = E_HZ_user + Eel_heatpump_user;
const kuehlleistung = P_KL_user - Pel_chiller_user;
const kuehlarbeit = E_KL_user - Eel_chiller_user;
// automatically balanced
const balanced = parseInt(data[23]);
const P_HZ_bal = parseFloat(data[24]);
const P_KL_bal = -parseFloat(data[25]);
const E_HZ_bal = parseFloat(data[26]) / 1000;
const E_KL_bal = -parseFloat(data[27]) / 1000;
const cover_bal = parseInt(data[28]);
const Pel_heatpump_bal = parseFloat(data[29]);
const Pel_chiller_bal = -parseFloat(data[30]);
const Eel_heatpump_bal = parseFloat(data[31]) / 1000;
const Eel_chiller_bal = -parseFloat(data[32]) / 1000;
const meanBoreholeSpacing = parseFloat(data[36]);
const cover_rise = parseFloat(data[37]);
const COP_bal = parseFloat(data[38]);
const EER_bal = parseFloat(data[39]);
const SCOP_bal = parseFloat(data[40]);
const SEER_bal = parseFloat(data[41]);
const Efactor_bal = parseFloat(data[42]);
const imagehashBal = 'data:image/png;base64,' + data[43];
const BS_HZ_bal = parseFloat(data[44]);
const BS_KL_bal = parseFloat(data[45]);
const T_radiator = parseInt(data[46]);
const heizleistungBal = P_HZ_bal + Pel_heatpump_bal;
const heizarbeitBal = Eel_heatpump_bal + E_HZ_bal;
const kuehlleistungBal = P_KL_bal - Pel_chiller_bal;
const kuehlarbeitBal = E_KL_bal - Eel_chiller_bal;
dispatch(
updateComputationResultEWS({
calculationMode,
points: points.length,
meanBoreholeSpacing,
boreDepth,
P_HZ,
P_KL,
BS_HZ,
BS_KL,
BS_HZ_Norm,
BS_KL_Norm,
P_HZ_user,
P_KL_user,
Pel_heatpump_user,
Pel_chiller_user,
E_HZ_user,
E_KL_user,
Efactor_user,
Eel_heatpump_user,
Eel_chiller_user,
cover,
imagehash,
imagehashSondenfeld,
balanced,
P_HZ_bal,
P_KL_bal,
Pel_heatpump_bal,
Pel_chiller_bal,
Eel_heatpump_bal,
Efactor_bal,
E_HZ_bal,
cover_bal,
imagehashBal,
heizleistung,
heizarbeit,
kuehlleistung,
kuehlarbeit,
heizleistungBal,
heizarbeitBal,
kuehlleistungBal,
GTcalc,
COP,
SCOP,
EER,
SEER,
BS_HZ_bal,
BS_KL_bal,
COP_bal,
EER_bal,
E_KL_bal,
SEER_bal,
SCOP_bal,
Eel_chiller_bal,
kuehlarbeitBal,
cover_rise,
T_radiator,
})
);
isLoading(false);
})
.catch((err) => {
dispatch(
updateComputationResultEWS({
error: JSON.stringify(err),
})
);
});
} else {
dispatch(
updateComputationResultEWS({
error: 'Aufgrund ungültiger Daten ist für dieses Grundstück keine Berechnung möglich.',
})
);
isLoading(false);
}
}
};
useEffect(() => {
// initialize callback functions
initializeCalculationsMenuHandlers(setPoints, setPolygon);
// cleanup
return () => {
// set polygon to null
setPolygon(null);
};
}, []);
// reset state
useEffect(() => {
setGridSpacing(10);
}, [polygon]);
const handleGridSpacing: ChangeEventHandler<HTMLInputElement> = (event) => {
let value = parseInt((event.target as HTMLInputElement).value);
if (value < 5) {
value = 5;
} else if (value > 15) {
value = 15;
}
setGridSpacing(value);
calculateGrid(polygon, value, setPoints);
};
const handleDepth: ChangeEventHandler<HTMLInputElement> = (event) => {
let value = parseInt((event.target as HTMLInputElement).value);
if (value > 250) {
value = 250;
} else if (value < 80) {
value = 80;
}
setBoreDepth(value);
};
const handleBS_HZ: ChangeEventHandler<HTMLInputElement> = (event) => {
let value = parseInt((event.target as HTMLInputElement).value);
if (value > 4379 || value < 0) {
value = -1;
}
if (value !== -1) {
setBS_HZ(value);
}
};
const handleBS_KL: ChangeEventHandler<HTMLInputElement> = (event) => {
let value = parseInt((event.target as HTMLInputElement).value);
if (event.target && event.target !== null) {
if (value > 4379 || value < 0) {
value = -1;
}
if (value !== -1) {
setBS_KL(value);
}
}
};
const handleP_HZ: ChangeEventHandler<HTMLInputElement> = (event) => {
let value = parseInt((event.target as HTMLInputElement).value);
if (value < 0) {
value = -1;
}
if (value !== -1) {
setP_HZ(value);
}
};
const handleP_KL: ChangeEventHandler<HTMLInputElement> = (event) => {
let value = parseInt((event.target as HTMLInputElement).value);
if (value < 0) {
value = -1;
}
if (value !== -1) {
setP_KL(value);
}
};
const handleKeyDown = (event: any) => {
event.preventDefault();
};
const handleHeating: ChangeEventHandler<HTMLSelectElement> = (event) => {
let value = parseInt((event.target as HTMLSelectElement).value);
setHeating(value);
};
const handleClick = () => {
setOpened(!opened);
if (innerRef && innerRef.current) {
innerRef.current.classList.toggle('hidden');
}
};
return (
<div
className={`absolute top-[70px] lg:top-24 left-4 ${
opened && isTablet ? 'bottom-12' : ''
} w-[calc(100%-2rem)] lg:w-[300px] z-0`}
>
<div
onClick={handleClick}
className="bg-gray-700 text-white cursor-pointer p-4 w-full border-0 hover:bg-gray-500 h-12 items-center justify-between text-sm xl:text-base z-0"
>
Berechnungsmenü <span className="float-right">{opened ? '-' : '+'}</span>
</div>
<div
className={`h-[calc(100%-3rem)] px-2 xl:px-4 pt-4 pb-2 overflow-y-auto bg-white text-sm ${
opened ? '' : 'hidden'
}`}
ref={innerRef}
>
<div className={!polygon ? 'bg-white opacity-50 pointer-events-none pb-2' : 'pb-2'}>
<label>Sondenpunkte auswählen/zeichnen</label>
<div ref={outerRef}></div>
</div>
<InputSection>
<label htmlFor="gridspacing-input">Heizungsart </label>
<select id="gridspacing-input" onChange={handleHeating} className={css} disabled={polygon ? false : true}>
<option value={35}>Fußbodenheizung</option>
<option value={50}>Radiator</option>
</select>
</InputSection>
<InputSection>
<label htmlFor="gridspacing-input">Sondenabstand in Meter</label>
<input
type="number"
value={gridSpacing}
className={css}
onChange={handleGridSpacing}
onKeyDown={handleKeyDown}
placeholder="Wert zwischen 15 und 15 m (default=100)"
min="5"
max="15"
disabled={polygon ? false : true}
></input>
</InputSection>
<InputSection>
<label htmlFor="depth-input">Sondentiefe in Meter</label>
<input
className={css}
type="number"
min="80"
max="250"
placeholder="Wert zwischen 80 und 250 m (default=100)"
value={boreDepth}
onChange={handleDepth}
onKeyDown={handleKeyDown}
disabled={polygon ? false : true}
></input>
</InputSection>
<InputSection>
<label htmlFor="phz-input">Heizleistung in kW (optional)</label>
<input
className={css}
type="number"
min="0"
placeholder="Wert größer 0"
onChange={handleP_HZ}
value={P_HZ === -1 ? '' : P_HZ}
disabled={polygon ? false : true}
></input>
</InputSection>
<InputSection>
<label htmlFor="pkl-input">Kühlleistung in kW (optional)</label>
<input
className={css}
type="number"
min="0"
placeholder="Wert größer 0"
onChange={handleP_KL}
value={P_KL === -1 ? '' : P_KL}
disabled={polygon ? false : true}
></input>
</InputSection>
<InputSection>
<label htmlFor="bshz-input">Jahresbetriebsstunden Heizen (optional)</label>
<input
className={css}
type="number"
min="0"
max="4379"
placeholder="Wert zwischen 0 und 4379"
onChange={handleBS_HZ}
value={BS_HZ === -1 ? '' : BS_HZ}
disabled={polygon ? false : true}
></input>
</InputSection>
<InputSection>
<label htmlFor="bskl-input">Jahresbetriebsstunden Kühlen (optional)</label>
<input
type="number"
min="0"
max="4379"
placeholder="Wert zwischen 0 und 4379"
onChange={handleBS_KL}
value={BS_KL === -1 ? '' : BS_KL}
className={css}
disabled={polygon ? false : true}
></input>
</InputSection>
{(P_HZ > 0 || P_KL > 0 || BS_HZ > 0 || BS_KL > 0) &&
(P_HZ === -1 || P_KL === -1 || BS_HZ === -1 || BS_KL === -1) ? (
<Warning>Solange nicht alle Parameter ausgefüllt sind, wird mit Normwerten gerechnet.</Warning>
) : null}
{points.length > 300 && <Warning>Es sind maximal 300 Punkte möglich. Bitte löschen Sie zuerst Punkte.</Warning>}
{polygon && points.length === 0 ? <Warning>Zeichnen Sie mindestens einen Punkt!</Warning> : null}
<div className=" p-px w-full">
<button
onClick={handlePythonCalculation}
className={
polygon && points.length > 0 && points.length <= 300
? 'w-full h-10 border-none bg-gray-700 hover:bg-gray-500 text-white mb-2 py-2 px-4 cursor-pointer'
: 'w-full h-10 border-none bg-gray-700 hover:bg-gray-500 text-white mb-2 py-2 px-4 opacity-50 cursor-not-allowed'
}
disabled={polygon && points.length > 0 && points.length <= 300 ? false : true}
>
Berechnung starten
</button>
</div>
</div>
</div>
);
});