481 lines
16 KiB
TypeScript
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]`}
|
|
>
|
|
<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"
|
|
>
|
|
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 z-50 pointer-events-none cursor-not-allowed 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>
|
|
);
|
|
});
|