Finish responsive UI
This commit is contained in:
parent
827273d84f
commit
2c0381a362
149
app/about/page.tsx
Normal file
149
app/about/page.tsx
Normal file
|
@ -0,0 +1,149 @@
|
|||
import Link from 'next/link';
|
||||
|
||||
export default function About() {
|
||||
return (
|
||||
<div className="absolute top-16 bottom-0 left-0 right-0 w-full px-[33%] flex flex-col items-center pt-8 pb-8 overflow-y-auto">
|
||||
<h1 className="text-xl mb-4">Informationen über diese Applikation</h1>
|
||||
<p>
|
||||
Diese Applikation unterstützt die Planung oberflächennaher geothermischer Anlagen. Es können geothermisch
|
||||
relevante Parameter, und mögliche rechtliche Einschränkungen und Hinweise abgefragt werden. Außerdem können
|
||||
standortspezifisch Berechnungen in Bezug auf die gewünschte Dimensionierung der geothermischen Anlage
|
||||
durchgeführt werden. Die der Applikation zugrunde liegenden Daten wurden im Rahmen des Green Energy Lab -
|
||||
Spatial Energy Planning Projekts erstellt. Nähere Informationen über dieses Projekt finden Sie unter{' '}
|
||||
<Link href="https://www.waermeplanung.at">https://www.waermeplanung.at</Link>. Die Untersuchungsgebiete des
|
||||
Projekts umfassen Wien, den Dauersiedlungsraum Salzburg und ausgewählte Gebiete in der Steiermark. Diese
|
||||
Applikation beschränkt sich ausschließlich auf Wien.
|
||||
</p>
|
||||
<h1 className="text-xl mb-4 mt-6">Zusatzinfo über interaktive Sondenfeldberechnung </h1>
|
||||
<p>
|
||||
Das Programm berechnet die mögliche Leistung und Energie, die aus dem vorgegebenen Sondenfeld gewonnen werden
|
||||
kann. Dabei wird die Geometrie des Sondenfeldes (Lage, Tiefe, Sondenabstand) vom Benutzer interaktiv vorgegeben.
|
||||
Für dieses Sondenfeld wird zuerst die g-Funktion berechnet und danach das maximale Potenzial (Leistung und
|
||||
Jahresenergie) ermittelt. Das ermittelte Potenzial berücksichtigt die Betriebsweise (siehe unten) und ist auf
|
||||
die Grenzwerte der mittleren Fluidtemperturen ausgelegt. Im Heizbetrieb werden -1.5 °C nicht unterschritten und
|
||||
im Kühlbetrieb werden 28 °C nicht überschritten. Die Betriebsweise wird vereinfacht in vier Phasen pro Jahr
|
||||
unterteilt: Heizbetrieb – Stillstand – Kühlbetrieb - Stillstand.
|
||||
</p>
|
||||
<h2 className="text-lg mb-2 mt-4">Betriebsweise</h2>
|
||||
<p>
|
||||
Die Betriebsfunktion kann durch Angabe der gebäudeseitigen Heiz- und Kühlleistung sowie der
|
||||
Jahresbetriebsstunden für Heizen und Kühlen vorgegeben werden. In diesem Fall wird die erzielbare Sondenleistung
|
||||
für das benutzerdefinierte Sondenfeld so ermittelt, dass das Leistungsverhältnis zwischen Heizen und Kühlen
|
||||
eingehalten wird. Es kann auch ein reiner Heiz- bzw. Kühlbetrieb vorgegeben werden. Bei der benutzerdefinierten
|
||||
Betriebsweise wird auch ein Deckungsgrad berechnet, der angibt wieviel Prozent des angegebenen Bedarfs durch das
|
||||
vorgegebene Sondenfeld abgedeckt werden kann. Wird die Betriebsfunktion nicht durch den Benutzer vorgegeben, so
|
||||
wird mit einem Normbetrieb gerechnet. Dabei werden die Jahresbetriebsstunden für Heizen und Kühlen aus der
|
||||
standortbezogenen Bodentemperatur für ein typisches Wohnhaus herangezogen. Die mittlere Oberflächentemperatur
|
||||
des Bodens wird dabei aus den Ressourcenkarten für den Standort abgefragt. Ein Deckungsgrad wird hier nicht
|
||||
ausgegeben. Ist die Jahresenergiebilanz des Sondenfeldes nicht ausgeglichen (mit einer Toleranz von +/- 10 %)
|
||||
wird zusätzlich die Betriebsweise „saisonaler Speicherbetrieb“ gerechnet. Wenn der Wärmeentzug überwiegt, wird
|
||||
automatisch eine Zusatzquelle verwendet, welche die Bilanz mit dem zusätzlichen Wärmeeintrag ausgleicht. Wenn
|
||||
der Wärmeeintrag überwiegt, wird automatisch eine Zusatzsenke zur Betriebsfunktion hinzugefügt, welche die
|
||||
Bilanz mit einem zusätzlichen Wärmeentzug ausgleicht. Dabei wird angegeben, um wieviel Prozent die
|
||||
Leistungsfähigkeit des Sondenfeldes im Speicherbetrieb gesteigert werden kann. Ist der gegenseitige
|
||||
Sondenabstand größer als 6 m wird ein Hinweis ausgegeben, dass der gegenseitige Sondenabstand auf 5 m reduziert
|
||||
werden kann. Die mittlere Wärmeleitfähigkeit und die mittlere Untergrundtemperatur von 0 bis 100 m Tiefe werden
|
||||
aus den Ressourcenkarten für das ausgewählte Grundstück übernommen und fließen in die Berechnung ein. Ist die
|
||||
Vorgabe der Sondentiefe größer als 100 m, so wird die Untergrundtemperatur mit einem Gradienten von 0.03 °C pro
|
||||
Meter mit der Tiefe erhöht. Zusätzlich wird eine Grafik mit der Entwicklung der mittleren Fluidtemperatur in
|
||||
Zusammenhang mit der berechneten Betriebsfunktion ausgegeben.
|
||||
</p>
|
||||
<h2 className="text-lg mb-2 mt-4">Zusätzliche Parameter für die Berechnung</h2>
|
||||
<p>
|
||||
Die folgenden Parameter werden für alle Simulationen verwendet und können nicht durch eine Eingabe verändert
|
||||
werden. <br></br>
|
||||
<br></br>
|
||||
Simulationsjahre: 20 Jahre<br></br>
|
||||
Volumetrische Wärmekapazität des Erdreichs: 2.2 MJ/m³/K<br></br>
|
||||
Sondenkopf Überdeckung: 1 m <br></br>
|
||||
Bohrradius: 0.075 m <br></br>
|
||||
Sondentyp: Duplex 32 mm, 0.04 m Rohrabstand<br></br>
|
||||
Wärmeträgermedium: Ethanol 12 % <br></br>
|
||||
Massenstrom pro Sonde: 0.4 kg/s<br></br>
|
||||
Wärmeleitfähigkeit der Verpressung: 2 W/m/K
|
||||
</p>
|
||||
<h2 className="text-lg mb-2 mt-4">Grenztemperaturen</h2>
|
||||
<p>
|
||||
Minimale mittlere Fluidtemperatur am Ende der Heizsaison: -1.5 °C <br></br>
|
||||
Maximale mittlere Fluidtemperatur am Ende der Kühlsaison: 28 °C <br></br>
|
||||
<br></br>
|
||||
Das Sondenfeld wird im Heizbetrieb auf die minimale Grenztemperatur ausgelegt, im Kühlbetrieb auf die maximale
|
||||
Grenztemperatur.
|
||||
</p>
|
||||
<h2 className="text-lg mb-2 mt-4">Leistungszahlen</h2>
|
||||
<p className="w-full">Folgende Leistungszahlen der Wärmepumpe im Heiz- und Kühlbetrieb werden berücksichtigt:</p>
|
||||
<dl className="mt-4">
|
||||
<dt>COP</dt>
|
||||
<dd className="mb-2">
|
||||
Leistungszahl der Wärmepumpe im Heizbetrieb (Coefficient of Performance): Die Leistungszahl für Heizen (COP)
|
||||
wird soleseitig immer auf die untere Grenztemperatur der Erdwärmesonden (Vorlauf -3 / Rücklauf 0 °C)
|
||||
ausgelegt. Wasserseitig wird zur Berechnung des COP die vorgegebene Heizungs-Vorlauftemperatur verwendet,
|
||||
welche ohne Nutzereingabe auf 35 °C voreingestellt ist. Diese Leistungszahl gilt also für den Extremfall, wenn
|
||||
die Fluidtemperatur der Sonden den unteren Grenzwert erreicht haben (in der Regel am Ende der Heizsaison nach
|
||||
20 Betriebsjahren).
|
||||
</dd>
|
||||
<dt>JAZ</dt>
|
||||
<dd className="mb-2">
|
||||
Jahresarbeitszahl oder saisonale Leistungszahl der Wärmepumpe im Heizbetrieb: Die Berechnung der
|
||||
Jahresarbeitszahl im Heizbetrieb (JAZ) berücksichtigt die Sole-Fluidtemperaturen jeder Betriebsstunde im Jahr,
|
||||
berechnet den COP und bildet daraus den Mittelwert über alle Betriebsstunden.
|
||||
</dd>
|
||||
<dt>EER</dt>
|
||||
<dd className="mb-2">
|
||||
Leistungszahl der Wärmepumpe im Kühlbetrieb (Energy Efficiency Rating): Die Leistungszahl für Kühlen (EER) ist
|
||||
statisch auf eine Fluidtemperatur von 18 °C auf der kalten Seite (gebäudeseitig) und 30 °C auf der warmen
|
||||
Seite ausgelegt (erdseitig).
|
||||
</dd>
|
||||
<dt>SEER</dt>
|
||||
<dd className="mb-2">
|
||||
Saisonale Leistungszahl der Wärmepumpe im Kühlbetrieb (Seasonal Energy Efficiency Rating): Die Berechnung der
|
||||
saisonalen Leistungszahl im Kühlbetrieb (SEER) berücksichtigt die berechneten Fluid-Temperaturen aller
|
||||
Kühlbetriebsstunden, insbesondere werden Fluidtemperaturen unterhalb 18 °C für die freie Kühlung ohne
|
||||
Wärmepumpe berücksichtigt. Ohne Wärmepumpe ist die Kühlung besonders effizient, daher wird für die
|
||||
Betriebsstunden mit freier Kühlung pauschal ein EER-Wert von 20 angenommen. Die Berechnungsformel basiert auf
|
||||
Messwerte verschiedener Betriebspunkte einer realen Sole-Wasser Wärmepumpe in der Klimakammer und wurde im FFG
|
||||
Projekt ZWEIFELDSPEICHER ermittelt.
|
||||
</dd>
|
||||
</dl>
|
||||
|
||||
<h1 className="text-xl mb-4 mt-6">Verwendete 3rd-Party Software</h1>
|
||||
<p>
|
||||
Die Berechnungen für Erdwärmesonden werden mit dem Python-Modul <code>pygfunction</code> durchgeführt (siehe{' '}
|
||||
<Link href="https://pypi.org/project/pygfunction/">https://pypi.org/project/pygfunction/</Link>
|
||||
).
|
||||
</p>
|
||||
<blockquote className="italic font-semibold text-left mt-4">
|
||||
<p>
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
|
||||
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
|
||||
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY
|
||||
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
|
||||
OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
|
||||
DAMAGE.
|
||||
</p>
|
||||
<p>Copyright (c) 2017-2022, Massimo Cimmino All rights reserved.</p>
|
||||
</blockquote>
|
||||
<h1 className="text-xl mb-4 mt-6">Hinweise und Haftungsausschluss</h1>
|
||||
<p>
|
||||
Die thematischen Inhalte auf unserem Webportal dienen dazu, einen Überblick über Potentiale und Konflikte in
|
||||
Zusammenhang mit geothermischen Anlagen zu geben. Sie ersetzen keine detaillierten Planungen. Aus unseren Karten
|
||||
ergibt sich keinerlei Genehmigungsanspruch einer geplanten Nutzung gegenüber den zuständigen Behörden. Der
|
||||
Anbieter dieses Webportals und der damit verbundenen Dienstleistungen übernimmt keine Haftung für Schäden, die
|
||||
durch den ungeeigneten Gebrauch des Webportals entstehen.
|
||||
</p>
|
||||
<h1 className="text-xl mb-4 mt-6">Kontakt</h1>
|
||||
<ul>
|
||||
<li>GeoSphere Austria</li>
|
||||
<li>Hohe Warte 38, 1190 Wien</li>
|
||||
<li>
|
||||
<Link href="mailto:geothermie@geosphere.at">geothermie@geosphere.at</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link href="https://geosphere.at">https://www.geosphere.at</Link>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
1095
app/api/BHEseppy_query_V50_MWP_beta6.py
Normal file
1095
app/api/BHEseppy_query_V50_MWP_beta6.py
Normal file
File diff suppressed because it is too large
Load Diff
22
app/api/pythonShell.js
Normal file
22
app/api/pythonShell.js
Normal file
|
@ -0,0 +1,22 @@
|
|||
import { PythonShell } from 'python-shell';
|
||||
|
||||
const scriptFile = './app/api/BHEseppy_query_V50_MWP_beta6.py';
|
||||
|
||||
export async function runPythonShell(options) {
|
||||
try {
|
||||
const results = await PythonShell.run(scriptFile, options);
|
||||
|
||||
let resultList = [];
|
||||
if (results && results.length > 0) {
|
||||
let resultString = results[results.length - 1];
|
||||
resultList = resultString
|
||||
.replace(/[\[\]]/g, '')
|
||||
.split(',')
|
||||
.map((entry) => entry.trim().replaceAll("'", ''));
|
||||
}
|
||||
|
||||
return resultList;
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
28
app/api/route.ts
Normal file
28
app/api/route.ts
Normal file
|
@ -0,0 +1,28 @@
|
|||
import { NextResponse } from 'next/server';
|
||||
import { runPythonShell } from './pythonShell';
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const req = await request.json();
|
||||
let options = {
|
||||
args: [
|
||||
req.BT,
|
||||
req.GT,
|
||||
req.WLF,
|
||||
req.BS_HZ_Norm,
|
||||
req.BS_KL_Norm,
|
||||
req.BS_HZ,
|
||||
req.BS_KL,
|
||||
req.P_HZ,
|
||||
req.P_KL,
|
||||
req.boreDepth,
|
||||
req.heating,
|
||||
req.points,
|
||||
req.heating,
|
||||
],
|
||||
};
|
||||
|
||||
if (options.args.every((arg) => typeof arg !== 'undefined')) {
|
||||
const results = await runPythonShell(options);
|
||||
return NextResponse.json(results);
|
||||
}
|
||||
}
|
43
app/components/collapsible.tsx
Normal file
43
app/components/collapsible.tsx
Normal file
|
@ -0,0 +1,43 @@
|
|||
import { useState, useRef, forwardRef, PropsWithChildren, ForwardedRef, useImperativeHandle } from 'react';
|
||||
|
||||
type Properties = {
|
||||
title: string;
|
||||
open: boolean;
|
||||
};
|
||||
|
||||
export default forwardRef(function Collapsible(
|
||||
props: PropsWithChildren<Properties>,
|
||||
outerRef: ForwardedRef<HTMLDivElement> | undefined
|
||||
) {
|
||||
const [opened, setOpened] = useState<boolean>(props.open);
|
||||
const [previouslyOpened, setPreviouslyOpened] = useState<boolean>(props.open);
|
||||
|
||||
const innerRef = useRef<HTMLDivElement | null>(null);
|
||||
useImperativeHandle(outerRef, () => innerRef.current!, []);
|
||||
|
||||
if (props.open !== previouslyOpened) {
|
||||
setOpened(props.open);
|
||||
setPreviouslyOpened(props.open);
|
||||
}
|
||||
|
||||
const handleClick = () => {
|
||||
setOpened(!opened);
|
||||
if (innerRef && innerRef.current) {
|
||||
innerRef.current.classList.toggle('hidden');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<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 mt-px text-sm xl:text-base leading:normal"
|
||||
>
|
||||
{props.title} <span className="float-right">{opened ? '-' : '+'}</span>
|
||||
</div>
|
||||
<div className={`px-2 pt-2 overflow-y-auto bg-white text-sm ${opened ? '' : 'hidden'}`} ref={innerRef}>
|
||||
{props.children}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
61
app/components/footer.tsx
Normal file
61
app/components/footer.tsx
Normal file
|
@ -0,0 +1,61 @@
|
|||
import Collapsible from './collapsible';
|
||||
import Link from 'next/link';
|
||||
|
||||
const tableDataCSS = 'w-full break-words border-b border-solid border-gray-300';
|
||||
export default function Footer() {
|
||||
return (
|
||||
<>
|
||||
<Collapsible title="Haftungsausschluss" open={true}>
|
||||
<table id="disclaimer" className="table-fixed w-full mb-4">
|
||||
<thead>
|
||||
<tr>
|
||||
<td></td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td className={tableDataCSS}>
|
||||
Die thematischen Inhalte dieser Webapplikation dienen dazu, einen Überblick über Potentiale und
|
||||
Konflikte in Zusammenhang mit geothermischen Anlagen zu geben. Sie ersetzen keine detaillierten
|
||||
Planungen. Aus unseren Karten ergibt sich keinerlei Genehmigungsanspruch einer geplanten Nutzung
|
||||
gegenüber den zuständigen Behörden. Der Anbieter dieser Webapplikation und der damit verbundenen
|
||||
Dienstleistungen übernimmt keine Haftung für Schäden, die durch den ungeeigneten Gebrauch des Webportals
|
||||
entstehen.
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</Collapsible>
|
||||
<Collapsible title="Kontakt" open={true}>
|
||||
<table id="contact" className="table-fixed w-full">
|
||||
<thead>
|
||||
<tr>
|
||||
<td></td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<Link
|
||||
href="mailto:geothermie@geosphere.at"
|
||||
className="underline underline-offset-4 decoration-transparent transition duration-300 ease-in-out hover:decoration-red-700"
|
||||
>
|
||||
geothermie@geosphere.at
|
||||
</Link>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Department für Rohstoffgeologie und Geoenergie</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Hohe Warte 38</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className={tableDataCSS}>1190 Wien</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</Collapsible>
|
||||
</>
|
||||
);
|
||||
}
|
5
app/components/success.tsx
Normal file
5
app/components/success.tsx
Normal file
|
@ -0,0 +1,5 @@
|
|||
import type { ReactNode } from 'react';
|
||||
|
||||
export default function Success(props: { children: ReactNode }) {
|
||||
return <p className="w-full whitespace-normal break-normal bg-green-300 text-green-700 mb-1 p-4">{props.children}</p>;
|
||||
}
|
7
app/components/warning.tsx
Normal file
7
app/components/warning.tsx
Normal file
|
@ -0,0 +1,7 @@
|
|||
import type { ReactNode } from 'react';
|
||||
|
||||
export default function Warning(props: { children: ReactNode }) {
|
||||
return (
|
||||
<p className="w-full whitespace-normal break-normal bg-orange-300 text-orange-700 mb-1 p-4">{props.children}</p>
|
||||
);
|
||||
}
|
38
app/config/config.js
Normal file
38
app/config/config.js
Normal file
|
@ -0,0 +1,38 @@
|
|||
// spatial reference system
|
||||
export const SRS = 31256;
|
||||
|
||||
// layer URLs
|
||||
export const AMPEL_EWS_URL = 'https://gis.geosphere.at/maps/rest/services/geothermie/ampelkarte_ews/MapServer';
|
||||
export const AMPEL_GWWP_URL = 'https://gis.geosphere.at/maps/rest/services/geothermie/ampelkarte_gwwp/MapServer';
|
||||
export const RESOURCES_EWS_URL = 'https://gis.geosphere.at/maps/rest/services/geothermie/potentialkarte_ews/MapServer';
|
||||
export const RESOURCES_GWWP_URL =
|
||||
'https://gis.geosphere.at/maps/rest/services/geothermie/potentialkarte_gwwp/MapServer';
|
||||
export const BEV_KATASTER_URL = 'https://data.bev.gv.at/geoserver/BEVdataKAT/wms';
|
||||
export const BASEMAP_AT_URL = 'https://mapsneu.wien.gv.at/basemap31256neu';
|
||||
|
||||
// text templates for info panel
|
||||
export const textTemplates = {
|
||||
0: ['Die mittlere jährliche Bodentemperatur beträgt laut Satellitendaten (MODIS) rund ', ' °C.'],
|
||||
1: ['Die mittlere Temperatur des Untergrunds für eine Tiefe von 0 bis 100 m beträgt rund ', ' °C.'],
|
||||
2: [
|
||||
'Die mittlere konduktive Wärmeleitfähigkeit des Untergrunds für eine Tiefe von 0 bis 100 m beträgt rund ',
|
||||
' W/m/K.',
|
||||
],
|
||||
3: [
|
||||
'Die Entzugsleistung einer 100 m tiefen Einzelsonde im standortbezogenen Normbetrieb (Heizen und Kühlen mit Normbetriebsstunden eines typischen Wohngebäudes am Standort) beträgt am Grundstück rund ',
|
||||
' W/lfm.',
|
||||
],
|
||||
4: [
|
||||
'Die Entzugsleistung einer 100 m tiefen Einzelsonde im saisonalem Speicherbetrieb (die im Winter zur Heizung entzogene Wärme wird im Sommer vollständig wieder zurückgegeben) beträgt am Grundstück rund ',
|
||||
' W/lfm.',
|
||||
],
|
||||
|
||||
5: [
|
||||
`Die flächenspezifische Jahresenergie eines 1156 m² großen und 100 m tiefen Sondenfeldes im standortbezogenen Normbetrieb (4 x 4 Sonden mit je 10 m Abstand - Heizen und Kühlen mit Normbetriebsstunden eines typischen Wohngebäudes am Standort) beträgt rund `,
|
||||
' kWh/m²/a.',
|
||||
],
|
||||
6: [
|
||||
`Die flächenspezifische Jahresenergie eines 1156 m² großen und 100 m tiefen Sondenfeldes im saisonalem Speicherbetrieb (7 x 7 Sonden mit je 5 m Abstand - die im Winter zur Heizung entzogene Wärme wird im Sommer vollständig wieder zurückgegeben) beträgt rund `,
|
||||
' kWh/m²/a.',
|
||||
],
|
||||
};
|
13
app/daten/page.tsx
Normal file
13
app/daten/page.tsx
Normal file
|
@ -0,0 +1,13 @@
|
|||
import Link from 'next/link';
|
||||
|
||||
export default function Daten() {
|
||||
return (
|
||||
<main className="w-full">
|
||||
<div className="flex flex-col items-center pt-32">
|
||||
<p>
|
||||
Hier kommen die Links zu <Link href="https://www.tethys.at/">Tethys</Link>
|
||||
</p>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
}
|
25
app/ews/grundlagenkarte/esri-ui-grundlagenkarte.css
Normal file
25
app/ews/grundlagenkarte/esri-ui-grundlagenkarte.css
Normal file
|
@ -0,0 +1,25 @@
|
|||
.esri-component {
|
||||
max-height: 80%;
|
||||
}
|
||||
|
||||
.esri-ui-corner .esri-component.esri-layer-list.esri-widget.esri-widget--panel {
|
||||
}
|
||||
|
||||
.esri-component.esri-legend.esri-widget.esri-widget--panel {
|
||||
}
|
||||
|
||||
.esri-ui-top-left.esri-ui-corner {
|
||||
height: 95%;
|
||||
}
|
||||
|
||||
.esri-layer-list {
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.esri-search {
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
.esri-legend__layer-child-table > .esri-legend__layer-caption {
|
||||
display: none;
|
||||
}
|
258
app/ews/grundlagenkarte/grundlagenkarte.tsx
Normal file
258
app/ews/grundlagenkarte/grundlagenkarte.tsx
Normal file
|
@ -0,0 +1,258 @@
|
|||
'use client';
|
||||
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { useMediaQuery } from 'react-responsive';
|
||||
|
||||
import ArcGISMap from '@arcgis/core/Map';
|
||||
import Extent from '@arcgis/core/geometry/Extent';
|
||||
import MapView from '@arcgis/core/views/MapView';
|
||||
import Basemap from '@arcgis/core/Basemap';
|
||||
import MapImageLayer from '@arcgis/core/layers/MapImageLayer';
|
||||
import WMTSLayer from '@arcgis/core/layers/WMTSLayer';
|
||||
import SpatialReference from '@arcgis/core/geometry/SpatialReference';
|
||||
import Search from '@arcgis/core/widgets/Search';
|
||||
import ScaleBar from '@arcgis/core/widgets/ScaleBar';
|
||||
import GraphicsLayer from '@arcgis/core/layers/GraphicsLayer';
|
||||
import Graphic from '@arcgis/core/Graphic';
|
||||
import LayerList from '@arcgis/core/widgets/LayerList';
|
||||
import Zoom from '@arcgis/core/widgets/Zoom';
|
||||
import Legend from '@arcgis/core/widgets/Legend';
|
||||
import FeatureLayer from '@arcgis/core/layers/FeatureLayer';
|
||||
import Polygon from '@arcgis/core/geometry/Polygon';
|
||||
import esriConfig from '@arcgis/core/config';
|
||||
import { watch } from '@arcgis/core/core/reactiveUtils';
|
||||
import type SimpleFillSymbol from '@arcgis/core/symbols/SimpleFillSymbol';
|
||||
|
||||
import './esri-ui-grundlagenkarte.css';
|
||||
import { Vienna, Austria } from '@/public/borders-vienna-austria';
|
||||
import { BASEMAP_AT_URL, AMPEL_EWS_URL, RESOURCES_EWS_URL, SRS } from '@/app/config/config';
|
||||
import getAddress from '@/app/utils/getAddress';
|
||||
import identifyAllLayers from '@/app/utils/identify';
|
||||
import takeScreenshot from '@/app/utils/screenshot';
|
||||
|
||||
import { updateAmpelkarteEWS } from '@/redux/ampelkarteEWSSlice';
|
||||
import { updateResourcesEWS } from '@/redux/resourcesEWSSlice';
|
||||
import { updateScreenshot } from '@/redux/screenshotSlice';
|
||||
import { useAppDispatch } from '@/redux/hooks';
|
||||
|
||||
import PanelGrundlagenkarte from './panel-grundlagenkarte';
|
||||
|
||||
// set path for local assets
|
||||
esriConfig.assetsPath = '/assets';
|
||||
|
||||
export default function MapComponent() {
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const mapDiv = useRef<HTMLDivElement | null>(null);
|
||||
const [address, setAddress] = useState<string[] | null>([]);
|
||||
|
||||
const isMobile = useMediaQuery({ maxWidth: 480 });
|
||||
|
||||
useEffect(() => {
|
||||
let handle: IHandle;
|
||||
let view: MapView;
|
||||
if (mapDiv.current) {
|
||||
dispatch(updateAmpelkarteEWS([]));
|
||||
dispatch(updateResourcesEWS([]));
|
||||
dispatch(updateScreenshot(''));
|
||||
|
||||
view = new MapView({
|
||||
container: mapDiv.current,
|
||||
extent: new Extent({
|
||||
xmin: -19000,
|
||||
ymin: 325000,
|
||||
xmax: 29000,
|
||||
ymax: 360000,
|
||||
spatialReference: new SpatialReference({ wkid: SRS }),
|
||||
}),
|
||||
popupEnabled: false,
|
||||
});
|
||||
|
||||
let viennaGraphic = new Graphic({
|
||||
geometry: new Polygon({ rings: [Austria, Vienna], spatialReference: { wkid: SRS } }),
|
||||
symbol: {
|
||||
type: 'simple-fill',
|
||||
color: [209, 213, 219, 0.65],
|
||||
style: 'solid',
|
||||
outline: {
|
||||
color: 'white',
|
||||
width: 0,
|
||||
},
|
||||
} as unknown as SimpleFillSymbol,
|
||||
});
|
||||
|
||||
// graphic layers
|
||||
let viennaGraphicsLayer = new GraphicsLayer({ title: 'Wien', listMode: 'hide' });
|
||||
viennaGraphicsLayer.add(viennaGraphic);
|
||||
|
||||
const ampelkarte_ews = new FeatureLayer({
|
||||
url: AMPEL_EWS_URL + '/0',
|
||||
title: 'Mögliche Einschränkungen',
|
||||
visible: false,
|
||||
listMode: 'show',
|
||||
opacity: 0.5,
|
||||
});
|
||||
|
||||
const resources_ews = new MapImageLayer({
|
||||
title: 'Ressourcen',
|
||||
url: RESOURCES_EWS_URL,
|
||||
visible: false,
|
||||
listMode: 'show',
|
||||
opacity: 0.5,
|
||||
});
|
||||
|
||||
// basemap in Viennese coordinate system due to tranformation inaccuracies from MGI to WGS84
|
||||
// default transformation in ArcGIS API from MGI to WGS84 is 1306
|
||||
// transformation 1618 is recommended
|
||||
const basemap_at = new WMTSLayer({
|
||||
url: BASEMAP_AT_URL,
|
||||
listMode: 'hide',
|
||||
});
|
||||
|
||||
let basemap = new Basemap({
|
||||
baseLayers: [basemap_at],
|
||||
title: 'basemap.at',
|
||||
id: 'basemap.at',
|
||||
spatialReference: { wkid: SRS },
|
||||
});
|
||||
|
||||
let arcgisMap = new ArcGISMap({
|
||||
basemap: basemap,
|
||||
layers: [resources_ews, ampelkarte_ews, viennaGraphicsLayer],
|
||||
});
|
||||
|
||||
const layerList = new LayerList({
|
||||
view,
|
||||
listItemCreatedFunction: addPanelInfo,
|
||||
});
|
||||
|
||||
const search = new Search({
|
||||
view,
|
||||
popupEnabled: true,
|
||||
});
|
||||
|
||||
const scaleBar = new ScaleBar({
|
||||
view: view,
|
||||
unit: 'metric',
|
||||
});
|
||||
|
||||
const zoom = new Zoom({
|
||||
view,
|
||||
layout: 'horizontal',
|
||||
});
|
||||
|
||||
const legend = new Legend({
|
||||
view,
|
||||
});
|
||||
|
||||
// register event handler for mouse clicks
|
||||
view.on('immediate-click', (event) => {
|
||||
if (setAddress && dispatch) {
|
||||
takeScreenshot(view, event.mapPoint, dispatch, true);
|
||||
getAddress(event.mapPoint, setAddress);
|
||||
identifyAllLayers(view, event.mapPoint, dispatch, 'EWS');
|
||||
}
|
||||
});
|
||||
|
||||
// add map to view
|
||||
view.map = arcgisMap;
|
||||
|
||||
// add UI components
|
||||
view.ui.components = [];
|
||||
if (!isMobile) {
|
||||
view.ui.add([zoom, search, layerList], 'top-left');
|
||||
view.ui.add(scaleBar, 'bottom-left');
|
||||
}
|
||||
|
||||
view.when(() => {
|
||||
handle = watch(
|
||||
() => view.map?.layers?.map((layer) => layer.visible),
|
||||
() => {
|
||||
if (view.map?.layers?.some((layer) => layer.title !== 'Wien' && layer.visible === true)) {
|
||||
view.ui?.add(legend, 'top-left');
|
||||
} else {
|
||||
view.ui?.remove(legend);
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
return () => {
|
||||
handle?.remove();
|
||||
view?.destroy();
|
||||
};
|
||||
}, [dispatch, isMobile, mapDiv]);
|
||||
|
||||
return (
|
||||
<div ref={mapDiv} className="absolute top-16 bottom-0 w-full">
|
||||
<PanelGrundlagenkarte address={address}></PanelGrundlagenkarte>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const addPanelInfo = (event: any) => {
|
||||
let item = event.item;
|
||||
switch (item.layer.id) {
|
||||
case 0:
|
||||
item.panel = {
|
||||
content: 'mittlere jährliche Bodentemperatur laut Satellitendaten (MODIS)',
|
||||
className: 'esri-icon-description',
|
||||
};
|
||||
break;
|
||||
case 1:
|
||||
item.panel = {
|
||||
content: 'mittlere Temperatur des Untergrunds für eine Tiefe von 0 bis 100 m',
|
||||
className: 'esri-icon-description',
|
||||
};
|
||||
break;
|
||||
case 2:
|
||||
item.panel = {
|
||||
content: 'mittlere konduktive Wärmeleitfähigkeit des Untergrunds für eine Tiefe von 0 bis 100 m',
|
||||
className: 'esri-icon-description',
|
||||
};
|
||||
break;
|
||||
case 3:
|
||||
item.panel = {
|
||||
content:
|
||||
'Entzugsleistung einer 100 m tiefen Einzelsonde im standortbezogenen Normbetrieb (Heizen und Kühlen mit Normbetriebsstunden eines typischen Wohngebäudes am Standort)',
|
||||
className: 'esri-icon-description',
|
||||
};
|
||||
break;
|
||||
case 4:
|
||||
item.panel = {
|
||||
content:
|
||||
'Entzugsleistung einer 100 m tiefen Einzelsonde im saisonalem Speicherbetrieb (die im Winter zur Heizung entzogene Wärme wird im Sommer vollständig wieder zurückgegeben)',
|
||||
className: 'esri-icon-description',
|
||||
};
|
||||
break;
|
||||
case 5:
|
||||
item.panel = {
|
||||
content:
|
||||
'flächenspezifische Jahresenergie eines 1156 m² großen und 100 m tiefen Sondenfeldes im standortbezogenen Normbetrieb (4 x 4 Sonden mit je 10 m Abstand - Heizen und Kühlen mit Normbetriebsstunden eines typischen Wohngebäudes am Standort)',
|
||||
className: 'esri-icon-description',
|
||||
};
|
||||
break;
|
||||
case 6:
|
||||
item.panel = {
|
||||
content:
|
||||
'flächenspezifische Jahresenergie eines 1156 m² großen und 100 m tiefen Sondenfeldes im saisonalem Speicherbetrieb (7 x 7 Sonden mit je 5 m Abstand - die im Winter zur Heizung entzogene Wärme wird im Sommer vollständig wieder zurückgegeben)',
|
||||
className: 'esri-icon-description',
|
||||
};
|
||||
break;
|
||||
case 7:
|
||||
item.panel = {
|
||||
content: 'mittlere Jahresbetriebsstunden für Heizen',
|
||||
className: 'esri-icon-description',
|
||||
};
|
||||
break;
|
||||
case 8:
|
||||
item.panel = {
|
||||
content: 'mittlere Jahresbetriebsstunden für Kühlen',
|
||||
className: 'esri-icon-description',
|
||||
};
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
};
|
9
app/ews/grundlagenkarte/page.tsx
Normal file
9
app/ews/grundlagenkarte/page.tsx
Normal file
|
@ -0,0 +1,9 @@
|
|||
'use client';
|
||||
|
||||
import dynamic from 'next/dynamic';
|
||||
|
||||
const MapComponent = dynamic(() => import('./grundlagenkarte'), { ssr: false });
|
||||
|
||||
export default function Grundlagenkarte() {
|
||||
return <MapComponent></MapComponent>;
|
||||
}
|
170
app/ews/grundlagenkarte/panel-grundlagenkarte.tsx
Normal file
170
app/ews/grundlagenkarte/panel-grundlagenkarte.tsx
Normal file
|
@ -0,0 +1,170 @@
|
|||
'use client';
|
||||
|
||||
import Image from 'next/image';
|
||||
import { useRef, useState } from 'react';
|
||||
import { useMediaQuery } from 'react-responsive';
|
||||
|
||||
import { useAppSelector } from '@/redux/hooks';
|
||||
|
||||
import { TableAmpelkarteEWS } from '@/app/ews/table-ampelkarte-ews';
|
||||
import Collapsible from '@/app/components/collapsible';
|
||||
import Footer from '@/app/components/footer';
|
||||
import Warning from '@/app/components/warning';
|
||||
|
||||
interface Resource {
|
||||
layerId: number;
|
||||
layerName: string;
|
||||
feature: any;
|
||||
}
|
||||
|
||||
export const textTemplates: { [key: number]: string[] } = {
|
||||
0: ['Die mittlere jährliche Bodentemperatur beträgt laut Satellitendaten (MODIS) rund ', ' °C.'],
|
||||
1: ['Die mittlere Temperatur des Untergrunds für eine Tiefe von 0 bis 100 m beträgt rund ', ' °C.'],
|
||||
2: [
|
||||
'Die mittlere konduktive Wärmeleitfähigkeit des Untergrunds für eine Tiefe von 0 bis 100 m beträgt rund ',
|
||||
' W/m/K.',
|
||||
],
|
||||
3: [
|
||||
'Die Entzugsleistung einer 100 m tiefen Einzelsonde im standortbezogenen Normbetrieb (Heizen und Kühlen mit Normbetriebsstunden eines typischen Wohngebäudes am Standort) beträgt am Grundstück rund ',
|
||||
' W/lfm.',
|
||||
],
|
||||
4: [
|
||||
'Die Entzugsleistung einer 100 m tiefen Einzelsonde im saisonalem Speicherbetrieb (die im Winter zur Heizung entzogene Wärme wird im Sommer vollständig wieder zurückgegeben) beträgt am Grundstück rund ',
|
||||
' W/lfm.',
|
||||
],
|
||||
|
||||
5: [
|
||||
`Die flächenspezifische Jahresenergie eines 1156 m² großen und 100 m tiefen Sondenfeldes im standortbezogenen Normbetrieb (4 x 4 Sonden mit je 10 m Abstand - Heizen und Kühlen mit Normbetriebsstunden eines typischen Wohngebäudes am Standort) beträgt rund `,
|
||||
' kWh/m²/a.',
|
||||
],
|
||||
6: [
|
||||
`Die flächenspezifische Jahresenergie eines 1156 m² großen und 100 m tiefen Sondenfeldes im saisonalem Speicherbetrieb (7 x 7 Sonden mit je 5 m Abstand - die im Winter zur Heizung entzogene Wärme wird im Sommer vollständig wieder zurückgegeben) beträgt rund `,
|
||||
' kWh/m²/a.',
|
||||
],
|
||||
};
|
||||
|
||||
const tableDataCSS = 'w-full break-words border-b border-solid border-gray-300';
|
||||
|
||||
export default function Panel({ address }: { address: string[] | null }) {
|
||||
const isMobile = useMediaQuery({ maxWidth: 640 });
|
||||
const [opened, setOpened] = useState<boolean>(true);
|
||||
|
||||
const innerRef = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
const ampelkarte = useAppSelector((store) => store.ampelkarteEWS.value);
|
||||
const resources: Resource[] = useAppSelector((store) => store.resourcesEWS.value);
|
||||
const screenshot = useAppSelector((store) => store.screenshot.value);
|
||||
|
||||
// format values
|
||||
const formatEWS = (layerId: number, layerName: string, value: string) => {
|
||||
if (value !== 'NoData') {
|
||||
if ([0, 1, 2, 4, 5, 6].includes(layerId)) {
|
||||
value = parseFloat(value).toFixed(1);
|
||||
} else {
|
||||
value = parseFloat(value).toFixed(0);
|
||||
}
|
||||
return textTemplates[layerId][0] + value + textTemplates[layerId][1];
|
||||
} else {
|
||||
return layerName + ': keine Daten';
|
||||
}
|
||||
};
|
||||
|
||||
const handleClick = () => {
|
||||
setOpened(!opened);
|
||||
if (innerRef && innerRef.current) {
|
||||
innerRef.current.classList.toggle('hidden');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`absolute top-4 right-4 ${opened && isMobile ? 'bottom-4' : ''} ${
|
||||
opened && resources && resources.length > 0 ? 'bottom-4' : ''
|
||||
} w-full md:w-1/3 xl:w-1/4 pl-8 md:pl-0`}
|
||||
>
|
||||
<div
|
||||
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"
|
||||
onClick={handleClick}
|
||||
>
|
||||
Abfrageergebnis <span className="float-right">{opened ? '-' : '+'}</span>
|
||||
</div>
|
||||
<div
|
||||
className={`max-h-[calc(100%-3rem)] px-2 xl:px-4 py-4 overflow-y-auto bg-white text-sm ${
|
||||
opened ? '' : 'hidden'
|
||||
}`}
|
||||
ref={innerRef}
|
||||
>
|
||||
{screenshot ? <Image src={screenshot} alt="Screenshot" width={1000} height={500}></Image> : null}
|
||||
{address && address.length > 0 ? (
|
||||
<>
|
||||
<table id="address-table" className="table-fixed w-full mt-3 mr-0 mb-3 ml-0">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
{address[0]}
|
||||
<br></br>
|
||||
{address[1]} {address[3]}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</>
|
||||
) : (
|
||||
<div className="pb-2">
|
||||
<Warning>Mit einem Klick in die Karte können Sie die geothermischen Daten abfragen.</Warning>
|
||||
</div>
|
||||
)}
|
||||
{resources && resources.length > 0 ? (
|
||||
<>
|
||||
<div className="pt-px"></div>
|
||||
<Collapsible title="Ressourcen" open={true}>
|
||||
<table id="resources-table" className="table-fixed w-full px-2 mb-4">
|
||||
<thead>
|
||||
<tr>
|
||||
<td></td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr className="w-full">
|
||||
<th className="pt-4 pb-2 text-center">Ressourcen für vordefinierte Erdwärmesondenanlage</th>
|
||||
</tr>
|
||||
{resources.slice(3, 7).map((result) => {
|
||||
return (
|
||||
<tr className="w-full" key={result.layerId}>
|
||||
<td className={tableDataCSS}>
|
||||
{formatEWS(
|
||||
result.layerId,
|
||||
result.layerName,
|
||||
result.feature.attributes['Classify.Pixel Value']
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
<tr className="w-full">
|
||||
<th className="pt-4 pb-2 text-center">Standortabhängige Parameter</th>
|
||||
</tr>
|
||||
{resources.slice(0, 3).map((result) => {
|
||||
return (
|
||||
<tr className="w-full" key={result.layerId}>
|
||||
<td className={tableDataCSS}>
|
||||
{formatEWS(
|
||||
result.layerId,
|
||||
result.layerName,
|
||||
result.feature.attributes['Classify.Pixel Value']
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</Collapsible>
|
||||
</>
|
||||
) : null}
|
||||
{ampelkarte && ampelkarte.length > 0 ? <TableAmpelkarteEWS results={ampelkarte}></TableAmpelkarteEWS> : null}
|
||||
<Footer></Footer>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
23
app/ews/page.tsx
Normal file
23
app/ews/page.tsx
Normal file
|
@ -0,0 +1,23 @@
|
|||
import Link from 'next/link';
|
||||
|
||||
export default function Home() {
|
||||
const linkCSS = 'hover:underline hover:decoration-red-700 hover:underline-offset-4';
|
||||
return (
|
||||
<main className="w-full">
|
||||
<div className="flex flex-col items-center pt-32">
|
||||
<ul className="mx-auto text-2xl font-semibold w-fit">
|
||||
<li>
|
||||
<Link href="/ews/grundlagenkarte" className={linkCSS}>
|
||||
Zur Grundlagenkarte
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link href="/ews/potenzialberechnung" className={linkCSS}>
|
||||
Zur Potenzialberechnung
|
||||
</Link>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
}
|
480
app/ews/potenzialberechnung/calculations-menu.tsx
Normal file
480
app/ews/potenzialberechnung/calculations-menu.tsx
Normal file
|
@ -0,0 +1,480 @@
|
|||
'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>
|
||||
);
|
||||
});
|
68
app/ews/potenzialberechnung/esri-ui-potenzialkarte.css
Normal file
68
app/ews/potenzialberechnung/esri-ui-potenzialkarte.css
Normal file
|
@ -0,0 +1,68 @@
|
|||
.esri-component {
|
||||
max-height: 80%;
|
||||
}
|
||||
|
||||
.esri-ui-corner .esri-component.esri-layer-list.esri-widget.esri-widget--panel {
|
||||
max-height: 25%;
|
||||
min-height: 45px;
|
||||
}
|
||||
|
||||
.esri-component.esri-legend.esri-widget.esri-widget--panel {
|
||||
max-height: 25%;
|
||||
min-height: 45px;
|
||||
}
|
||||
|
||||
.esri-ui-top-left.esri-ui-corner {
|
||||
height: 95%;
|
||||
}
|
||||
|
||||
.esri-ui-bottom-right.esri-ui-corner {
|
||||
height: 95%;
|
||||
}
|
||||
|
||||
a.nav-link {
|
||||
color: #444444;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
text-decoration: none;
|
||||
padding: 0 1rem;
|
||||
height: fit-content;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
a.nav-link.active span,
|
||||
a.nav-link:active,
|
||||
a.nav-link:hover {
|
||||
text-underline-offset: 3px;
|
||||
text-decoration: solid underline #a92a4e;
|
||||
}
|
||||
|
||||
div.esri-sketch__panel {
|
||||
justify-content: start;
|
||||
}
|
||||
|
||||
calcite-action[label='Select feature'] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
calcite-action[label='Feature auswählen'] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
calcite-icon {
|
||||
color: #a92a4e;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
.esri-layer-list {
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.esri-search {
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
.esri-legend__layer-child-table > .esri-legend__layer-caption {
|
||||
display: none;
|
||||
}
|
263
app/ews/potenzialberechnung/gridcomputer.js
Normal file
263
app/ews/potenzialberechnung/gridcomputer.js
Normal file
|
@ -0,0 +1,263 @@
|
|||
import Graphic from '@arcgis/core/Graphic';
|
||||
import * as geometryEngine from '@arcgis/core/geometry/geometryEngine';
|
||||
import Point from '@arcgis/core/geometry/Point';
|
||||
import Polygon from '@arcgis/core/geometry/Polygon';
|
||||
|
||||
import { view, pointGraphicsLayer, boundaryGraphicsLayer } from './potenzialkarte';
|
||||
|
||||
// grid points have to be at least 2.5 meters away from parcel boundary
|
||||
const distanceToBoundary = 2.5;
|
||||
|
||||
const drawPoint = (point, color = [255, 255, 255, 0.25]) => {
|
||||
pointGraphicsLayer.add(
|
||||
new Graphic({
|
||||
geometry: point,
|
||||
symbol: {
|
||||
type: 'simple-marker',
|
||||
color,
|
||||
},
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const drawPolygon = (polygon) => {
|
||||
boundaryGraphicsLayer.add(
|
||||
new Graphic({
|
||||
geometry: polygon,
|
||||
symbol: {
|
||||
type: 'simple-fill',
|
||||
color: [0, 0, 0, 0],
|
||||
outline: { color: '#009696', width: '2px' },
|
||||
},
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const dotProduct = (u, v) => {
|
||||
return u[0] * v[0] + u[1] * v[1];
|
||||
};
|
||||
|
||||
const determinant = (m) =>
|
||||
m.length === 1
|
||||
? m[0][0]
|
||||
: m.length === 2
|
||||
? m[0][0] * m[1][1] - m[0][1] * m[1][0]
|
||||
: m[0].reduce(
|
||||
(sum, element, i) =>
|
||||
sum + (-1) ** (i + 2) * element * determinant(m.slice(1).map((c) => c.filter((_, j) => i !== j))),
|
||||
0
|
||||
);
|
||||
|
||||
const spread = (u, v) => {
|
||||
return Math.pow(determinant([u, v]), 2) / (dotProduct(u, u) * dotProduct(v, v));
|
||||
};
|
||||
|
||||
const intersect = (x1, y1, x2, y2, x3, y3, x4, y4) => {
|
||||
// Check if no line is of length 0
|
||||
if ((x1 === x2 && y1 === y2) || (x3 === x4 && y3 === y4)) {
|
||||
return false;
|
||||
}
|
||||
const denominator = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1);
|
||||
// Lines are parallel
|
||||
if (denominator === 0) {
|
||||
return false;
|
||||
}
|
||||
// Return an object with the x and y coordinates of the intersection
|
||||
let ua = ((x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3)) / denominator;
|
||||
let x = x1 + ua * (x2 - x1);
|
||||
let y = y1 + ua * (y2 - y1);
|
||||
return [x, y];
|
||||
};
|
||||
|
||||
const computeParallelLine = (line, offset) => {
|
||||
const v = [line[1][0] - line[0][0], line[1][1] - line[0][1]];
|
||||
const n = [-v[1], v[0]];
|
||||
const il = 1 / Math.sqrt(Math.pow(n[0], 2) + Math.pow(n[1], 2));
|
||||
const nn = [il * n[0], il * n[1]];
|
||||
const p = [line[0][0] - offset * nn[0], line[0][1] - offset * nn[1]];
|
||||
const q = [p[0] + v[0], p[1] + v[1]];
|
||||
return [p, q];
|
||||
};
|
||||
|
||||
const computeParallelLines = (line, offset, maxOffset) => {
|
||||
const lines = [];
|
||||
let currentOffset = offset;
|
||||
while (Math.abs(currentOffset) < maxOffset) {
|
||||
const offsetPolyline = computeParallelLine(line, currentOffset);
|
||||
lines.push(offsetPolyline);
|
||||
currentOffset += offset;
|
||||
}
|
||||
return lines;
|
||||
};
|
||||
|
||||
export const distance = (point1, point2) => {
|
||||
return Math.sqrt(Math.pow(point2[0] - point1[0], 2) + Math.pow(point2[1] - point1[1], 2));
|
||||
};
|
||||
|
||||
const computeGridLines = (point1, point2, points, gridSpacing) => {
|
||||
let v = [point2[0] - point1[0], point2[1] - point1[1]];
|
||||
let n = [-v[0], -v[1]];
|
||||
let c = -n[0] * point1[0] - n[1] * point1[1];
|
||||
let longestDistanceRight = 0;
|
||||
let longestDistanceLeft = 0;
|
||||
|
||||
for (let point of points) {
|
||||
let projectedPoint = [
|
||||
(point[0] * n[1] * n[1] - n[0] * n[1] * point[1] - n[0] * c) / (n[0] * n[0] + n[1] * n[1]),
|
||||
(n[0] * n[0] * point[1] - n[0] * n[1] * point[0] - n[1] * c) / (n[0] * n[0] + n[1] * n[1]),
|
||||
];
|
||||
|
||||
const length = distance(point1, projectedPoint);
|
||||
|
||||
const m = [
|
||||
[point1[0], point1[1], 1],
|
||||
[point2[0], point2[1], 1],
|
||||
[projectedPoint[0], projectedPoint[1], 1],
|
||||
];
|
||||
|
||||
if (determinant(m) < 0 && length > longestDistanceRight) {
|
||||
longestDistanceRight = length;
|
||||
} else if (determinant(m) > 0 && length > longestDistanceLeft) {
|
||||
longestDistanceLeft = length;
|
||||
}
|
||||
}
|
||||
|
||||
const line = [point1, point2];
|
||||
|
||||
const linesRight = computeParallelLines([point1, point2], gridSpacing, longestDistanceRight);
|
||||
const linesLeft = computeParallelLines([point1, point2], -gridSpacing, longestDistanceLeft);
|
||||
const lines = linesRight.concat(linesLeft, [line]);
|
||||
|
||||
return lines;
|
||||
};
|
||||
|
||||
export const calculateGrid = (polygon, gridSpacing = 10, setPoints) => {
|
||||
boundaryGraphicsLayer.removeAll();
|
||||
|
||||
let offsetPolygon = geometryEngine.offset(polygon, distanceToBoundary, 'meters');
|
||||
|
||||
if (offsetPolygon) {
|
||||
// draw only outer polygon and ignore inner rings
|
||||
drawPolygon(
|
||||
new Polygon({
|
||||
rings: offsetPolygon.rings[0],
|
||||
spatialReference: view.spatialReference,
|
||||
})
|
||||
);
|
||||
|
||||
const points = offsetPolygon.rings[0];
|
||||
|
||||
// search for most perpendicular corner
|
||||
let widestSpread = 0;
|
||||
let index = 0;
|
||||
for (let i = 0; i < points.length - 1; i++) {
|
||||
let currentSpread, u, v;
|
||||
if (i === 0) {
|
||||
u = [points[points.length - 1][0] - points[0][0], points[points.length - 1][1] - points[0][1]];
|
||||
v = [points[1][0] - points[0][0], points[1][1] - points[0][1]];
|
||||
currentSpread = spread(u, v);
|
||||
} else if (i === points.length - 1) {
|
||||
u = [
|
||||
points[points.length - 2][0] - points[points.length - 1][0],
|
||||
points[points.length - 2][1] - points[points.length - 1][1],
|
||||
];
|
||||
v = [points[points.length - 1][0] - points[0][0], points[points.length - 1][1] - points[0][1]];
|
||||
currentSpread = spread(u, v);
|
||||
} else {
|
||||
u = [points[i - 1][0] - points[i][0], points[i - 1][1] - points[i][1]];
|
||||
v = [points[i + 1][0] - points[i][0], points[i + 1][1] - points[i][1]];
|
||||
currentSpread = spread(u, v);
|
||||
}
|
||||
|
||||
if (currentSpread > widestSpread) {
|
||||
widestSpread = currentSpread;
|
||||
index = i;
|
||||
}
|
||||
}
|
||||
|
||||
const v = [points[index + 1][0] - points[index][0], points[index + 1][1] - points[index][1]];
|
||||
const n = [-v[1], v[0]];
|
||||
const lines = computeGridLines(points[index], points[index + 1], points, gridSpacing);
|
||||
const orthogonalLines = computeGridLines(
|
||||
points[index],
|
||||
[points[index][0] + n[0], points[index][1] + n[1]],
|
||||
points,
|
||||
gridSpacing
|
||||
);
|
||||
|
||||
const gridPoints = [];
|
||||
for (let line1 of lines) {
|
||||
for (let line2 of orthogonalLines) {
|
||||
const intersection = intersect(
|
||||
line1[0][0],
|
||||
line1[0][1],
|
||||
line1[1][0],
|
||||
line1[1][1],
|
||||
line2[0][0],
|
||||
line2[0][1],
|
||||
line2[1][0],
|
||||
line2[1][1]
|
||||
);
|
||||
|
||||
if (intersection) {
|
||||
const intersectionPoint = new Point({
|
||||
x: intersection[0],
|
||||
y: intersection[1],
|
||||
spatialReference: view.spatialReference,
|
||||
});
|
||||
|
||||
gridPoints.push(intersectionPoint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// filter points that are inside the boundary polygon
|
||||
const filteredGridPoints = gridPoints.filter((point) => {
|
||||
let include = false;
|
||||
if (geometryEngine.intersects(point, offsetPolygon)) {
|
||||
include = true;
|
||||
}
|
||||
return include;
|
||||
});
|
||||
|
||||
// filter points that are not on buildings
|
||||
filterPointsByPixelAndDraw(filteredGridPoints, setPoints);
|
||||
}
|
||||
};
|
||||
|
||||
// select points that are not on buildings
|
||||
const filterPointsByPixelAndDraw = (points, setPoints) => {
|
||||
view.takeScreenshot().then((screenshot) => {
|
||||
let imageElement = document.createElement('img');
|
||||
imageElement.src = screenshot.dataUrl;
|
||||
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = view.width;
|
||||
canvas.height = view.height;
|
||||
|
||||
const context = canvas.getContext('2d');
|
||||
|
||||
imageElement.onload = () => {
|
||||
context.drawImage(imageElement, 0, 0);
|
||||
const selectedGridPoints = [];
|
||||
for (const point of points) {
|
||||
const screenPoint = view.toScreen(point);
|
||||
|
||||
if (screenPoint) {
|
||||
const { data } = context.getImageData(Math.round(screenPoint.x), Math.round(screenPoint.y), 1, 1);
|
||||
// buildings are identified by their RGB values; G === B
|
||||
if (!(data[1] === data[2])) {
|
||||
selectedGridPoints.push(point);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// draw points
|
||||
pointGraphicsLayer.removeAll();
|
||||
selectedGridPoints.map((point) => drawPoint(point));
|
||||
|
||||
// set grid points for the UI
|
||||
setPoints(selectedGridPoints.map((point) => [point.x, point.y]));
|
||||
};
|
||||
});
|
||||
};
|
9
app/ews/potenzialberechnung/page.tsx
Normal file
9
app/ews/potenzialberechnung/page.tsx
Normal file
|
@ -0,0 +1,9 @@
|
|||
'use client';
|
||||
|
||||
import dynamic from 'next/dynamic';
|
||||
|
||||
const MapComponent = dynamic(() => import('./potenzialkarte'), { ssr: false });
|
||||
|
||||
export default function Potenzialkarte() {
|
||||
return <MapComponent></MapComponent>;
|
||||
}
|
617
app/ews/potenzialberechnung/panel-potenzialkarte.tsx
Normal file
617
app/ews/potenzialberechnung/panel-potenzialkarte.tsx
Normal file
|
@ -0,0 +1,617 @@
|
|||
'use client';
|
||||
|
||||
import { useState, useEffect, useRef } from 'react';
|
||||
import { useMediaQuery } from 'react-responsive';
|
||||
import Image from 'next/image';
|
||||
|
||||
import { updateAmpelkarteEWS } from '@/redux/ampelkarteEWSSlice';
|
||||
import { updateResourcesEWS } from '@/redux/resourcesEWSSlice';
|
||||
import { updateCadastralData } from '@/redux/cadastreSlice';
|
||||
import { updateComputationResultEWS } from '@/redux/computationsEWSSlice';
|
||||
import { useAppSelector, useAppDispatch } from '@/redux/hooks';
|
||||
|
||||
import { initializeInfoPanelHandlers } from './potenzialkarte';
|
||||
import { TableAmpelkarteEWS } from '@/app/ews/table-ampelkarte-ews';
|
||||
import Collapsible from '@/app/components/collapsible';
|
||||
import Footer from '@/app/components/footer';
|
||||
import Warning from '@/app/components/warning';
|
||||
import Success from '@/app/components/success';
|
||||
|
||||
import { textTemplates } from '@/app/config/config';
|
||||
|
||||
import print from '@/app/utils/print';
|
||||
|
||||
const tableDataCSS = 'w-full break-words border-b border-solid border-gray-300';
|
||||
|
||||
export default function Panel() {
|
||||
const image = useRef<HTMLImageElement>(null);
|
||||
const image_bal = useRef<HTMLImageElement>(null);
|
||||
const image_borefield = useRef<HTMLImageElement>(null);
|
||||
|
||||
const innerRef = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
const [address, setAddress] = useState<string[]>([]);
|
||||
const [closenessWarning, setClosenessWarning] = useState<boolean>(false);
|
||||
const [outsideWarning, setOutsideWarning] = useState<boolean>(false);
|
||||
const [scaleWarning, setScaleWarning] = useState<boolean>(true);
|
||||
const [opened, setOpened] = useState<boolean>(true);
|
||||
|
||||
const ampelkarte: any = useAppSelector((store) => store.ampelkarteEWS.value);
|
||||
const resources: any = useAppSelector((store) => store.resourcesEWS.value);
|
||||
const cadastralData: any = useAppSelector((store) => store.cadastre.value);
|
||||
const computationResult: any = useAppSelector((store) => store.computationsEWS.value);
|
||||
const screenshot: any = useAppSelector((store) => store.screenshot.value);
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const isTablet = useMediaQuery({ maxWidth: 1024 });
|
||||
|
||||
// initialize query handlers
|
||||
useEffect(() => {
|
||||
dispatch(updateAmpelkarteEWS([]));
|
||||
dispatch(updateResourcesEWS([]));
|
||||
dispatch(updateCadastralData({}));
|
||||
dispatch(updateComputationResultEWS({}));
|
||||
initializeInfoPanelHandlers(setAddress, setClosenessWarning, setOutsideWarning, setScaleWarning);
|
||||
|
||||
return () => {
|
||||
setAddress([]);
|
||||
dispatch(updateAmpelkarteEWS([]));
|
||||
dispatch(updateResourcesEWS([]));
|
||||
dispatch(updateCadastralData({}));
|
||||
dispatch(updateComputationResultEWS({}));
|
||||
};
|
||||
}, [dispatch, isTablet]);
|
||||
|
||||
// print pdf report
|
||||
const clickHandler = () => {
|
||||
print(
|
||||
true,
|
||||
true,
|
||||
Object.keys(computationResult).length > 0,
|
||||
screenshot,
|
||||
image_bal,
|
||||
image,
|
||||
Object.keys(cadastralData).length > 0,
|
||||
closenessWarning || outsideWarning ? true : false,
|
||||
image_borefield,
|
||||
computationResult.calculationMode,
|
||||
'EWS',
|
||||
Object.keys(resources).length > 0
|
||||
);
|
||||
};
|
||||
|
||||
// format values
|
||||
const formatEWS = (layerId: number, layerName: string, value: string) => {
|
||||
if (value !== 'NoData') {
|
||||
if ([0, 1, 2, 4, 5, 6].includes(layerId)) {
|
||||
value = parseFloat(value).toFixed(1);
|
||||
} else {
|
||||
value = parseFloat(value).toFixed(0);
|
||||
}
|
||||
return (textTemplates as any)[layerId][0] + value + (textTemplates as any)[layerId][1];
|
||||
} else {
|
||||
return layerName + ': keine Daten';
|
||||
}
|
||||
};
|
||||
|
||||
const handleClick = () => {
|
||||
setOpened(!opened);
|
||||
if (innerRef && innerRef.current) {
|
||||
innerRef.current.classList.toggle('hidden');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`absolute top-4 right-4 ${opened && isTablet ? 'bottom-4' : ''} ${
|
||||
opened && Object.keys(computationResult).length > 1 ? 'bottom-4' : ''
|
||||
} w-full md:w-1/3 xl:w-1/4 pl-8 md:pl-0 z-50`}
|
||||
>
|
||||
<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"
|
||||
>
|
||||
Berechnungsergebnis <span className="float-right">{opened ? '-' : '+'}</span>
|
||||
</div>
|
||||
<div
|
||||
className={`h-[calc(100%-3rem)] px-2 xl:px-4 py-4 overflow-y-auto bg-white text-sm ${opened ? '' : 'hidden'}`}
|
||||
ref={innerRef}
|
||||
>
|
||||
<div className="flex justify-end">
|
||||
<button
|
||||
className={
|
||||
ampelkarte && ampelkarte.length > 0
|
||||
? 'text-white bg-red-700 p-2 border-0 outline-none cursor-pointer hover:bg-red-400 transition-colors mb-2 w-1/2 text-sm xl:text-base'
|
||||
: 'text-white bg-red-500 p-2 border-0 outline-none cursor-not-allowed hover:bg-red-200 transition-colors mb-2 w-1/2 text-sm xl:text-base'
|
||||
}
|
||||
onClick={clickHandler}
|
||||
>
|
||||
PDF erstellen
|
||||
</button>
|
||||
</div>
|
||||
{Object.keys(cadastralData).length > 0 ? (
|
||||
<table id="cadastral-data-table" className="table-fixed w-full p-2">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
Katastralgemeinde: {cadastralData.KG}
|
||||
<br></br>
|
||||
Grundstücksnummer: {cadastralData.GNR}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
) : null}
|
||||
{address && address.length > 0 ? (
|
||||
<table id="address-table" className="table-fixed w-full p-2">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>{address[0]}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
{address[1]} {address[3]}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
) : null}
|
||||
{Object.keys(cadastralData).length > 0 ? (
|
||||
<div className="grid grid-cols-3 p-2">
|
||||
<span className="border border-solid border-blue-700 my-auto mr-2"></span>
|
||||
<span className="col-span-2">Grundstücksgrenze</span>
|
||||
<span className="border border-solid border-teal-600 my-auto mr-2"></span>
|
||||
<span className="col-span-2">2,5-Meter-Abstand zur Grundstücksgrenze</span>
|
||||
</div>
|
||||
) : null}
|
||||
{scaleWarning ? (
|
||||
Object.keys(cadastralData).length === 0 ? (
|
||||
<div className="py-2">
|
||||
<Warning>Bitte zoomen Sie hinein um Ihr gewünschtes Grundstück durch Mausklick auszuwählen.</Warning>
|
||||
</div>
|
||||
) : (
|
||||
<div className="py-2">
|
||||
<Warning>Bitte zoomen Sie hinein, wenn Sie ein anderes Grundstück auswählen möchten.</Warning>
|
||||
</div>
|
||||
)
|
||||
) : Object.keys(cadastralData).length === 0 ? (
|
||||
<div className="py-2">
|
||||
<Success>Sie können jetzt Ihr Grundstück auswählen.</Success>
|
||||
</div>
|
||||
) : (
|
||||
<div className="py-2">
|
||||
<Success>Sie können jetzt die Berechnungen starten oder ein anderes Grundstück auswählen.</Success>
|
||||
</div>
|
||||
)}
|
||||
{closenessWarning || outsideWarning ? (
|
||||
<table id="warnings-table" className="table-fixed w-full mb-2">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
{closenessWarning ? (
|
||||
<Warning>Achtung: Mindestens ein Punkt liegt näher als fünf Meter zu einem anderen Punkt!</Warning>
|
||||
) : null}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
{outsideWarning ? (
|
||||
<Warning>Achtung: Mindestens ein Punkt liegt außerhalb der zugelassenen Grenzen!</Warning>
|
||||
) : null}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
) : null}
|
||||
{ampelkarte && ampelkarte.length > 0 ? <TableAmpelkarteEWS results={ampelkarte}></TableAmpelkarteEWS> : null}
|
||||
{Object.keys(computationResult).includes('error') ? (
|
||||
<Collapsible title="Berechnungsergebnisse" open={true}>
|
||||
<table id="calculations-output-table" className="table-fixed w-full p-2">
|
||||
<thead>
|
||||
<tr>
|
||||
<td></td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>{computationResult.error}</th>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div></div>
|
||||
</Collapsible>
|
||||
) : null}
|
||||
{resources && resources.length > 0 ? (
|
||||
<div className="hidden">
|
||||
<Collapsible title="Ressourcen" open={false}>
|
||||
<table id="resources-table" className="table-fixed w-full p-2">
|
||||
<thead>
|
||||
<tr>
|
||||
<td></td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>Ressourcen für vordefinierte Erdwärmesondenanlage</th>
|
||||
</tr>
|
||||
{resources.slice(3, 7).map((result: any) => {
|
||||
return (
|
||||
<tr key={result.layerId}>
|
||||
<td className={tableDataCSS}>
|
||||
{formatEWS(
|
||||
result.layerId,
|
||||
result.layerName,
|
||||
result.feature.attributes['Classify.Pixel Value']
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
<tr>
|
||||
<th>Standortabhängige Parameter</th>
|
||||
</tr>
|
||||
{resources.slice(0, 3).map((result: any) => {
|
||||
return (
|
||||
<tr key={result.layerId}>
|
||||
<td className={tableDataCSS}>
|
||||
{formatEWS(
|
||||
result.layerId,
|
||||
result.layerName,
|
||||
result.feature.attributes['Classify.Pixel Value']
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</Collapsible>
|
||||
</div>
|
||||
) : null}
|
||||
{Object.keys(computationResult).length > 1 ? (
|
||||
<Collapsible title="Berechnungsergebnisse" open={true}>
|
||||
<table id="calculations-input-table" className="table-fixed w-full p-2">
|
||||
<thead>
|
||||
<tr>
|
||||
<td></td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>Berechnungsvorgaben</th>
|
||||
</tr>
|
||||
<tr>
|
||||
{computationResult.calculationMode === 'norm' ? (
|
||||
<td>
|
||||
Es wurde keine Betriebsfunktion vom Benutzer vorgeben. Die Berechnung der möglichen Leistung des
|
||||
gewählten Sondenfelds erfolgt mit Norm-Jahresbetriebsstunden für Heizen und Kühlen am Standort
|
||||
eines typischen Wohngebäudes. Die Berechnung berücksichtigt zudem Untergrunddaten, fixe
|
||||
Sondenparameter und Temperaturgrenzen für die Fluidtemperatur in der Sonde. Ergebnisse sind die
|
||||
maximal erzielbare Leistung (kW) und Energiemenge (MWh/a) bei einem Betrieb von 20 Jahren.
|
||||
</td>
|
||||
) : (
|
||||
<td>
|
||||
Die Berechnung erfolgt für das gewählte Sondenfeld mit der benutzerdefinierten Betriebsfunktion,
|
||||
bestehend aus den Jahresbetriebsstunden und dem Leistungsverhältnis zwischen Heizen und Kühlen.
|
||||
Die Berechnung berücksichtigt zudem Untergrunddaten, fixe Sondenparameter und Temperaturgrenzen
|
||||
für die Fluidtemperatur in der Sonde. Ergebnisse sind die maximal erzielbare Leistung (kW) und
|
||||
Energiemenge (MWh/a) bei einem Betrieb von 20 Jahren.
|
||||
</td>
|
||||
)}
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Benutzereingabe</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Sondenanzahl: {computationResult.points}</td>
|
||||
</tr>
|
||||
{computationResult.meanBoreholeSpacing > 0 && (
|
||||
<tr>
|
||||
<td>Durchschnittlicher Sondenabstand: {computationResult.meanBoreholeSpacing} m</td>
|
||||
</tr>
|
||||
)}
|
||||
<tr>
|
||||
<td>Sondentiefe: {computationResult.boreDepth} m</td>
|
||||
</tr>
|
||||
{computationResult.calculationMode === 'user' && (
|
||||
<>
|
||||
<tr>
|
||||
<td>Jahresbetriebsstunden Heizen: {computationResult.BS_HZ} h</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Jahresbetriebsstunden Kühlen: {computationResult.BS_KL} h</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Heizleistung Gebäude: {computationResult.P_HZ} kW</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Kühlleistung Gebäude: {computationResult.P_KL} kW</td>
|
||||
</tr>
|
||||
</>
|
||||
)}
|
||||
<tr>
|
||||
<td>Vorlauftemperatur Heizung: {computationResult.T_radiator} °C</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Standortabhängige Parameter</th>
|
||||
</tr>
|
||||
{computationResult.calculationMode === 'norm' && (
|
||||
<>
|
||||
<tr>
|
||||
<td>Norm-Jahresbetriebsstunden Heizen: {computationResult.BS_HZ_Norm} h</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Norm-Jahresbetriebsstunden Kühlen: {computationResult.BS_KL_Norm} h</td>
|
||||
</tr>
|
||||
</>
|
||||
)}
|
||||
{resources && resources.length > 0 && computationResult && computationResult.GTcalc && (
|
||||
<>
|
||||
<tr>
|
||||
<td>
|
||||
Wärmeleitfähigkeit:{' '}
|
||||
{parseFloat(resources[2].feature.attributes['Classify.Pixel Value']).toFixed(1)} W/m/K
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Untergrundtemperatur: {computationResult.GTcalc.toFixed(1)} °C</td>
|
||||
</tr>
|
||||
</>
|
||||
)}
|
||||
{computationResult.points >= 10 &&
|
||||
(computationResult.Efactor_user > 1.1 || computationResult.Efactor_user < 0.9) && (
|
||||
<tr>
|
||||
<td>
|
||||
Hinweis: Größere Sondenfelder sollten mit einer möglichst ausgeglichenen Jahresenergiebilanz
|
||||
zwischen Heizen und Kühlen betrieben werden. Dadurch beeinflussen sich die Sonden gegenseitig
|
||||
kaum und der Sondenabstand kann auf ungefähr 5 Meter reduziert werden. Dies ermöglicht eine
|
||||
optimale thermische Nutzung des Untergrunds und es können höhere Leistungen erreicht werden.
|
||||
Überlegen Sie eine Verbesserung der Energiebilanz zwischen Heizen und Kühlen!
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
<div></div>
|
||||
<Image
|
||||
src={computationResult.imagehashSondenfeld}
|
||||
alt="Grafik mit Sondenfeld"
|
||||
width={384}
|
||||
height={288}
|
||||
ref={image_borefield}
|
||||
className="mx-auto"
|
||||
></Image>
|
||||
<table id="calculations-output-table" className="table-fixed w-full p-2">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>
|
||||
{computationResult.calculationMode === 'norm'
|
||||
? 'Berechnungsergebnisse für den Normbetrieb'
|
||||
: 'Berechnungsergebnisse für den benutzerdefinierten Betrieb '}
|
||||
</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>
|
||||
Heizbetrieb mit{' '}
|
||||
{computationResult.BS_HZ > 0 ? computationResult.BS_HZ : computationResult.BS_HZ_Norm} h/a
|
||||
</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Wärmeentzugsleistung aus Erdwärmesonden: {computationResult.P_HZ_user.toFixed(1)} kW</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
+ Elektrische Leistung Wärmepumpe (bei COP {computationResult.COP.toFixed(1)}):{' '}
|
||||
{computationResult.Pel_heatpump_user.toFixed(1)} kW
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>= Heizleistung Erdwärmeanlage: {computationResult.heizleistung.toFixed(1)} kW</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Jährlicher Wärmeentzug aus Erdwärmesonden: {computationResult.E_HZ_user.toFixed(1)} MWh/a</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
+ Strombedarf Wärmepumpe (bei JAZ {computationResult.SCOP.toFixed(1)}):{' '}
|
||||
{computationResult.Eel_heatpump_user.toFixed(1)} MWh/a
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>= Heizarbeit Erdwärmeanlage: {computationResult.heizarbeit.toFixed(1)} MWh/a</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>
|
||||
Kühlbetrieb mit{' '}
|
||||
{computationResult.BS_KL > 0 ? computationResult.BS_KL : computationResult.BS_KL_Norm} h/a
|
||||
</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Wärmeeintragsleistung in Erdwärmesonden: {computationResult.P_KL_user.toFixed(1)} kW</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
- Elektrische Leistung Wärmepumpe (bei EER {computationResult.EER.toFixed(1)}):{' '}
|
||||
{computationResult.Pel_chiller_user.toFixed(1)} kW
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>= Kühlleistung Erdwärmeanlage: {computationResult.kuehlleistung.toFixed(1)} kW</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Jährlicher Wärmeeintrag in Erdwärmesonde: {computationResult.E_KL_user.toFixed(1)} MWh/a</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
- Strombedarf Wärmepumpe (bei SEER {computationResult.SEER.toFixed(1)}
|
||||
): {computationResult.Eel_chiller_user.toFixed(1)} MWh/a
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>= Kühlarbeit Erdwärmeanlage: {computationResult.kuehlarbeit.toFixed(1)} MWh/a</td>
|
||||
</tr>
|
||||
{computationResult.cover > 0 && (
|
||||
<tr>
|
||||
<td>Deckungsgrad gesamt: {computationResult.cover} %</td>
|
||||
</tr>
|
||||
)}
|
||||
{computationResult.balanced === 0 && (
|
||||
<tr>
|
||||
<td>
|
||||
Ihre Energie- und Leistungsvorgaben des Gebäudes für Heizen und Kühlen bewirken eine ausgeglichene
|
||||
Betriebsweise im Erdsondenfeld. Die Auslegung ist optimal für den saisonalen Speicherbetrieb
|
||||
geeignet.
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
<div></div>
|
||||
<Image
|
||||
src={computationResult.imagehash}
|
||||
alt="Grafik mit Berechnungsergebnissen"
|
||||
ref={image}
|
||||
width={384}
|
||||
height={288}
|
||||
className="mx-auto"
|
||||
></Image>
|
||||
<div></div>
|
||||
</Collapsible>
|
||||
) : null}
|
||||
{Object.keys(computationResult).length > 1 && computationResult.balanced === 1 ? (
|
||||
<Collapsible title="Berechnungsergebnisse für den saisonalen Speicherbetrieb" open={true}>
|
||||
<table id="calculations-bal-output-table" className="table-fixed w-full p-2">
|
||||
<thead>
|
||||
<tr>
|
||||
<td></td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
Die Berechnung erfolgt für das gewählte Sondenfeld im saisonalen Speicherbetrieb. Die folgenden
|
||||
Ergebnisse beziehen sich auf eine automatisch angepasste Betriebsweise, wobei das Sondenfeld über
|
||||
das Jahr gesehen gleich stark be- und entladen wird.{' '}
|
||||
{computationResult.Efactor_user >= 1 &&
|
||||
`Um eine ausgeglichene Betriebsweise zu erreichen ist eine zusätzliche Wärmequelle notwendig. Die Zusatzquelle (Solar, Luft, Abwärme, etc.) kann außerhalb der Heizsaison betrieben werden und gleicht die Jahresenergiebilanz der Erdwärmesonden aus. Alternativ zur Zusatzquelle kann auch eine Reduktion der vorgegebenen Heizleistung mit klassischen Wärmequellen (Fernwärme, Biomasse, etc.) in Betracht gezogen werden. Versuchen Sie auch eine manuelle Anpassung der Leistungsvorgabe!`}
|
||||
{computationResult.Efactor_user < 1 &&
|
||||
`Um eine ausgeglichene Betriebsweise zu erreichen ist eine zusätzliche Wärmesenke notwendig. Als Zusatzsenke bietet sich eine Wärmeversorgung benachbarter Objekte an, wodurch die Jahresenergiebilanz in den Erdwärmesonden ausgeglichen werden kann.`}
|
||||
</td>
|
||||
</tr>
|
||||
{computationResult.meanBoreholeSpacing > 5 && (
|
||||
<>
|
||||
<tr>
|
||||
<td>
|
||||
Hinweis: Im saisonalen Speicherbetrieb kann der Sondenabstand auf ungefähr fünf Meter reduziert
|
||||
werden ohne dass sich die einzelnen Erdwärmesonden nennenswert gegenseitig beeinflussen. Somit
|
||||
kann der Flächenbedarf ohne signifikante Einbußen der Sondenleistung reduziert werden. Versuchen
|
||||
Sie eine Reduktion des Sondenabstands!
|
||||
</td>
|
||||
</tr>
|
||||
</>
|
||||
)}
|
||||
</tbody>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>Berechnungsergebnisse</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>
|
||||
Heizbetrieb {computationResult.Efactor_user < 1 && 'und Zusatzsenke'} mit{' '}
|
||||
{computationResult.BS_HZ_bal} h/a
|
||||
</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Wärmeentzugsleistung aus Erdwärmesonden: {computationResult.P_HZ_bal.toFixed(1)} kW</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
+ Elektrische Leistung Wärmepumpe (bei COP {computationResult.COP_bal.toFixed(1)}):{' '}
|
||||
{computationResult.Pel_heatpump_bal.toFixed(1)} kW
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>= Heizleistung Erdwärmeanlage: {computationResult.heizleistungBal.toFixed(1)} kW</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Jährlicher Wärmeentzug aus Erdwärmesonden: {computationResult.E_HZ_bal.toFixed(1)} MWh/a</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
+ Strombedarf Wärmepumpe (bei JAZ {computationResult.SCOP_bal.toFixed(1)}):{' '}
|
||||
{computationResult.Eel_heatpump_bal.toFixed(1)} MWh/a
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>= Heizarbeit Erdwärmeanlage: {computationResult.heizarbeitBal.toFixed(1)} MWh/a</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>
|
||||
Kühlbetrieb {computationResult.Efactor_user >= 1 && 'und Zusatzquelle'} mit{' '}
|
||||
{computationResult.BS_KL_bal} h/a
|
||||
</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Wärmeeintragsleistung in Erdwärmesonden: {computationResult.P_KL_bal.toFixed(1)} kW</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
- Elektrische Leistung Wärmepumpe (bei EER {computationResult.EER_bal.toFixed(1)}):{' '}
|
||||
{computationResult.Pel_chiller_bal.toFixed(1)} kW
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>= Kühlleistung Erdwärmeanlage: {computationResult.kuehlleistungBal.toFixed(1)} kW</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Jährlicher Wärmeeintrag in Erdwärmesonden: {computationResult.E_KL_bal.toFixed(1)} MWh/a</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
- Strombedarf Wärmepumpe (bei SEER {computationResult.SEER_bal.toFixed(1)}):{' '}
|
||||
{computationResult.Eel_chiller_bal.toFixed(1)} MWh/a
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>= Kühlarbeit Erdwärmeanlage: {computationResult.kuehlarbeitBal.toFixed(1)} MWh/a</td>
|
||||
</tr>
|
||||
{computationResult.cover > 0 && (
|
||||
<tr>
|
||||
<td>Deckungsgrad: {computationResult.cover_bal} %</td>
|
||||
</tr>
|
||||
)}
|
||||
{computationResult.Efactor_user >= 1 ? (
|
||||
<tr>
|
||||
<td>
|
||||
Bei einer ausgeglichenen Betriebsweise mit einer zusätzlichen Wärmequelle kann die Heizarbeit um{' '}
|
||||
{computationResult.cover_rise.toFixed(1)} % gesteigert werden.
|
||||
</td>
|
||||
</tr>
|
||||
) : (
|
||||
<tr>
|
||||
<td>
|
||||
Bei einer ausgeglichenen Betriebsweise mit einer zusätzlichen Wärmesenke kann die Kühlarbeit um{' '}
|
||||
{computationResult.cover_rise.toFixed(1)} % gesteigert werden.
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
<div></div>
|
||||
<Image
|
||||
src={computationResult.imagehashBal}
|
||||
alt="Grafik mit bilanzierten Berechnungsergebnissen"
|
||||
ref={image_bal}
|
||||
width={384}
|
||||
height={288}
|
||||
className="mx-auto"
|
||||
></Image>
|
||||
<div></div>
|
||||
</Collapsible>
|
||||
) : null}
|
||||
<Footer></Footer>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
487
app/ews/potenzialberechnung/potenzialkarte.tsx
Normal file
487
app/ews/potenzialberechnung/potenzialkarte.tsx
Normal file
|
@ -0,0 +1,487 @@
|
|||
import { useRef, useEffect, useState, Dispatch, SetStateAction } from 'react';
|
||||
import { useMediaQuery } from 'react-responsive';
|
||||
|
||||
import ArcGISMap from '@arcgis/core/Map';
|
||||
import Extent from '@arcgis/core/geometry/Extent';
|
||||
import MapView from '@arcgis/core/views/MapView';
|
||||
import Basemap from '@arcgis/core/Basemap';
|
||||
import MapImageLayer from '@arcgis/core/layers/MapImageLayer';
|
||||
import WMTSLayer from '@arcgis/core/layers/WMTSLayer';
|
||||
import Search from '@arcgis/core/widgets/Search';
|
||||
import ScaleBar from '@arcgis/core/widgets/ScaleBar';
|
||||
import Sketch from '@arcgis/core/widgets/Sketch';
|
||||
import GraphicsLayer from '@arcgis/core/layers/GraphicsLayer';
|
||||
import Graphic from '@arcgis/core/Graphic';
|
||||
import * as geometryEngine from '@arcgis/core/geometry/geometryEngine';
|
||||
import * as reactiveUtils from '@arcgis/core/core/reactiveUtils';
|
||||
import Zoom from '@arcgis/core/widgets/Zoom';
|
||||
import FeatureLayer from '@arcgis/core/layers/FeatureLayer';
|
||||
import Polygon from '@arcgis/core/geometry/Polygon';
|
||||
import SimpleFillSymbol from '@arcgis/core/symbols/SimpleFillSymbol';
|
||||
import SimpleMarkerSymbol from '@arcgis/core/symbols/SimpleMarkerSymbol';
|
||||
import esriConfig from '@arcgis/core/config';
|
||||
import Point from '@arcgis/core/geometry/Point';
|
||||
|
||||
import { updateComputationResultEWS } from '@/redux/computationsEWSSlice';
|
||||
import { updateCadastralData } from '@/redux/cadastreSlice';
|
||||
|
||||
import identifyAllLayers from '../../utils/identify';
|
||||
import queryCadastre from '../../utils/queryCadastre';
|
||||
import getAddress from '../../utils/getAddress';
|
||||
|
||||
import './esri-ui-potenzialkarte.css';
|
||||
import { Vienna, Austria } from '@/public/borders-vienna-austria';
|
||||
|
||||
import ReduxProvider from '@/redux/provider';
|
||||
import { useAppDispatch } from '@/redux/hooks';
|
||||
|
||||
import PanelPotenzialkarte from './panel-potenzialkarte';
|
||||
import CalculationsMenu from './calculations-menu';
|
||||
|
||||
// import layer URLs and spatial reference system wkid
|
||||
import { BASEMAP_AT_URL, AMPEL_EWS_URL, RESOURCES_EWS_URL, SRS } from '@/app/config/config';
|
||||
|
||||
// set path for local assets
|
||||
esriConfig.assetsPath = '/assets';
|
||||
|
||||
const scaleLimit = 1000;
|
||||
const gridSpacing = 10;
|
||||
|
||||
// initialize info panel callback functions
|
||||
let setAddress: Dispatch<SetStateAction<string[]>>,
|
||||
setClosenessWarning: Dispatch<SetStateAction<boolean>>,
|
||||
setOutsideWarning: Dispatch<SetStateAction<boolean>>,
|
||||
setScaleWarning: Dispatch<SetStateAction<boolean>>;
|
||||
export function initializeInfoPanelHandlers(
|
||||
setAddressCallback: Dispatch<SetStateAction<string[]>>,
|
||||
setClosenessWarningCallback: Dispatch<SetStateAction<boolean>>,
|
||||
setOutsideWarningCallback: Dispatch<SetStateAction<boolean>>,
|
||||
setScaleWarningCallback: Dispatch<SetStateAction<boolean>>
|
||||
) {
|
||||
setAddress = setAddressCallback;
|
||||
setClosenessWarning = setClosenessWarningCallback;
|
||||
setOutsideWarning = setOutsideWarningCallback;
|
||||
setScaleWarning = setScaleWarningCallback;
|
||||
}
|
||||
|
||||
// initialize calculations menu callback functions
|
||||
let setPoints: Dispatch<SetStateAction<number[][]>>, setPolygon: Dispatch<SetStateAction<Polygon | null>>;
|
||||
export function initializeCalculationsMenuHandlers(
|
||||
setPointsCallback: Dispatch<SetStateAction<number[][]>>,
|
||||
setPolygonCallback: Dispatch<SetStateAction<Polygon | null>>
|
||||
) {
|
||||
setPoints = setPointsCallback;
|
||||
setPolygon = setPolygonCallback;
|
||||
}
|
||||
|
||||
// initialize the map view container
|
||||
export let view: MapView;
|
||||
export let pointGraphicsLayer: GraphicsLayer;
|
||||
export let boundaryGraphicsLayer: GraphicsLayer;
|
||||
export default function MapComponent() {
|
||||
const dispatch = useAppDispatch();
|
||||
const isMobile = useMediaQuery({ maxWidth: 640 });
|
||||
|
||||
const mapDiv = useRef<HTMLDivElement | null>(null);
|
||||
const calculationsMenuRef = useRef<HTMLDivElement | null>(null);
|
||||
const loaded = useRef<boolean>(false);
|
||||
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
|
||||
useEffect(() => {
|
||||
let handle: IHandle;
|
||||
if (mapDiv.current && calculationsMenuRef.current) {
|
||||
view = new MapView({
|
||||
container: mapDiv.current,
|
||||
extent: new Extent({
|
||||
xmin: -19000,
|
||||
ymin: 325000,
|
||||
xmax: 29000,
|
||||
ymax: 360000,
|
||||
spatialReference: { wkid: SRS },
|
||||
}),
|
||||
spatialReference: { wkid: SRS },
|
||||
popupEnabled: false,
|
||||
popup: {
|
||||
dockEnabled: false,
|
||||
dockOptions: {
|
||||
buttonEnabled: false,
|
||||
},
|
||||
collapseEnabled: false,
|
||||
viewModel: {
|
||||
includeDefaultActions: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// listen to scale changes
|
||||
handle = reactiveUtils.watch(
|
||||
() => view.scale,
|
||||
() => {
|
||||
if (view.scale < scaleLimit) {
|
||||
setScaleWarning(false);
|
||||
} else {
|
||||
setScaleWarning(true);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// graphic layers
|
||||
let viennaGraphic = new Graphic({
|
||||
geometry: new Polygon({ rings: [Austria, Vienna], spatialReference: { wkid: SRS } }),
|
||||
symbol: {
|
||||
type: 'simple-fill',
|
||||
color: [209, 213, 219, 0.65],
|
||||
style: 'solid',
|
||||
outline: {
|
||||
color: 'white',
|
||||
width: 0,
|
||||
},
|
||||
} as unknown as SimpleFillSymbol,
|
||||
});
|
||||
const viennaGraphicsLayer = new GraphicsLayer({ title: 'Wien', listMode: 'hide' });
|
||||
viennaGraphicsLayer.add(viennaGraphic);
|
||||
|
||||
pointGraphicsLayer = new GraphicsLayer({
|
||||
title: 'Planungslayer - Punkte',
|
||||
listMode: 'hide',
|
||||
});
|
||||
|
||||
boundaryGraphicsLayer = new GraphicsLayer({
|
||||
title: 'EWS - innere Grenze',
|
||||
listMode: 'hide',
|
||||
});
|
||||
|
||||
const polygonGraphicsLayer = new GraphicsLayer({
|
||||
title: 'Grundstücksgrenze',
|
||||
listMode: 'hide',
|
||||
});
|
||||
|
||||
const highlightGraphicsLayer = new GraphicsLayer({
|
||||
title: 'Grundstücksgrenze',
|
||||
listMode: 'hide',
|
||||
});
|
||||
|
||||
// thematic layers
|
||||
const ampelkarte_ews = new FeatureLayer({
|
||||
url: AMPEL_EWS_URL + '/0',
|
||||
title: 'Mögliche Einschränkungen',
|
||||
visible: false,
|
||||
listMode: 'show',
|
||||
opacity: 0.5,
|
||||
});
|
||||
|
||||
const ews = new MapImageLayer({
|
||||
title: 'Ressourcen',
|
||||
url: RESOURCES_EWS_URL,
|
||||
visible: false,
|
||||
listMode: 'show',
|
||||
});
|
||||
|
||||
// basemap in Viennese coordinate system due to tranformation inaccuracies from MGI to WGS84
|
||||
// default transformation in ArcGIS API from MGI to WGS84 is 1306
|
||||
// transformation 1618 is recommended
|
||||
const basemap_at = new WMTSLayer({
|
||||
url: BASEMAP_AT_URL,
|
||||
activeLayer: {
|
||||
id: 'geolandbasemap',
|
||||
},
|
||||
listMode: 'hide',
|
||||
serviceMode: 'KVP',
|
||||
});
|
||||
|
||||
let basemap = new Basemap({
|
||||
baseLayers: [basemap_at],
|
||||
title: 'basemap',
|
||||
id: 'basemap',
|
||||
spatialReference: { wkid: SRS },
|
||||
});
|
||||
|
||||
let arcgisMap = new ArcGISMap({
|
||||
basemap: basemap,
|
||||
layers: [
|
||||
ews,
|
||||
ampelkarte_ews,
|
||||
pointGraphicsLayer,
|
||||
boundaryGraphicsLayer,
|
||||
polygonGraphicsLayer,
|
||||
highlightGraphicsLayer,
|
||||
viennaGraphicsLayer,
|
||||
],
|
||||
});
|
||||
|
||||
const search = new Search({
|
||||
view,
|
||||
popupEnabled: true,
|
||||
});
|
||||
|
||||
const scaleBar = new ScaleBar({
|
||||
view: view,
|
||||
unit: 'metric',
|
||||
});
|
||||
|
||||
const zoom = new Zoom({
|
||||
view,
|
||||
layout: 'horizontal',
|
||||
});
|
||||
|
||||
let sketch = new Sketch({
|
||||
// container: calculationsMenuRef.current,
|
||||
layer: pointGraphicsLayer,
|
||||
view: view,
|
||||
availableCreateTools: ['point'],
|
||||
visibleElements: {
|
||||
selectionTools: {
|
||||
'lasso-selection': true,
|
||||
'rectangle-selection': true,
|
||||
},
|
||||
settingsMenu: false,
|
||||
undoRedoMenu: false,
|
||||
},
|
||||
creationMode: 'single',
|
||||
});
|
||||
|
||||
if (loaded.current) {
|
||||
sketch.container = calculationsMenuRef.current;
|
||||
}
|
||||
loaded.current = true;
|
||||
|
||||
sketch.on('create', (event) => {
|
||||
if (event.tool === 'point' && event.state === 'complete') {
|
||||
// point symbol
|
||||
const symbol = {
|
||||
type: 'simple-marker',
|
||||
color: [255, 255, 255, 0.25],
|
||||
};
|
||||
|
||||
// delete default point symbol
|
||||
pointGraphicsLayer.remove(event.graphic);
|
||||
|
||||
let point = event.graphic.geometry as Point;
|
||||
let points;
|
||||
|
||||
// add point to current list of points
|
||||
setPoints((currentPoints) => {
|
||||
points = [...currentPoints, [point.x, point.y]];
|
||||
return points;
|
||||
});
|
||||
|
||||
// check if point is too close to any other point
|
||||
pointGraphicsLayer.graphics.forEach((graphic) => {
|
||||
if (geometryEngine.distance(graphic.geometry, point) < 5) {
|
||||
setClosenessWarning(true);
|
||||
}
|
||||
});
|
||||
|
||||
// check if point is outside the parcel
|
||||
if (!geometryEngine.intersects(point, boundaryGraphicsLayer.graphics.at(0).geometry)) {
|
||||
setOutsideWarning(true);
|
||||
}
|
||||
|
||||
// draw point graphic
|
||||
pointGraphicsLayer.add(
|
||||
new Graphic({
|
||||
geometry: point,
|
||||
symbol,
|
||||
})
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
sketch.on('update', (event) => {
|
||||
if (event.state === 'start') {
|
||||
// on first load update starts with two identical points
|
||||
// cancel second update
|
||||
if (sketch.updateGraphics.length === 2 && sketch.updateGraphics.at(0) === sketch.updateGraphics.at(1)) {
|
||||
sketch.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
if (event.state === 'complete') {
|
||||
// list of updated points
|
||||
let allPoints = pointGraphicsLayer.graphics.map((graphic) => graphic.geometry as Point);
|
||||
|
||||
// update state of calculations menu
|
||||
setPoints(allPoints.map((point) => [point.x, point.y]).toArray());
|
||||
|
||||
// check if all points are inside the boundary
|
||||
let allPointsInside = true;
|
||||
allPoints.forEach((point) => {
|
||||
if (!geometryEngine.intersects(point, boundaryGraphicsLayer.graphics.at(0).geometry)) {
|
||||
allPointsInside = false;
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
setOutsideWarning(!allPointsInside);
|
||||
|
||||
// check if points are too close
|
||||
let tooClose = false;
|
||||
allPoints.forEach((firstPoint) => {
|
||||
allPoints.forEach((secondPoint) => {
|
||||
if (secondPoint !== firstPoint) {
|
||||
if (geometryEngine.distance(firstPoint, secondPoint, 'meters') <= 4.9) {
|
||||
tooClose = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
setClosenessWarning(tooClose);
|
||||
}
|
||||
});
|
||||
|
||||
// register event handler for mouse clicks
|
||||
view.on('click', ({ mapPoint }) => {
|
||||
// at application start if no polygon is drawn yet
|
||||
if (polygonGraphicsLayer.graphics.length === 0) {
|
||||
startNewQuery(mapPoint);
|
||||
} else {
|
||||
// check if point is selected
|
||||
view
|
||||
.hitTest(view.toScreen(mapPoint), {
|
||||
include: pointGraphicsLayer,
|
||||
})
|
||||
.then(({ results }) => {
|
||||
if (results && results.length > 0) {
|
||||
// if point was selected then start update
|
||||
const pointGraphic = (results.at(0) as any)?.graphic;
|
||||
sketch.update(pointGraphic);
|
||||
} else {
|
||||
if (
|
||||
!geometryEngine.intersects(mapPoint, polygonGraphicsLayer.graphics.at(0).geometry) &&
|
||||
view.scale < scaleLimit
|
||||
) {
|
||||
// if click was outside parcel and no point was selected then start new query
|
||||
view.openPopup({
|
||||
title: 'Wollen Sie ein neues Grundstück auswählen?',
|
||||
location: mapPoint,
|
||||
content: getPopupContent(mapPoint),
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// listen to move event
|
||||
view.on('pointer-move', (event) => {
|
||||
let mapPoint = view.toMap({ x: event.x, y: event.y });
|
||||
view
|
||||
.hitTest(view.toScreen(mapPoint), {
|
||||
include: pointGraphicsLayer,
|
||||
})
|
||||
.then(({ results }) => {
|
||||
// if user hovers over a point
|
||||
if (results.length > 0 && event.buttons === 0 && !sketch.createGraphic) {
|
||||
const graphicHits = results?.filter((hitResult) => hitResult.type === 'graphic');
|
||||
|
||||
let graphic = (graphicHits.at(0) as any)?.graphic;
|
||||
|
||||
sketch.update(graphic);
|
||||
|
||||
let pointGraphic = new Graphic({
|
||||
geometry: graphic.geometry,
|
||||
symbol: {
|
||||
type: 'simple-marker',
|
||||
size: '30px',
|
||||
color: null,
|
||||
outline: { color: '#00ffff' },
|
||||
} as unknown as SimpleMarkerSymbol,
|
||||
});
|
||||
highlightGraphicsLayer.add(pointGraphic);
|
||||
document.body.style.cursor = 'pointer';
|
||||
} else {
|
||||
highlightGraphicsLayer.removeAll();
|
||||
document.body.style.cursor = 'default';
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const getPopupContent = (mapPoint: Point) => {
|
||||
const popupContent = document.createElement('div');
|
||||
popupContent.className = 'flex justify-center';
|
||||
|
||||
const popupButton = document.createElement('button');
|
||||
popupButton.textContent = 'Ja';
|
||||
popupButton.className = 'text-white bg-gray-700 p-2 border-none outline-none cursor-pointer w-2/3';
|
||||
|
||||
popupButton.addEventListener('mouseover', handleMouseOver);
|
||||
function handleMouseOver(this: HTMLButtonElement) {
|
||||
this.className = 'text-white bg-gray-300 p-2 border-none outline-none cursor-pointer w-2/3';
|
||||
}
|
||||
|
||||
popupButton.addEventListener('mouseout', handleMouseOut);
|
||||
function handleMouseOut(this: HTMLButtonElement) {
|
||||
this.className = 'text-white bg-gray-700 p-2 border-none outline-none cursor-pointer w-2/3';
|
||||
}
|
||||
|
||||
popupContent.append(popupButton);
|
||||
|
||||
popupButton.onclick = () => {
|
||||
startNewQuery(mapPoint);
|
||||
view.popup.close();
|
||||
};
|
||||
|
||||
return popupContent;
|
||||
};
|
||||
|
||||
const startNewQuery = (mapPoint: Point) => {
|
||||
// remove existing graphics
|
||||
polygonGraphicsLayer.removeAll();
|
||||
boundaryGraphicsLayer.removeAll();
|
||||
pointGraphicsLayer.removeAll();
|
||||
|
||||
// clear state
|
||||
setClosenessWarning(false);
|
||||
setOutsideWarning(false);
|
||||
setPolygon(null);
|
||||
setPoints([]);
|
||||
|
||||
// initialize store in case there was a previous computation
|
||||
dispatch(updateCadastralData({}));
|
||||
dispatch(updateComputationResultEWS({}));
|
||||
|
||||
// start new layer queries
|
||||
identifyAllLayers(view, mapPoint, dispatch, 'EWS');
|
||||
getAddress(mapPoint, setAddress);
|
||||
|
||||
if (view.scale < scaleLimit) {
|
||||
// query cadastral data
|
||||
queryCadastre(view, polygonGraphicsLayer, mapPoint, dispatch, setPolygon, setPoints, gridSpacing);
|
||||
}
|
||||
};
|
||||
|
||||
// add map to view
|
||||
view.map = arcgisMap;
|
||||
|
||||
// add UI components
|
||||
view.ui.components = [];
|
||||
if (!isMobile) {
|
||||
view.ui.add([zoom, search], 'top-left');
|
||||
view.ui.add(scaleBar, 'bottom-left');
|
||||
}
|
||||
}
|
||||
|
||||
return () => {
|
||||
view?.destroy();
|
||||
handle?.remove();
|
||||
};
|
||||
}, [dispatch, isMobile]);
|
||||
|
||||
return (
|
||||
<div ref={mapDiv} className="absolute top-16 bottom-0 w-full">
|
||||
<PanelPotenzialkarte></PanelPotenzialkarte>
|
||||
<ReduxProvider>
|
||||
<CalculationsMenu isLoading={setLoading} ref={calculationsMenuRef}></CalculationsMenu>
|
||||
</ReduxProvider>
|
||||
{loading ? (
|
||||
<div
|
||||
className="absolute left-1/2 top-1/2 inline-block h-12 w-12 animate-spin rounded-full border-4 border-solid border-gray-950 border-r-transparent"
|
||||
role="status"
|
||||
></div>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
300
app/ews/table-ampelkarte-ews.tsx
Normal file
300
app/ews/table-ampelkarte-ews.tsx
Normal file
|
@ -0,0 +1,300 @@
|
|||
import Collapsible from '@/app/components/collapsible';
|
||||
|
||||
const tableDataCSS = 'w-full break-words border-b border-solid border-gray-300 px-2 text-sm';
|
||||
let einschraenkungen_erlaeuterungen: any = {};
|
||||
|
||||
const einschraenkungenText: any = {
|
||||
Naturschutz: `Sind durch ein Vorhaben ein Schutzgebiet (z.B. Nationalpark, Europaschutzgebiet, Landschaftsschutzgebiet, Naturschutzgebiet, geschützter Landschafsteil),
|
||||
ein Schutzobjekt (Naturdenkmal) oder streng (geschützte) Tier- und Pflanzenarten betroffen, ist jedenfalls rechtzeitig mit der Magistratsabteilung 22 - Umweltschutz Kontakt aufzunehmen,
|
||||
um eine allfällige naturschutzbehördliche Bewilligungspflicht abklären zu können. Auf folgenden Seiten sind sämtliche Informationen zu Schutzgebieten und –objekten sowie zu den Artenschutzbestimmungen zu finden:`,
|
||||
Naturschutz_links: [
|
||||
'https://www.wien.gv.at/umweltschutz/naturschutz/gebiet/schutzgebiete.html#schutzgebiete',
|
||||
'https://www.wien.gv.at/umweltschutz/naturschutz/biotop/artenschutz.html',
|
||||
],
|
||||
'Artesisch gespannte Brunnen': `In einem Umkreis von 100 m Radius wurde mit Bohrungen artesisch gespanntes Grundwasser angetroffen. Bei der Planung und Durchführung zukünftiger Bohrungen in diesem Bereich muss dies berücksichtigt werden.`,
|
||||
Karstzonen: `Am Standort treten verkarstungsfähige Gesteine auf. Bohrungen können daher Hohlräume antreffen.`,
|
||||
Altlasten: `Es befindet sich eine Altlast am Standort. Weitere Informationen über die Altlasten sind im Altlasten-GIS unter folgendem Link zu finden: `,
|
||||
Altlasten_links: 'https://secure.umweltbundesamt.at/altlasten/?servicehandler=publicgis',
|
||||
'Unterirdische Bauwerke': `In einem Umkreis von 2 m befindet sich ein unterirdisches Verkehrsbauwerk (entsprechend Digitalem Verkehrsgraph - GIP). Auf diesen Flächen und im Nahbereich ist der Einsatz von Erdwärmesonden ausgeschlossen.
|
||||
Es kann jedoch noch weitere unterirdische Bauwerke wie Tiefgaragen, Verbindungsgänge oder Einbauten im restlichen Stadtgebiet geben, die den Einsatz von Erdwärmesonden beschränken können.`,
|
||||
};
|
||||
|
||||
const hinweiseText: any = {
|
||||
Grundwasserchemismus: {
|
||||
'Eisen- und Manganausfällung': `Am Standort kann es zu Eisen- und Manganausfällungen in den Brunnen kommen.
|
||||
Diese können mit bestimmten technischen Maßnahmen wie der Luftfreihaltung des Systems von der Entnahme bis zur Rückgabe des Wassers reduziert oder vermieden werden.
|
||||
Jedenfalls wird im Vorfeld eine chemische Analyse des Grundwassers am Standort empfohlen.`,
|
||||
Metallkorrosion: `Am Standort kann es zur Metallkorrosion in den Brunnen kommen. Dies kann mit bestimmten technischen Maßnahmen, wie einem Wärmetauscher aus rostfreiem Stahl bzw. einem zusätzlichen Trennwärmetauscher reduziert oder vermieden werden. Jedenfalls wird im Vorfeld eine chemische Analyse des Grundwassers am Standort empfohlen.`,
|
||||
'Keine Daten': `Auf Grund fehlender chemischer Wasseranalysen können keine Aussagen zur Grundwasserchemie getroffen werden.`,
|
||||
'Kein Risiko durch GW-Chemismus': 'Kein Risiko durch GW-Chemismus',
|
||||
},
|
||||
Naturdenkmal: `Am Standort gibt es Naturdenkmäler, die eine Nutzung der Oberflächennahen Geothermie eventuell beschränken können.`,
|
||||
'Gespannte Grundwasserzone': `Am Standort können gespannte Grundwasserverhältnisse auftreten. Bei der Planung und Durchführung zukünftiger Bohrungen in diesem Bereich muss dies berücksichtigt werden.`,
|
||||
Gasvorkommen: `Am Standort können oberflächennahe Gasvorkommen nicht ausgeschlossen werden. Bei der Planung und Durchführung zukünftiger Bohrungen in diesem Bereich muss dies berücksichtigt werden.`,
|
||||
'Mehrere Grundwasserstockwerke': `Am Standort können mehrere Grundwasserstockwerke angetroffen werden.`,
|
||||
};
|
||||
|
||||
const getEinschraenkungen = (attributes: any) => {
|
||||
let einschraenkungen = [];
|
||||
|
||||
// Wasserschutz- und Wasserschongebiete
|
||||
switch (attributes['EWS_01']) {
|
||||
case 'Grün':
|
||||
einschraenkungen.push(
|
||||
<tr className="w-full" key={einschraenkungen.length}>
|
||||
<td className={tableDataCSS}>{attributes['Para_01']}</td>
|
||||
<td className={tableDataCSS}>{getAmpelText('Grün')}</td>
|
||||
</tr>
|
||||
);
|
||||
break;
|
||||
case 'Gelb':
|
||||
einschraenkungen.push(
|
||||
<tr className="w-full" key={einschraenkungen.length}>
|
||||
<td className={tableDataCSS}>
|
||||
{attributes['Para_01']}: {attributes['Kat_01']}
|
||||
</td>
|
||||
<td className={tableDataCSS}>{getAmpelText('Gelb')}</td>
|
||||
</tr>
|
||||
);
|
||||
break;
|
||||
case 'Magenta':
|
||||
einschraenkungen.push(
|
||||
<tr className="w-full" key={einschraenkungen.length}>
|
||||
<td className={tableDataCSS}>
|
||||
{attributes['Para_01']}: {attributes['Kat_01']}
|
||||
</td>
|
||||
<td className={tableDataCSS}>{getAmpelText('Magenta')}</td>
|
||||
</tr>
|
||||
);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// Altlasten
|
||||
switch (attributes['EWS_02']) {
|
||||
case 'Gelb':
|
||||
einschraenkungen_erlaeuterungen[attributes['Para_02']] = (
|
||||
<>
|
||||
{einschraenkungenText[attributes['Para_02']]}
|
||||
<a href={einschraenkungenText[attributes['Para_02'] + '_links']}>
|
||||
{einschraenkungenText[attributes['Para_02'] + '_links']}
|
||||
</a>
|
||||
</>
|
||||
);
|
||||
einschraenkungen.push(
|
||||
<tr key={einschraenkungen.length} className="w-full">
|
||||
<td className={tableDataCSS}>
|
||||
{attributes['Para_02']}
|
||||
<sup>{Object.keys(einschraenkungen_erlaeuterungen).length}</sup>
|
||||
</td>
|
||||
<td className={tableDataCSS}>{getAmpelText('Gelb')}</td>
|
||||
</tr>
|
||||
);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// Artesisch gespannte Brunnen
|
||||
switch (attributes['EWS_03']) {
|
||||
case 'Gelb':
|
||||
einschraenkungen_erlaeuterungen[attributes['Para_03']] = einschraenkungenText[attributes['Para_03']];
|
||||
einschraenkungen.push(
|
||||
<tr key={einschraenkungen.length} className="w-full">
|
||||
<td className={tableDataCSS}>
|
||||
{attributes['Para_03']}
|
||||
<sup>{Object.keys(einschraenkungen_erlaeuterungen).length}</sup>
|
||||
</td>
|
||||
<td className={tableDataCSS}>{getAmpelText('Gelb')}</td>
|
||||
</tr>
|
||||
);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// Bergbaugebiete
|
||||
switch (attributes['EWS_04']) {
|
||||
case 'Gelb':
|
||||
einschraenkungen.push(
|
||||
<tr key={einschraenkungen.length} className="w-full">
|
||||
<td className={tableDataCSS}>{attributes['Para_04']}</td>
|
||||
<td className={tableDataCSS}>{getAmpelText('Gelb')}</td>
|
||||
</tr>
|
||||
);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// Karstzonen
|
||||
switch (attributes['EWS_05']) {
|
||||
case 'Gelb':
|
||||
einschraenkungen_erlaeuterungen[attributes['Para_05']] = einschraenkungenText[attributes['Para_05']];
|
||||
einschraenkungen.push(
|
||||
<tr key={einschraenkungen.length} className="w-full">
|
||||
<td className={tableDataCSS}>{attributes['Para_05']}</td>
|
||||
<td className={tableDataCSS}>{getAmpelText('Gelb')}</td>
|
||||
</tr>
|
||||
);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// Naturschutz
|
||||
switch (attributes['EWS_06']) {
|
||||
case 'Gelb':
|
||||
einschraenkungen_erlaeuterungen[attributes['Para_06']] = (
|
||||
<>
|
||||
Sind durch ein Vorhaben ein Schutzgebiet (z.B. Nationalpark, Europaschutzgebiet, Landschaftsschutzgebiet,
|
||||
Naturschutzgebiet, geschützter Landschafsteil), ein Schutzobjekt (Naturdenkmal) oder streng (geschützte) Tier-
|
||||
und Pflanzenarten betroffen, ist jedenfalls rechtzeitig mit der Magistratsabteilung 22 - Umweltschutz Kontakt
|
||||
aufzunehmen, um eine allfällige naturschutzbehördliche Bewilligungspflicht abklären zu können. Auf folgenden
|
||||
Seiten sind sämtliche Informationen zu Schutzgebieten und –objekten sowie zu den Artenschutzbestimmungen zu
|
||||
finden:
|
||||
<ul className="pt-2">
|
||||
<li>
|
||||
<a href="https://www.wien.gv.at/umweltschutz/naturschutz/gebiet/schutzgebiete.html#schutzgebiete">
|
||||
https://www.wien.gv.at/umweltschutz/naturschutz/gebiet/schutzgebiete.html#schutzgebiete
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://www.wien.gv.at/umweltschutz/naturschutz/biotop/artenschutz.html">
|
||||
https://www.wien.gv.at/umweltschutz/naturschutz/biotop/artenschutz.html
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</>
|
||||
);
|
||||
|
||||
einschraenkungen.push(
|
||||
<tr key={einschraenkungen.length} className="w-full">
|
||||
<td className={tableDataCSS}>
|
||||
{attributes['Para_06']}
|
||||
<sup>{Object.keys(einschraenkungen_erlaeuterungen).length}</sup>:{' '}
|
||||
{attributes['Kat_06'].replaceAll(',', ', ')}
|
||||
</td>
|
||||
<td className={tableDataCSS}>{getAmpelText('Gelb')}</td>
|
||||
</tr>
|
||||
);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return einschraenkungen;
|
||||
};
|
||||
|
||||
const getHinweise = (attributes: any) => {
|
||||
let hinweise = [];
|
||||
if (attributes['Hinweis_01']) {
|
||||
hinweise.push(
|
||||
<tr key={hinweise.length} className="w-full">
|
||||
<td className={tableDataCSS}>{hinweiseText[attributes['Hinweis_01']]}</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
|
||||
if (attributes['Hinweis_02']) {
|
||||
hinweise.push(
|
||||
<tr key={hinweise.length} className="w-full">
|
||||
<td className={tableDataCSS}>{hinweiseText[attributes['Hinweis_02']]}</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
|
||||
if (attributes['Hinweis_03']) {
|
||||
hinweise.push(
|
||||
<tr key={hinweise.length} className="w-full">
|
||||
<td className={tableDataCSS}>{hinweiseText[attributes['Hinweis_03']]}</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
|
||||
return hinweise;
|
||||
};
|
||||
|
||||
export const getAmpelText = (color: string) => {
|
||||
switch (color) {
|
||||
case 'Grün':
|
||||
return 'Nutzung generell möglich';
|
||||
case 'Gelb':
|
||||
return 'Genauere Beurteilung notwendig';
|
||||
case 'Magenta':
|
||||
return 'Nutzung generell nicht möglich';
|
||||
default:
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
export const TableAmpelkarteEWS = ({ results }: { results: any }) => {
|
||||
let einschraenkungen: any, hinweise: any;
|
||||
einschraenkungen_erlaeuterungen = {};
|
||||
|
||||
results.forEach((result: any) => {
|
||||
const attributes = result.feature.attributes;
|
||||
if (result.layerId === 0) {
|
||||
einschraenkungen = getEinschraenkungen(attributes);
|
||||
} else {
|
||||
hinweise = getHinweise(attributes);
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
{einschraenkungen && einschraenkungen.length > 0 ? (
|
||||
<Collapsible title="Einschränkungen" open={true}>
|
||||
<table id={'einschraenkungen-table'} className="table-fixed w-full mb-4">
|
||||
<thead>
|
||||
<tr>
|
||||
<td colSpan={2}></td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>{einschraenkungen}</tbody>
|
||||
<tbody>
|
||||
{Object.keys(einschraenkungen_erlaeuterungen).length > 0 &&
|
||||
Object.keys(einschraenkungen_erlaeuterungen).map((key, index) => {
|
||||
return (
|
||||
<tr key={key} className="w-full">
|
||||
<td colSpan={2} className="w-full break-words border-b border-solid border-gray-300 px-2 text-xs">
|
||||
{index + 1}: {einschraenkungen_erlaeuterungen[key]}
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</Collapsible>
|
||||
) : (
|
||||
<Collapsible title="Einschränkungen" open={true}>
|
||||
<table id={'einschraenkungen-table'} className="table-fixed w-full mb-4">
|
||||
<thead>
|
||||
<tr>
|
||||
<td></td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr className="w-full">
|
||||
<td className={tableDataCSS}>An diesem Standort sind keine Einschränkungen bekannt.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</Collapsible>
|
||||
)}
|
||||
{hinweise && hinweise.length > 0 ? (
|
||||
<Collapsible title="Hinweise" open={true}>
|
||||
<table id={'hinweise-table'} className="table-fixed w-full mb-4">
|
||||
<thead>
|
||||
<tr>
|
||||
<td></td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>{hinweise}</tbody>
|
||||
</table>
|
||||
</Collapsible>
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
};
|
BIN
app/favicon.ico
BIN
app/favicon.ico
Binary file not shown.
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 15 KiB |
|
@ -4,7 +4,7 @@
|
|||
|
||||
:root {
|
||||
--foreground-rgb: 0, 0, 0;
|
||||
--background-start-rgb: 214, 219, 220;
|
||||
--background-start-rgb: 255, 255, 255;
|
||||
--background-end-rgb: 255, 255, 255;
|
||||
}
|
||||
|
||||
|
@ -16,12 +16,21 @@
|
|||
}
|
||||
}
|
||||
|
||||
@import '@arcgis/core/assets/esri/themes/light/main.css';
|
||||
|
||||
html,
|
||||
body {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
body {
|
||||
color: rgb(var(--foreground-rgb));
|
||||
background: linear-gradient(
|
||||
to bottom,
|
||||
transparent,
|
||||
rgb(var(--background-end-rgb))
|
||||
)
|
||||
rgb(var(--background-start-rgb));
|
||||
background: linear-gradient(to bottom, transparent, rgb(var(--background-end-rgb))) rgb(var(--background-start-rgb));
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
|
25
app/gwwp/esri-ui-grundlagenkarte.css
Normal file
25
app/gwwp/esri-ui-grundlagenkarte.css
Normal file
|
@ -0,0 +1,25 @@
|
|||
.esri-component {
|
||||
max-height: 80%;
|
||||
}
|
||||
|
||||
.esri-ui-corner .esri-component.esri-layer-list.esri-widget.esri-widget--panel {
|
||||
}
|
||||
|
||||
.esri-component.esri-legend.esri-widget.esri-widget--panel {
|
||||
}
|
||||
|
||||
.esri-ui-top-left.esri-ui-corner {
|
||||
height: 95%;
|
||||
}
|
||||
|
||||
.esri-layer-list {
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.esri-search {
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
.esri-legend__layer-child-table > .esri-legend__layer-caption {
|
||||
display: none;
|
||||
}
|
180
app/gwwp/grundlagenkarte-gwwp.tsx
Normal file
180
app/gwwp/grundlagenkarte-gwwp.tsx
Normal file
|
@ -0,0 +1,180 @@
|
|||
import { useEffect, useRef, useState } from 'react';
|
||||
import { useMediaQuery } from 'react-responsive';
|
||||
|
||||
import ArcGISMap from '@arcgis/core/Map';
|
||||
import Extent from '@arcgis/core/geometry/Extent';
|
||||
import MapView from '@arcgis/core/views/MapView';
|
||||
import Basemap from '@arcgis/core/Basemap';
|
||||
import MapImageLayer from '@arcgis/core/layers/MapImageLayer';
|
||||
import WMTSLayer from '@arcgis/core/layers/WMTSLayer';
|
||||
import SpatialReference from '@arcgis/core/geometry/SpatialReference';
|
||||
import Search from '@arcgis/core/widgets/Search';
|
||||
import ScaleBar from '@arcgis/core/widgets/ScaleBar';
|
||||
import GraphicsLayer from '@arcgis/core/layers/GraphicsLayer';
|
||||
import Graphic from '@arcgis/core/Graphic';
|
||||
import LayerList from '@arcgis/core/widgets/LayerList';
|
||||
import Zoom from '@arcgis/core/widgets/Zoom';
|
||||
import Legend from '@arcgis/core/widgets/Legend';
|
||||
import FeatureLayer from '@arcgis/core/layers/FeatureLayer';
|
||||
import Polygon from '@arcgis/core/geometry/Polygon';
|
||||
import esriConfig from '@arcgis/core/config';
|
||||
import { watch } from '@arcgis/core/core/reactiveUtils';
|
||||
import SimpleFillSymbol from '@arcgis/core/symbols/SimpleFillSymbol';
|
||||
|
||||
import './esri-ui-grundlagenkarte.css';
|
||||
import { Vienna, Austria } from '@/public/borders-vienna-austria';
|
||||
import { BASEMAP_AT_URL, AMPEL_GWWP_URL, RESOURCES_GWWP_URL, SRS } from '@/app/config/config';
|
||||
import getAddress from '@/app/utils/getAddress';
|
||||
import identifyAllLayers from '@/app/utils/identify';
|
||||
import takeScreenshot from '@/app/utils/screenshot';
|
||||
import { useAppDispatch } from '@/redux/hooks';
|
||||
|
||||
import PanelGrundlagenkarte from './panel-grundlagenkarte-gwwp';
|
||||
|
||||
// set path for local assets
|
||||
esriConfig.assetsPath = '/assets';
|
||||
|
||||
export default function MapComponent() {
|
||||
const dispatch = useAppDispatch();
|
||||
const isMobile = useMediaQuery({ maxWidth: 480 });
|
||||
|
||||
const mapDiv = useRef<HTMLDivElement | null>(null);
|
||||
const [address, setAddress] = useState<string[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
let view: MapView;
|
||||
let handle: IHandle;
|
||||
if (mapDiv.current) {
|
||||
view = new MapView({
|
||||
container: mapDiv.current,
|
||||
extent: new Extent({
|
||||
xmin: -19000,
|
||||
ymin: 325000,
|
||||
xmax: 29000,
|
||||
ymax: 360000,
|
||||
spatialReference: new SpatialReference({ wkid: SRS }),
|
||||
}),
|
||||
popupEnabled: false,
|
||||
});
|
||||
|
||||
let viennaGraphic = new Graphic({
|
||||
geometry: new Polygon({ rings: [Austria, Vienna], spatialReference: { wkid: SRS } }),
|
||||
symbol: {
|
||||
type: 'simple-fill',
|
||||
color: [209, 213, 219, 0.65],
|
||||
style: 'solid',
|
||||
outline: {
|
||||
color: 'white',
|
||||
width: 0,
|
||||
},
|
||||
} as unknown as SimpleFillSymbol,
|
||||
});
|
||||
|
||||
// graphic layers
|
||||
let viennaGraphicsLayer = new GraphicsLayer({ title: 'Wien', listMode: 'hide' });
|
||||
viennaGraphicsLayer.add(viennaGraphic);
|
||||
|
||||
const ampelkarte_gwwp = new FeatureLayer({
|
||||
url: AMPEL_GWWP_URL + '/0',
|
||||
title: 'Mögliche Einschränkungen',
|
||||
visible: false,
|
||||
listMode: 'show',
|
||||
opacity: 0.5,
|
||||
});
|
||||
|
||||
const resources_gwwp = new MapImageLayer({
|
||||
title: 'Ressourcen',
|
||||
url: RESOURCES_GWWP_URL,
|
||||
visible: false,
|
||||
listMode: 'show',
|
||||
opacity: 0.5,
|
||||
});
|
||||
|
||||
// basemap in Viennese coordinate system due to tranformation inaccuracies from MGI to WGS84
|
||||
// default transformation in ArcGIS API from MGI to WGS84 is 1306
|
||||
// transformation 1618 is recommended
|
||||
const basemap_at = new WMTSLayer({
|
||||
url: BASEMAP_AT_URL,
|
||||
listMode: 'hide',
|
||||
});
|
||||
|
||||
let basemap = new Basemap({
|
||||
baseLayers: [basemap_at],
|
||||
title: 'basemap.at',
|
||||
id: 'basemap.at',
|
||||
spatialReference: { wkid: SRS },
|
||||
});
|
||||
|
||||
let arcgisMap = new ArcGISMap({
|
||||
basemap: basemap,
|
||||
layers: [resources_gwwp, ampelkarte_gwwp, viennaGraphicsLayer],
|
||||
});
|
||||
|
||||
const layerList = new LayerList({
|
||||
view,
|
||||
});
|
||||
|
||||
const search = new Search({
|
||||
view,
|
||||
popupEnabled: true,
|
||||
});
|
||||
|
||||
const scaleBar = new ScaleBar({
|
||||
view: view,
|
||||
unit: 'metric',
|
||||
});
|
||||
|
||||
const zoom = new Zoom({
|
||||
view,
|
||||
layout: 'horizontal',
|
||||
});
|
||||
|
||||
const legend = new Legend({
|
||||
view,
|
||||
});
|
||||
|
||||
// register event handler for mouse clicks
|
||||
view.on('immediate-click', (event) => {
|
||||
if (setAddress) {
|
||||
takeScreenshot(view, event.mapPoint, dispatch, true);
|
||||
getAddress(event.mapPoint, setAddress);
|
||||
identifyAllLayers(view, event.mapPoint, dispatch, 'GWWP');
|
||||
}
|
||||
});
|
||||
|
||||
// add map to view
|
||||
view.map = arcgisMap;
|
||||
|
||||
// add UI components
|
||||
view.ui.components = [];
|
||||
if (!isMobile) {
|
||||
view.ui.add([zoom, search, layerList], 'top-left');
|
||||
view.ui.add(scaleBar, 'bottom-left');
|
||||
}
|
||||
|
||||
view.when(() => {
|
||||
handle = watch(
|
||||
() => view.map?.layers?.map((layer) => layer.visible),
|
||||
() => {
|
||||
if (view.map?.layers?.some((layer) => layer.title !== 'Wien' && layer.visible === true)) {
|
||||
view.ui?.add(legend, 'top-left');
|
||||
} else {
|
||||
view.ui?.remove(legend);
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
return () => {
|
||||
handle?.remove();
|
||||
view?.destroy();
|
||||
};
|
||||
}, [dispatch, isMobile]);
|
||||
|
||||
return (
|
||||
<div ref={mapDiv} className="absolute top-16 bottom-0 w-full">
|
||||
<PanelGrundlagenkarte address={address}></PanelGrundlagenkarte>
|
||||
</div>
|
||||
);
|
||||
}
|
9
app/gwwp/page.tsx
Normal file
9
app/gwwp/page.tsx
Normal file
|
@ -0,0 +1,9 @@
|
|||
'use client';
|
||||
|
||||
import dynamic from 'next/dynamic';
|
||||
|
||||
const MapComponent = dynamic(() => import('./grundlagenkarte-gwwp'), { ssr: false });
|
||||
|
||||
export default function Grundlagenkarte() {
|
||||
return <MapComponent></MapComponent>;
|
||||
}
|
184
app/gwwp/panel-grundlagenkarte-gwwp.tsx
Normal file
184
app/gwwp/panel-grundlagenkarte-gwwp.tsx
Normal file
|
@ -0,0 +1,184 @@
|
|||
'use client';
|
||||
|
||||
import { useEffect, useState, useRef } from 'react';
|
||||
import { useMediaQuery } from 'react-responsive';
|
||||
import Image from 'next/image';
|
||||
|
||||
import { updateAmpelkarteGWWP } from '@/redux/ampelkarteGWWPSlice';
|
||||
import { updateResourcesGWWP } from '@/redux/resourcesGWWPSlice';
|
||||
import { updateScreenshot } from '@/redux/screenshotSlice';
|
||||
import { useAppSelector, useAppDispatch } from '@/redux/hooks';
|
||||
|
||||
import Collapsible from '@/app/components/collapsible';
|
||||
import Footer from '@/app/components/footer';
|
||||
import Warning from '@/app/components/warning';
|
||||
|
||||
import { TableAmpelkarteGWWP } from './table-ampelkarte-gwwp';
|
||||
|
||||
const textTemplates = {
|
||||
0: [
|
||||
`Die flächenspezifische Jahresenergie für eine thermische Grundwassernutzung mit ausgeglichener Jahresbilanz, wobei die im Winter zur Heizung entzogene Wärme im Sommer vollständig wieder zurückgegeben wird,
|
||||
abhängig von der bestehenden Grundwassertemperatur und einer minimalen Rückgabetemperatur von 5 °C und einer maximalen Rückgabetemperatur von 18 °C beträgt rund `,
|
||||
' kWh/m²/a',
|
||||
],
|
||||
1: [
|
||||
`Die flächenspezifische Jahresenergie für eine thermische Grundwassernutzung im Heiz- und Kühlbetrieb bei Normbetriebsstunden,
|
||||
abhängig von der bestehenden Grundwassertemperatur und einer minimalen Rückgabetemperatur von 5 °C und einer maximalen Rückgabetemperatur von 18 °C beträgt rund `,
|
||||
' kWh/m²/a',
|
||||
],
|
||||
2: ['Der Grundwasserspiegel ist am Grundstück in einer Tiefe von rund ', ' m zu erwarten.'],
|
||||
3: ['Das Grundwasser ist am Grundstück rund ', ' m mächtig.'],
|
||||
4: ['Die hydraulische Leitfähigkeit (kf-Wert) beträgt am Grundstück rund ', ' m/s.'],
|
||||
5: ['Die maximale Jahrestemperatur des Grundwassers (für das Jahr 2020) liegt bei ', ' °C.'],
|
||||
6: ['Die mittlere Jahrestemperatur des Grundwassers (für das Jahr 2020) liegt bei ', ' °C.'],
|
||||
7: ['Die minimale Jahrestemperatur des Grundwassers (für das Jahr 2020) liegt bei ', ' °C.'],
|
||||
8: [
|
||||
'Die maximale Pumpleistung eines Brunnenpaars mit 50 m Abstand zwischen Entnahme- und Rückgabebrunnen beträgt rund ',
|
||||
' l/s.',
|
||||
],
|
||||
9: [
|
||||
'Die maximale Volllast-Leistung eines Brunnenpaars mit 50 m Abstand zwischen Entnahme- und Rückgabebrunnen beträgt rund ',
|
||||
' kW.',
|
||||
],
|
||||
};
|
||||
|
||||
export default function Panel({ address }: { address: string[] }) {
|
||||
const dispatch = useAppDispatch();
|
||||
const isMobile = useMediaQuery({ maxWidth: 640 });
|
||||
|
||||
const [opened, setOpened] = useState<boolean>(true);
|
||||
|
||||
const innerRef = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
const ampelkarte = useAppSelector((store) => store.ampelkarteGWWP.value);
|
||||
const resources = useAppSelector((store) => store.resourcesGWWP.value);
|
||||
const screenshot = useAppSelector((store) => store.screenshot.value);
|
||||
|
||||
// initialize query handlers
|
||||
useEffect(() => {
|
||||
dispatch(updateAmpelkarteGWWP([]));
|
||||
dispatch(updateResourcesGWWP([]));
|
||||
dispatch(updateScreenshot(''));
|
||||
|
||||
return () => {
|
||||
dispatch(updateAmpelkarteGWWP([]));
|
||||
dispatch(updateResourcesGWWP([]));
|
||||
dispatch(updateScreenshot(''));
|
||||
};
|
||||
}, [dispatch, isMobile]);
|
||||
|
||||
// format values
|
||||
const formatGWWP = (layerId: number, layerName: string, value: string) => {
|
||||
if (value !== 'NoData') {
|
||||
if ([5, 6, 7].includes(layerId)) {
|
||||
value = parseFloat(value).toFixed(1);
|
||||
} else if (layerId === 4) {
|
||||
value = parseFloat(value).toFixed(4);
|
||||
} else {
|
||||
value = parseFloat(value).toFixed(0);
|
||||
}
|
||||
}
|
||||
|
||||
if (value === 'NoData') {
|
||||
return layerName + ': keine Daten';
|
||||
} else {
|
||||
return (textTemplates as any)[layerId][0] + value + (textTemplates as any)[layerId][1];
|
||||
}
|
||||
};
|
||||
|
||||
const handleClick = () => {
|
||||
setOpened(!opened);
|
||||
if (innerRef && innerRef.current) {
|
||||
innerRef.current.classList.toggle('hidden');
|
||||
}
|
||||
};
|
||||
|
||||
const tableDataCSS = 'w-full break-words border-b border-solid border-gray-300';
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`absolute top-4 right-4 ${opened && isMobile ? 'bottom-4' : ''} ${
|
||||
opened && resources && resources.length > 0 ? 'bottom-4' : ''
|
||||
} w-full md:w-1/3 xl:w-1/4 pl-8 md:pl-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"
|
||||
>
|
||||
Abfrageergebnis <span className="float-right">{opened ? '-' : '+'}</span>
|
||||
</div>
|
||||
<div
|
||||
className={`max-h-[calc(100%-3rem)] px-2 xl:px-4 py-4 overflow-y-auto bg-white text-sm ${
|
||||
opened ? '' : 'hidden'
|
||||
}`}
|
||||
ref={innerRef}
|
||||
>
|
||||
{screenshot ? <Image src={screenshot} alt="Screenshot" width={1000} height={500}></Image> : null}
|
||||
{address && address.length > 0 ? (
|
||||
<table id="address-table" className="table-fixed w-full mt-3 mr-0 mb-3 ml-0">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
{address[0]}
|
||||
<br></br>
|
||||
{address[1]} {address[3]}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
) : (
|
||||
<div className="pb-2">
|
||||
<Warning>Mit einem Klick in die Karte können Sie geothermische Daten abfragen.</Warning>
|
||||
</div>
|
||||
)}
|
||||
{resources && resources.length > 0 ? (
|
||||
<Collapsible title="Ressourcen" open={true}>
|
||||
<table id="resources-table" className="table-fixed w-full mb-4">
|
||||
<thead>
|
||||
<tr>
|
||||
<td></td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr className="w-full">
|
||||
<th className="pt-4 pb-2 text-center">Ressourcen für vordefinierte Grundwassernutzung</th>
|
||||
</tr>
|
||||
{resources.slice(0, 2).map((result: any) => {
|
||||
return (
|
||||
<tr className="w-full" key={result.layerId}>
|
||||
<td className={tableDataCSS}>
|
||||
{formatGWWP(
|
||||
result.layerId,
|
||||
result.layerName,
|
||||
result.feature.attributes['Classify.Pixel Value']
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
<tr className="w-full">
|
||||
<th className="pt-4 pb-2 text-center">Standortabhängige Parameter</th>
|
||||
</tr>
|
||||
{resources.slice(2, 10).map((result: any) => {
|
||||
return (
|
||||
<tr className="w-full" key={result.layerId}>
|
||||
<td className={tableDataCSS}>
|
||||
{formatGWWP(
|
||||
result.layerId,
|
||||
result.layerName,
|
||||
result.feature.attributes['Classify.Pixel Value']
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</Collapsible>
|
||||
) : null}
|
||||
{ampelkarte && ampelkarte.length > 0 ? <TableAmpelkarteGWWP results={ampelkarte}></TableAmpelkarteGWWP> : null}
|
||||
<Footer></Footer>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
311
app/gwwp/table-ampelkarte-gwwp.tsx
Normal file
311
app/gwwp/table-ampelkarte-gwwp.tsx
Normal file
|
@ -0,0 +1,311 @@
|
|||
import Collapsible from '../components/collapsible';
|
||||
|
||||
const hinweiseText: any = {
|
||||
Grundwasserchemismus: {
|
||||
'Eisen- und Manganausfällung': `Am Standort kann es zu Eisen- und Manganausfällungen in den Brunnen kommen.
|
||||
Diese können mit bestimmten technischen Maßnahmen wie der Luftfreihaltung des Systems von der Entnahme bis zur Rückgabe des Wassers reduziert oder vermieden werden.
|
||||
Jedenfalls wird im Vorfeld eine chemische Analyse des Grundwassers am Standort empfohlen.`,
|
||||
Metallkorrosion: `Am Standort kann es zur Metallkorrosion in den Brunnen kommen. Dies kann mit bestimmten technischen Maßnahmen, wie einem Wärmetauscher aus rostfreiem Stahl bzw. einem zusätzlichen Trennwärmetauscher reduziert oder vermieden werden. Jedenfalls wird im Vorfeld eine chemische Analyse des Grundwassers am Standort empfohlen.`,
|
||||
'Keine Daten': `Auf Grund fehlender chemischer Wasseranalysen können keine Aussagen zur Grundwasserchemie getroffen werden.`,
|
||||
'Kein Risiko durch GW-Chemismus': 'Kein Risiko durch GW-Chemismus',
|
||||
},
|
||||
Naturdenkmal: `Am Standort gibt es Naturdenkmäler, die eine Nutzung der Oberflächennahen Geothermie eventuell beschränken können.`,
|
||||
'Gespannte Grundwasserzone': `Am Standort können gespannte Grundwasserverhältnisse auftreten. Bei der Planung und Durchführung zukünftiger Bohrungen in diesem Bereich muss dies berücksichtigt werden.`,
|
||||
Gasvorkommen: `Am Standort können oberflächennahe Gasvorkommen nicht ausgeschlossen werden. Bei der Planung und Durchführung zukünftiger Bohrungen in diesem Bereich muss dies berücksichtigt werden.`,
|
||||
'Mehrere Grundwasserstockwerke': `Am Standort können mehrere Grundwasserstockwerke angetroffen werden.`,
|
||||
};
|
||||
|
||||
const einschraenkungenText: any = {
|
||||
Naturschutz: `Sind durch ein Vorhaben ein Schutzgebiet (z.B. Nationalpark, Europaschutzgebiet, Landschaftsschutzgebiet, Naturschutzgebiet, geschützter Landschafsteil),
|
||||
ein Schutzobjekt (Naturdenkmal) oder streng (geschützte) Tier- und Pflanzenarten betroffen, ist jedenfalls rechtzeitig mit der Magistratsabteilung 22 - Umweltschutz Kontakt aufzunehmen,
|
||||
um eine allfällige naturschutzbehördliche Bewilligungspflicht abklären zu können. Auf folgenden Seiten sind sämtliche Informationen zu Schutzgebieten und –objekten sowie zu den Artenschutzbestimmungen zu finden:`,
|
||||
Naturschutz_links: [
|
||||
'https://www.wien.gv.at/umweltschutz/naturschutz/gebiet/schutzgebiete.html#schutzgebiete',
|
||||
'https://www.wien.gv.at/umweltschutz/naturschutz/biotop/artenschutz.html',
|
||||
],
|
||||
'Artesisch gespannte Brunnen': `In einem Umkreis von 100 m Radius wurde mit Bohrungen artesisch gespanntes Grundwasser angetroffen. Bei der Planung und Durchführung zukünftiger Bohrungen in diesem Bereich muss dies berücksichtigt werden.`,
|
||||
Karstzonen: `Am Standort treten verkarstungsfähige Gesteine auf. Bohrungen können daher Hohlräume antreffen.`,
|
||||
Altlasten: `Es befindet sich eine Altlast am Standort. Weitere Informationen über die Altlasten sind im Altlasten-GIS unter folgendem Link zu finden: `,
|
||||
Altlasten_links: 'https://secure.umweltbundesamt.at/altlasten/?servicehandler=publicgis',
|
||||
'Unterirdische Bauwerke': `In einem Umkreis von 2 m befindet sich ein unterirdisches Verkehrsbauwerk (entsprechend Digitalem Verkehrsgraph - GIP). Auf diesen Flächen und im Nahbereich ist der Einsatz von Erdwärmesonden ausgeschlossen.
|
||||
Es kann jedoch noch weitere unterirdische Bauwerke wie Tiefgaragen, Verbindungsgänge oder Einbauten im restlichen Stadtgebiet geben, die den Einsatz von Erdwärmesonden beschränken können.`,
|
||||
};
|
||||
|
||||
const tableDataCSS = 'w-full break-words border-b border-solid border-gray-300 px-2 text-sm';
|
||||
let einschraenkungen_erlaeuterungen: any = {};
|
||||
let hinweise_erlaeuterungen: any = {};
|
||||
|
||||
const getEinschraenkungen = (attributes: any) => {
|
||||
let einschraenkungen = [];
|
||||
|
||||
// Wasserschutz- und Wasserschongebiete
|
||||
switch (attributes['GWP_01']) {
|
||||
case 'Grün':
|
||||
einschraenkungen.push(
|
||||
<tr key={einschraenkungen.length}>
|
||||
<td className={tableDataCSS}>{attributes['Para_01']}</td>
|
||||
<td className={tableDataCSS}>{getAmpelText('Grün')}</td>
|
||||
</tr>
|
||||
);
|
||||
break;
|
||||
case 'Gelb':
|
||||
einschraenkungen.push(
|
||||
<tr key={einschraenkungen.length}>
|
||||
<td className={tableDataCSS}>
|
||||
{attributes['Para_01']}: {attributes['Kat_01']}
|
||||
</td>
|
||||
<td className={tableDataCSS}>{getAmpelText('Gelb')}</td>
|
||||
</tr>
|
||||
);
|
||||
break;
|
||||
case 'Magenta':
|
||||
einschraenkungen.push(
|
||||
<tr key={einschraenkungen.length}>
|
||||
<td className={tableDataCSS}>
|
||||
{attributes['Para_01']}: {attributes['Kat_01']}
|
||||
</td>
|
||||
<td className={tableDataCSS}>{getAmpelText('Magenta')}</td>
|
||||
</tr>
|
||||
);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// Altlasten
|
||||
switch (attributes['GWP_02']) {
|
||||
case 'Gelb':
|
||||
einschraenkungen_erlaeuterungen[attributes['Para_02']] = (
|
||||
<>
|
||||
{einschraenkungenText[attributes['Para_02']]}
|
||||
<a href={einschraenkungenText[attributes['Para_02'] + '_links']}>
|
||||
{einschraenkungenText[attributes['Para_02'] + '_links']}
|
||||
</a>
|
||||
</>
|
||||
);
|
||||
einschraenkungen.push(
|
||||
<tr key={einschraenkungen.length}>
|
||||
<td className={tableDataCSS}>
|
||||
{attributes['Para_02']}
|
||||
<sup>{Object.keys(einschraenkungen_erlaeuterungen).length}</sup>
|
||||
</td>
|
||||
<td className={tableDataCSS}>{getAmpelText('Gelb')}</td>
|
||||
</tr>
|
||||
);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// Artesisch gespannte Brunnen
|
||||
switch (attributes['GWP_03']) {
|
||||
case 'Gelb':
|
||||
einschraenkungen_erlaeuterungen[attributes['Para_03']] = einschraenkungenText[attributes['Para_03']];
|
||||
einschraenkungen.push(
|
||||
<tr key={einschraenkungen.length}>
|
||||
<td className={tableDataCSS}>
|
||||
{attributes['Para_03']}
|
||||
<sup>{Object.keys(einschraenkungen_erlaeuterungen).length}</sup>
|
||||
</td>
|
||||
<td className={tableDataCSS}>{getAmpelText('Gelb')}</td>
|
||||
</tr>
|
||||
);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// Bergbaugebiete
|
||||
switch (attributes['GWP_04']) {
|
||||
case 'Gelb':
|
||||
einschraenkungen.push(
|
||||
<tr key={einschraenkungen.length}>
|
||||
<td className={tableDataCSS}>{attributes['Para_04']}</td>
|
||||
<td className={tableDataCSS}>{getAmpelText('Gelb')}</td>
|
||||
</tr>
|
||||
);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// Karstzonen
|
||||
switch (attributes['GWP_05']) {
|
||||
case 'Gelb':
|
||||
einschraenkungen_erlaeuterungen[attributes['Para_05']] = einschraenkungenText[attributes['Para_05']];
|
||||
einschraenkungen.push(
|
||||
<tr key={einschraenkungen.length}>
|
||||
<td className={tableDataCSS}>{attributes['Para_05']}</td>
|
||||
<td className={tableDataCSS}>{getAmpelText('Gelb')}</td>
|
||||
</tr>
|
||||
);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// Naturschutz
|
||||
switch (attributes['GWP_06']) {
|
||||
case 'Gelb':
|
||||
einschraenkungen_erlaeuterungen[attributes['Para_06']] = (
|
||||
<>
|
||||
Sind durch ein Vorhaben ein Schutzgebiet (z.B. Nationalpark, Europaschutzgebiet, Landschaftsschutzgebiet,
|
||||
Naturschutzgebiet, geschützter Landschafsteil), ein Schutzobjekt (Naturdenkmal) oder streng (geschützte) Tier-
|
||||
und Pflanzenarten betroffen, ist jedenfalls rechtzeitig mit der Magistratsabteilung 22 - Umweltschutz Kontakt
|
||||
aufzunehmen, um eine allfällige naturschutzbehördliche Bewilligungspflicht abklären zu können. Auf folgenden
|
||||
Seiten sind sämtliche Informationen zu Schutzgebieten und –objekten sowie zu den Artenschutzbestimmungen zu
|
||||
finden:
|
||||
<ul className="pt-2">
|
||||
<li>
|
||||
<a href="https://www.wien.gv.at/umweltschutz/naturschutz/gebiet/schutzgebiete.html#schutzgebiete">
|
||||
https://www.wien.gv.at/umweltschutz/naturschutz/gebiet/schutzgebiete.html#schutzgebiete
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://www.wien.gv.at/umweltschutz/naturschutz/biotop/artenschutz.html">
|
||||
https://www.wien.gv.at/umweltschutz/naturschutz/biotop/artenschutz.html
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</>
|
||||
);
|
||||
|
||||
einschraenkungen.push(
|
||||
<tr key={einschraenkungen.length} className="w-full">
|
||||
<td className={tableDataCSS}>
|
||||
{attributes['Para_06']}
|
||||
<sup>{Object.keys(einschraenkungen_erlaeuterungen).length}</sup>:{' '}
|
||||
{attributes['Kat_06'].replaceAll(',', ', ')}
|
||||
</td>
|
||||
<td className={tableDataCSS}>{getAmpelText('Gelb')}</td>
|
||||
</tr>
|
||||
);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return einschraenkungen;
|
||||
};
|
||||
|
||||
const getHinweise = (attributes: any) => {
|
||||
let hinweise = [];
|
||||
if (attributes['Hinweis_01']) {
|
||||
hinweise.push(
|
||||
<tr key={hinweise.length}>
|
||||
<td className={tableDataCSS}>{attributes['Hinweis_01']}</td>
|
||||
<td className={tableDataCSS}>
|
||||
{attributes['Kat_01']} {attributes['Kat_01'] !== 'Kein Risiko durch GW-Chemismus' && <sup>1</sup>}
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
|
||||
if (attributes['Kat_01'] !== 'Kein Risiko durch GW-Chemismus') {
|
||||
hinweise_erlaeuterungen[attributes['Hinweis_01']] = (
|
||||
<>{hinweiseText.Grundwasserchemismus[attributes['Kat_01']]}</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return hinweise;
|
||||
};
|
||||
|
||||
export const getAmpelText = (color: string) => {
|
||||
switch (color) {
|
||||
case 'Grün':
|
||||
return 'Nutzung generell möglich';
|
||||
case 'Gelb':
|
||||
return 'Genauere Beurteilung notwendig';
|
||||
case 'Magenta':
|
||||
return 'Nutzung generell nicht möglich';
|
||||
default:
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
export const TableAmpelkarteGWWP = ({ results }: { results: any }) => {
|
||||
let einschraenkungen: any[] = [];
|
||||
let hinweise: any[] = [];
|
||||
einschraenkungen_erlaeuterungen = {};
|
||||
|
||||
results.forEach((result: any) => {
|
||||
const attributes = result.feature.attributes;
|
||||
if (result.layerId === 0) {
|
||||
einschraenkungen = getEinschraenkungen(attributes);
|
||||
} else {
|
||||
hinweise = getHinweise(attributes);
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
{einschraenkungen.length > 0 ? (
|
||||
<Collapsible title="Einschränkungen" open={true}>
|
||||
<table id={'einschraenkungen-table'} className="table-fixed w-full mb-4">
|
||||
<thead>
|
||||
<tr>
|
||||
<td colSpan={2}></td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>{einschraenkungen}</tbody>
|
||||
<tbody>
|
||||
{Object.keys(einschraenkungen_erlaeuterungen).length > 0
|
||||
? Object.keys(einschraenkungen_erlaeuterungen).map((key, index) => {
|
||||
return (
|
||||
<tr key={key} className="w-full">
|
||||
<td
|
||||
colSpan={2}
|
||||
className="w-full break-words border-b border-solid border-gray-300 px-2 text-xs"
|
||||
>
|
||||
{index + 1}: {einschraenkungen_erlaeuterungen[key]}
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})
|
||||
: null}
|
||||
</tbody>
|
||||
</table>
|
||||
</Collapsible>
|
||||
) : (
|
||||
<Collapsible title="Einschränkungen" open={true}>
|
||||
<table id={'einschraenkungen-table'} className="table-fixed w-full mb-4">
|
||||
<thead>
|
||||
<tr>
|
||||
<td></td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr className="w-full">
|
||||
<td className={tableDataCSS}>An diesem Standort sind keine Einschränkungen bekannt.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</Collapsible>
|
||||
)}
|
||||
{hinweise.length > 0 ? (
|
||||
<Collapsible title="Hinweise" open={true}>
|
||||
<table id={'hinweise-table'} className="table-fixed w-full mb-4">
|
||||
<thead>
|
||||
<tr>
|
||||
<td></td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>{hinweise}</tbody>
|
||||
<tbody>
|
||||
{Object.keys(hinweise_erlaeuterungen).length > 0 &&
|
||||
Object.keys(hinweise_erlaeuterungen).map((key, index) => {
|
||||
return (
|
||||
<tr key={key}>
|
||||
<td colSpan={2} className="w-full break-words border-b border-solid border-gray-300 px-2 text-xs">
|
||||
{index + 1}: {hinweise_erlaeuterungen[key]}
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</Collapsible>
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -1,22 +1,45 @@
|
|||
import './globals.css'
|
||||
import type { Metadata } from 'next'
|
||||
import { Inter } from 'next/font/google'
|
||||
import './globals.css';
|
||||
|
||||
const inter = Inter({ subsets: ['latin'] })
|
||||
import type { Metadata } from 'next';
|
||||
import Image from 'next/image';
|
||||
import { Inter } from 'next/font/google';
|
||||
|
||||
import Navigation from './navigation';
|
||||
import MobileNavigation from './mobile-navigation';
|
||||
import ReduxProvider from '@/redux/provider';
|
||||
|
||||
const inter = Inter({ subsets: ['latin'] });
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Create Next App',
|
||||
description: 'Generated by create next app',
|
||||
}
|
||||
title: 'Geothermie Atlas',
|
||||
description: 'Generated by GeoSphere Austria',
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body className={inter.className}>{children}</body>
|
||||
<body className={inter.className}>
|
||||
<div className="h-16 flex flex-row items-center border-b border-solid border-slate-300 px-8 xl:px-32 max-w-full">
|
||||
<Image
|
||||
src="/geosphere-austria-logo.png"
|
||||
alt="GeoSphere Austria Logo"
|
||||
className="w-16 xl:w-28 object-contain"
|
||||
width={296}
|
||||
height={92}
|
||||
></Image>
|
||||
<Image
|
||||
src="/stadt-wien-logo.png"
|
||||
alt="Stadt Wien Logo"
|
||||
className="w-16 xl:w-28 object-contain pl-4 pl-4 xl:pl-10"
|
||||
width={170}
|
||||
height={76}
|
||||
></Image>
|
||||
<span className="pl-5 xl:pl-10 text-xs xl:text-base">Geothermie Atlas</span>
|
||||
<Navigation></Navigation>
|
||||
</div>
|
||||
<MobileNavigation></MobileNavigation>
|
||||
<ReduxProvider>{children}</ReduxProvider>
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
|
89
app/mobile-navigation.tsx
Normal file
89
app/mobile-navigation.tsx
Normal file
|
@ -0,0 +1,89 @@
|
|||
'use client';
|
||||
|
||||
import { useRef } from 'react';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import Link from 'next/link';
|
||||
|
||||
const linkCSS = 'hover:underline hover:decoration-red-700 hover:underline-offset-4';
|
||||
|
||||
export default function Navigation() {
|
||||
const pathname = usePathname();
|
||||
const menuDiv = useRef<HTMLDivElement | null>(null);
|
||||
const decoration = 'underline decoration-red-700 underline-offset-4';
|
||||
|
||||
const handleClick = () => {
|
||||
if (menuDiv.current) {
|
||||
menuDiv.current.classList.toggle('hidden');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="md:hidden ">
|
||||
<button
|
||||
className="absolute top-5 right-5 inline-flex hover:text-red-700 lg:hidden ml-auto outline-none"
|
||||
onClick={handleClick}
|
||||
>
|
||||
<svg
|
||||
className="w-6 h-6"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 6h16M4 12h16M4 18h16" />
|
||||
</svg>
|
||||
</button>
|
||||
<div className="hidden absolute top-0 right-0 bottom-0 bg-white w-5/6 h-full z-50" ref={menuDiv}>
|
||||
<button
|
||||
className="absolute top-5 right-5 inline-flex hover:text-red-700 lg:hidden ml-auto outline-none"
|
||||
onClick={handleClick}
|
||||
>
|
||||
<svg
|
||||
className="w-6 h-6"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path d="M3 21.32L21 3.32001" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round" />
|
||||
<path d="M3 3.32001L21 21.32" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round" />
|
||||
</svg>
|
||||
</button>
|
||||
<div className="absolute top-20 pl-8 ">
|
||||
<ul className="flex flex-col justify-between gap-y-4 text-sm">
|
||||
<li>
|
||||
<Link href="/" className={pathname === '/' ? decoration : linkCSS}>
|
||||
Home
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link href="/ews/grundlagenkarte" className={linkCSS}>
|
||||
Grundlagenkarte Erdwärme
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link href="/ews/potenzialberechnung" className={linkCSS}>
|
||||
Potenzialberechnung Erdwärme
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link href="/gwwp" className={pathname === '/gwwp' ? decoration : linkCSS}>
|
||||
Grundwasserwärmepumpen
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link href="/daten" className={pathname === '/daten' ? decoration : linkCSS}>
|
||||
Daten
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link href="/about" className={pathname === '/about' ? decoration : linkCSS}>
|
||||
About
|
||||
</Link>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
57
app/navigation.tsx
Normal file
57
app/navigation.tsx
Normal file
|
@ -0,0 +1,57 @@
|
|||
'use client';
|
||||
|
||||
import { usePathname } from 'next/navigation';
|
||||
import Link from 'next/link';
|
||||
|
||||
const linkCSS = 'hover:underline hover:decoration-red-700 hover:underline-offset-4 text-xs xl:text-base';
|
||||
|
||||
const Tooltip = ({ pathname }: { pathname: string }) => {
|
||||
const css = pathname.startsWith('/ews')
|
||||
? 'group relative inline-block hover:cursor-pointer duration-300 underline decoration-red-700 underline-offset-4 text-xs xl:text-base'
|
||||
: 'group relative inline-block hover:cursor-pointer duration-300 text-xs xl:text-base';
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className={css}>
|
||||
Erdwärmesonden
|
||||
<div className="absolute hidden group-hover:flex -top-3 text-sm w-fit py-px bg-white z-50">
|
||||
<ul>
|
||||
<li>
|
||||
<Link href="/ews/grundlagenkarte" className={linkCSS}>
|
||||
Grundlagenkarte
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link href="/ews/potenzialberechnung" className={linkCSS}>
|
||||
Potenzialberechnung
|
||||
</Link>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default function Navigation() {
|
||||
const pathname = usePathname();
|
||||
const decoration = 'underline decoration-red-700 underline-offset-4 text-xs xl:text-base';
|
||||
|
||||
return (
|
||||
<div className="hidden md:block lg:pl-32 md:flex md:justify-start gap-x-8 xl:gap-x-10 text-xs xl:text-base">
|
||||
<Link href="/" className={pathname === '/' ? decoration : linkCSS}>
|
||||
Home
|
||||
</Link>
|
||||
<Tooltip pathname={pathname}></Tooltip>
|
||||
<Link href="/gwwp" className={pathname === '/gwwp' ? decoration : linkCSS}>
|
||||
Grundwasserwärmepumpen
|
||||
</Link>
|
||||
<Link href="/daten" className={pathname === '/daten' ? decoration : linkCSS}>
|
||||
Daten
|
||||
</Link>
|
||||
<Link href="/about" className={pathname === '/about' ? decoration : linkCSS}>
|
||||
About
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
}
|
175
app/page.tsx
175
app/page.tsx
|
@ -1,113 +1,80 @@
|
|||
import Image from 'next/image'
|
||||
import Link from 'next/link';
|
||||
import Image from 'next/image';
|
||||
|
||||
const oldLink = (
|
||||
<Link href="/ews" className="group-hover:">
|
||||
<Image src="/EWS.jpg" width={575} height={663} alt="Erdwärmesonden" className="mx-auto"></Image>
|
||||
<p className="max-w-screen-md mt-4">
|
||||
Nutzen Sie die Wärme des Untergrunds mit Erdwärmesonden. Dieses System nutzt vertikale Bohrungen, in denen eine
|
||||
Wärmeträgerflüssigkeit zirkuliert und über die der Wärmeaustausch stattfindet.
|
||||
</p>
|
||||
</Link>
|
||||
);
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<main className="flex min-h-screen flex-col items-center justify-between p-24">
|
||||
<div className="z-10 max-w-5xl w-full items-center justify-between font-mono text-sm lg:flex">
|
||||
<p className="fixed left-0 top-0 flex w-full justify-center border-b border-gray-300 bg-gradient-to-b from-zinc-200 pb-6 pt-8 backdrop-blur-2xl dark:border-neutral-800 dark:bg-zinc-800/30 dark:from-inherit lg:static lg:w-auto lg:rounded-xl lg:border lg:bg-gray-200 lg:p-4 lg:dark:bg-zinc-800/30">
|
||||
Get started by editing
|
||||
<code className="font-mono font-bold">app/page.tsx</code>
|
||||
<div className="absolute bottom-0 top-16 left-0 right-0 overflow-y-auto text-sm xl:text-base mb-4">
|
||||
<div className="max-w-screen-2xl mx-auto mt-8 mb-4 px-8 xl:px-24">
|
||||
<h1 className="text-red-700 text-base md:text-xl xl:text-2xl mb-2 font-semibold">
|
||||
Willkommen beim Geothermie-Atlas!
|
||||
</h1>
|
||||
<p className="whitespace-normal wordbreak-normal">
|
||||
Hier erhalten Sie maßgeschneiderte Informationen, um fundierte Entscheidungen über oberflächennahe
|
||||
Geothermie-Systeme an gewählten Standorten treffen zu können. Ob Sie HausbesitzerIn, ProjektentwicklerIn,
|
||||
InvestorIn sind, oder sich allgemein für erneuerbare Energiequellen interessieren, unsere Karten und die
|
||||
Grundstücksabfrage bieten Ihnen wertvolle Einblicke in das Energiepotential Ihres Grundstücks. Momentan ist
|
||||
der Geothermie-Atlas für Wien verfügbar, an einer österreichweiten Ergänzung wird gearbeitet. Nutzen Sie die
|
||||
Kraft der Geothermie! Klicken Sie unten, um zu den Karten und der Grundstücksabfrage zu gelangen.
|
||||
</p>
|
||||
<div className="fixed bottom-0 left-0 flex h-48 w-full items-end justify-center bg-gradient-to-t from-white via-white dark:from-black dark:via-black lg:static lg:h-auto lg:w-auto lg:bg-none">
|
||||
<a
|
||||
className="pointer-events-none flex place-items-center gap-2 p-8 lg:pointer-events-auto lg:p-0"
|
||||
href="https://vercel.com?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
</div>
|
||||
<div className="absolute px-8 flex justify-center flex-col md:flex-row w-full gap-x-2 gap-y-2">
|
||||
<div className="px-2 md:px-5 py-4 max-w-lg xl:max-w-2xl border border-gray-300">
|
||||
<div className="flex justify-between">
|
||||
<Link
|
||||
href="/ews/grundlagenkarte"
|
||||
className="shrink group border border-transparent px-2 xl:px-5 py-4 transition-colors hover:border-gray-300 hover:bg-gray-100"
|
||||
>
|
||||
By{' '}
|
||||
<Image
|
||||
src="/vercel.svg"
|
||||
alt="Vercel Logo"
|
||||
className="dark:invert"
|
||||
width={100}
|
||||
height={24}
|
||||
priority
|
||||
/>
|
||||
</a>
|
||||
<h2 className={`mb-3 text-sm md:text-base`}>Zur Grundlagenkarte</h2>
|
||||
<p className={`m-0 max-w-[30ch] text-xs xl:text-sm opacity-50`}>
|
||||
Grundlegende Abfragen zum Thema Erdwärmesonden
|
||||
</p>
|
||||
</Link>
|
||||
<Link
|
||||
href="/ews/potenzialberechnung"
|
||||
className="shrink group border border-transparent px-2 xl:px-5 py-4 transition-colors hover:border-gray-300 hover:bg-gray-100"
|
||||
>
|
||||
<h2 className={`mb-3 text-sm md:text-base`}>Zur Potenzialberechnung</h2>
|
||||
<p className={`m-0 max-w-[30ch] text-xs xl:text-sm opacity-50`}>
|
||||
Grundstücksbezogene Potenzialberechnung
|
||||
</p>
|
||||
</Link>
|
||||
</div>
|
||||
<Image src="/EWS.jpg" width={575} height={663} alt="Erdwärmesonden" className="mx-auto"></Image>
|
||||
<p className="max-w-screen-md mt-4 mb-4 xl:px-8 text-xs xl:text-sm">
|
||||
Nutzen Sie die Wärme des Untergrunds mit Erdwärmesonden. Dieses System nutzt vertikale Bohrungen, in denen
|
||||
eine Wärmeträgerflüssigkeit zirkuliert und über die der Wärmeaustausch stattfindet.
|
||||
</p>
|
||||
</div>
|
||||
<div className="px-2 md:px-5 py-4 max-w-lg xl:max-w-2xl border border-gray-300">
|
||||
<div className="flex justify-start">
|
||||
<Link
|
||||
href="/gwwp"
|
||||
className="group border border-transparent px-2 xl:px-5 py-4 transition-colors hover:border-gray-300 hover:bg-gray-100"
|
||||
>
|
||||
<h2 className={`mb-3 text-sm md:text-base`}>Zur Grundlagenkarte</h2>
|
||||
<p className={`m-0 max-w-[30ch] text-xs xl:text-sm opacity-50`}>
|
||||
Grundlegende Abfragen zum Thema Grundwasserwärmepumpen
|
||||
</p>
|
||||
</Link>
|
||||
</div>
|
||||
<Image src="/GWWP.jpg" width={575} height={663} alt="Grundwasserwärmepumpen" className="mx-auto"></Image>
|
||||
<p className="max-w-screen-md mt-4 mb-4 xl:px-8 text-xs xl:text-sm">
|
||||
Ein weiteres effizientes System ist die thermische Grundwassernutzung. Hier wird nach der Entnahme von einem
|
||||
Brunnen die Wärme des Grundwassers an das Gebäude übertragen und anschließend das Wasser über einen
|
||||
Schluckbrunnen dem Grundwasserkörper zurückgegeben.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="relative flex place-items-center before:absolute before:h-[300px] before:w-[480px] before:-translate-x-1/2 before:rounded-full before:bg-gradient-radial before:from-white before:to-transparent before:blur-2xl before:content-[''] after:absolute after:-z-20 after:h-[180px] after:w-[240px] after:translate-x-1/3 after:bg-gradient-conic after:from-sky-200 after:via-blue-200 after:blur-2xl after:content-[''] before:dark:bg-gradient-to-br before:dark:from-transparent before:dark:to-blue-700 before:dark:opacity-10 after:dark:from-sky-900 after:dark:via-[#0141ff] after:dark:opacity-40 before:lg:h-[360px] z-[-1]">
|
||||
<Image
|
||||
className="relative dark:drop-shadow-[0_0_0.3rem_#ffffff70] dark:invert"
|
||||
src="/next.svg"
|
||||
alt="Next.js Logo"
|
||||
width={180}
|
||||
height={37}
|
||||
priority
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="mb-32 grid text-center lg:max-w-5xl lg:w-full lg:mb-0 lg:grid-cols-4 lg:text-left">
|
||||
<a
|
||||
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
|
||||
className="group rounded-lg border border-transparent px-5 py-4 transition-colors hover:border-gray-300 hover:bg-gray-100 hover:dark:border-neutral-700 hover:dark:bg-neutral-800/30"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<h2 className={`mb-3 text-2xl font-semibold`}>
|
||||
Docs{' '}
|
||||
<span className="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none">
|
||||
->
|
||||
</span>
|
||||
</h2>
|
||||
<p className={`m-0 max-w-[30ch] text-sm opacity-50`}>
|
||||
Find in-depth information about Next.js features and API.
|
||||
</p>
|
||||
</a>
|
||||
|
||||
<a
|
||||
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
className="group rounded-lg border border-transparent px-5 py-4 transition-colors hover:border-gray-300 hover:bg-gray-100 hover:dark:border-neutral-700 hover:dark:bg-neutral-800/30"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<h2 className={`mb-3 text-2xl font-semibold`}>
|
||||
Learn{' '}
|
||||
<span className="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none">
|
||||
->
|
||||
</span>
|
||||
</h2>
|
||||
<p className={`m-0 max-w-[30ch] text-sm opacity-50`}>
|
||||
Learn about Next.js in an interactive course with quizzes!
|
||||
</p>
|
||||
</a>
|
||||
|
||||
<a
|
||||
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
|
||||
className="group rounded-lg border border-transparent px-5 py-4 transition-colors hover:border-gray-300 hover:bg-gray-100 hover:dark:border-neutral-700 hover:dark:bg-neutral-800/30"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<h2 className={`mb-3 text-2xl font-semibold`}>
|
||||
Templates{' '}
|
||||
<span className="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none">
|
||||
->
|
||||
</span>
|
||||
</h2>
|
||||
<p className={`m-0 max-w-[30ch] text-sm opacity-50`}>
|
||||
Explore the Next.js 13 playground.
|
||||
</p>
|
||||
</a>
|
||||
|
||||
<a
|
||||
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
|
||||
className="group rounded-lg border border-transparent px-5 py-4 transition-colors hover:border-gray-300 hover:bg-gray-100 hover:dark:border-neutral-700 hover:dark:bg-neutral-800/30"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<h2 className={`mb-3 text-2xl font-semibold`}>
|
||||
Deploy{' '}
|
||||
<span className="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none">
|
||||
->
|
||||
</span>
|
||||
</h2>
|
||||
<p className={`m-0 max-w-[30ch] text-sm opacity-50`}>
|
||||
Instantly deploy your Next.js site to a shareable URL with Vercel.
|
||||
</p>
|
||||
</a>
|
||||
</div>
|
||||
</main>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
|
17
app/utils/getAddress.js
Normal file
17
app/utils/getAddress.js
Normal file
|
@ -0,0 +1,17 @@
|
|||
import * as locator from '@arcgis/core/rest/locator';
|
||||
|
||||
// reverse-geocode address for a given point
|
||||
export default function getAddress(mapPoint, setAddress) {
|
||||
const serviceUrl = 'https://geocode.arcgis.com/arcgis/rest/services/World/GeocodeServer';
|
||||
const params = {
|
||||
location: mapPoint,
|
||||
};
|
||||
locator.locationToAddress(serviceUrl, params).then(
|
||||
function (response) {
|
||||
setAddress(response.address.split(','));
|
||||
},
|
||||
function () {
|
||||
setAddress([]);
|
||||
}
|
||||
);
|
||||
}
|
85
app/utils/identify.js
Normal file
85
app/utils/identify.js
Normal file
|
@ -0,0 +1,85 @@
|
|||
import { identify } from '@arcgis/core/rest/identify';
|
||||
import IdentifyParameters from '@arcgis/core/rest/support/IdentifyParameters';
|
||||
|
||||
import { updateAmpelkarteEWS } from '@/redux/ampelkarteEWSSlice';
|
||||
import { updateAmpelkarteGWWP } from '@/redux/ampelkarteGWWPSlice';
|
||||
import { updateResourcesEWS } from '@/redux/resourcesEWSSlice';
|
||||
import { updateResourcesGWWP } from '@/redux/resourcesGWWPSlice';
|
||||
|
||||
import { RESOURCES_EWS_URL, AMPEL_EWS_URL, RESOURCES_GWWP_URL, AMPEL_GWWP_URL } from '../config/config';
|
||||
|
||||
// query layers
|
||||
export default function identifyAllLayers(view, mapPoint, dispatch, theme) {
|
||||
// define query parameters
|
||||
const params = new IdentifyParameters();
|
||||
params.geometry = mapPoint;
|
||||
params.tolerance = 0;
|
||||
params.layerOption = 'all';
|
||||
params.width = view.width;
|
||||
params.height = view.height;
|
||||
params.mapExtent = view.extent;
|
||||
|
||||
if (theme === 'EWS') {
|
||||
identify(RESOURCES_EWS_URL, params)
|
||||
.then((res) => {
|
||||
const results = res.results.map((result) => {
|
||||
return {
|
||||
layerId: result.layerId,
|
||||
layerName: result.layerName,
|
||||
feature: { attributes: result.feature.attributes },
|
||||
};
|
||||
});
|
||||
dispatch(updateResourcesEWS(results));
|
||||
})
|
||||
.catch(() => {
|
||||
dispatch(updateResourcesEWS([]));
|
||||
});
|
||||
|
||||
identify(AMPEL_EWS_URL, params)
|
||||
.then((res) => {
|
||||
const results = res.results.map((result) => {
|
||||
return {
|
||||
layerId: result.layerId,
|
||||
layerName: result.layerName,
|
||||
feature: { attributes: result.feature.attributes },
|
||||
};
|
||||
});
|
||||
dispatch(updateAmpelkarteEWS(results));
|
||||
})
|
||||
.catch(() => {
|
||||
dispatch(updateAmpelkarteEWS([]));
|
||||
});
|
||||
}
|
||||
|
||||
if (theme === 'GWWP') {
|
||||
identify(RESOURCES_GWWP_URL, params)
|
||||
.then((res) => {
|
||||
const results = res.results.map((result) => {
|
||||
return {
|
||||
layerId: result.layerId,
|
||||
layerName: result.layerName,
|
||||
feature: { attributes: result.feature.attributes },
|
||||
};
|
||||
});
|
||||
dispatch(updateResourcesGWWP(results));
|
||||
})
|
||||
.catch(() => {
|
||||
dispatch(updateResourcesGWWP([]));
|
||||
});
|
||||
|
||||
identify(AMPEL_GWWP_URL, params)
|
||||
.then((res) => {
|
||||
const results = res.results.map((result) => {
|
||||
return {
|
||||
layerId: result.layerId,
|
||||
layerName: result.layerName,
|
||||
feature: { attributes: result.feature.attributes },
|
||||
};
|
||||
});
|
||||
dispatch(updateAmpelkarteGWWP(results));
|
||||
})
|
||||
.catch(() => {
|
||||
dispatch(updateAmpelkarteGWWP([]));
|
||||
});
|
||||
}
|
||||
}
|
420
app/utils/print.js
Normal file
420
app/utils/print.js
Normal file
|
@ -0,0 +1,420 @@
|
|||
import jsPDF from 'jspdf';
|
||||
import 'jspdf-autotable';
|
||||
|
||||
export default function print(
|
||||
einschraenkungen,
|
||||
hinweise,
|
||||
computationResult,
|
||||
screenshot,
|
||||
image_bal,
|
||||
image_userdefined,
|
||||
cadastralData,
|
||||
warnings = false,
|
||||
image_borefield,
|
||||
calculationMode,
|
||||
theme,
|
||||
resources
|
||||
) {
|
||||
// space between tables
|
||||
let spaceBetween = 5;
|
||||
|
||||
// create new pdf document object
|
||||
const doc = new jsPDF({
|
||||
orientation: 'portrait',
|
||||
unit: 'mm',
|
||||
format: 'a4',
|
||||
});
|
||||
|
||||
// add date to top right corner
|
||||
let today = new Date().toLocaleDateString();
|
||||
doc.setFontSize(8);
|
||||
doc.text('erstellt am ' + today, 190, 10, { align: 'right' });
|
||||
|
||||
// add heading
|
||||
doc.setFontSize(14);
|
||||
doc.text('Standortbasierter Bericht', 105, 20, {
|
||||
align: 'center',
|
||||
});
|
||||
|
||||
// screenshot of the map
|
||||
// doc.addImage(screenshot, 'PNG', 20, 30, 170, 85);
|
||||
|
||||
// cadastral data
|
||||
if (cadastralData) {
|
||||
doc.autoTable({
|
||||
html: '#cadastral-data-table',
|
||||
rowPageBreak: 'avoid',
|
||||
startY: 30,
|
||||
styles: { halign: 'center' },
|
||||
columnStyles: { 0: { fillColor: [255, 255, 255] } },
|
||||
});
|
||||
}
|
||||
|
||||
// address table
|
||||
doc.autoTable({
|
||||
html: '#address-table',
|
||||
rowPageBreak: 'avoid',
|
||||
startY: doc.lastAutoTable.finalY ? doc.lastAutoTable.finalY : 30,
|
||||
styles: { halign: 'center' },
|
||||
columnStyles: { 0: { fillColor: [255, 255, 255] } },
|
||||
});
|
||||
|
||||
// legend for parcel boundary lines
|
||||
// if (computationResult && theme === 'EWS') {
|
||||
// doc.autoTable({
|
||||
// startY: doc.lastAutoTable.finalY,
|
||||
// head: [],
|
||||
// body: [
|
||||
// [' ', 'Grundstücksgrenze'],
|
||||
// [' ', '2,5-Meter-Abstand zur Grundstückgsrenze'],
|
||||
// ],
|
||||
// willDrawCell: () => {
|
||||
// doc.setFillColor(255, 255, 255);
|
||||
// },
|
||||
// didDrawCell: function (data) {
|
||||
// let rowCenterY = data.row.height / 2;
|
||||
// doc.setLineWidth(0.5);
|
||||
// if (computationResult && data.row.index === 0 && data.column.index === 0) {
|
||||
// doc.setDrawColor('blue');
|
||||
// doc.line(data.cursor.x + 5, data.cursor.y + rowCenterY, data.cursor.x + 40, data.cursor.y + rowCenterY);
|
||||
// }
|
||||
|
||||
// if (computationResult && theme === 'EWS' && data.row.index === 1 && data.column.index === 0) {
|
||||
// doc.setDrawColor('#00890c');
|
||||
// doc.line(data.cursor.x + 5, data.cursor.y + rowCenterY, data.cursor.x + 40, data.cursor.y + rowCenterY);
|
||||
// }
|
||||
// },
|
||||
// });
|
||||
// }
|
||||
|
||||
// warnings table
|
||||
if (computationResult && warnings) {
|
||||
doc.autoTable({
|
||||
html: '#warnings-table',
|
||||
rowPageBreak: 'avoid',
|
||||
startY: doc.lastAutoTable.finalY,
|
||||
willDrawCell: function (data) {
|
||||
if (data.section === 'body' && data.cell.text !== '') {
|
||||
doc.setFillColor(255, 251, 214);
|
||||
doc.setTextColor(113, 81, 0);
|
||||
} else {
|
||||
doc.setFillColor(255, 255, 255);
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// resources table
|
||||
if (resources) {
|
||||
doc.autoTable({
|
||||
html: '#resources-table',
|
||||
rowPageBreak: 'avoid',
|
||||
showHead: 'firstPage',
|
||||
startY: doc.lastAutoTable.finalY + spaceBetween,
|
||||
columnStyles: {
|
||||
0: {
|
||||
lineWidth: { bottom: 0.1 },
|
||||
lineColor: '#d1d1d1',
|
||||
fillColor: [255, 255, 255],
|
||||
},
|
||||
},
|
||||
willDrawCell: function (data) {
|
||||
if (data.section === 'head') {
|
||||
doc.setFillColor(5, 46, 55);
|
||||
data.cell.text = 'Ressourcen';
|
||||
}
|
||||
|
||||
if (
|
||||
data.cell.text.length > 0 &&
|
||||
(data.cell.text[0].startsWith('Ressourcen') || data.cell.text[0].startsWith('Standortabhängige'))
|
||||
) {
|
||||
data.cell.styles.halign = 'center';
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// restrictions table
|
||||
if (einschraenkungen) {
|
||||
// start at second page
|
||||
doc.addPage();
|
||||
|
||||
doc.autoTable({
|
||||
html: '#einschraenkungen-table',
|
||||
rowPageBreak: 'avoid',
|
||||
showHead: 'firstPage',
|
||||
startY: 20,
|
||||
columnStyles: {
|
||||
0: {
|
||||
lineWidth: { bottom: 0.1 },
|
||||
lineColor: '#d1d1d1',
|
||||
fillColor: [255, 255, 255],
|
||||
},
|
||||
1: {
|
||||
lineWidth: { bottom: 0.1 },
|
||||
lineColor: '#d1d1d1',
|
||||
fillColor: [255, 255, 255],
|
||||
},
|
||||
},
|
||||
willDrawCell: (data) => {
|
||||
if (data.section === 'head') {
|
||||
doc.setFillColor(5, 46, 55);
|
||||
data.cell.text = 'Einschränkungen';
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// hints table
|
||||
if (hinweise) {
|
||||
doc.autoTable({
|
||||
html: '#hinweise-table',
|
||||
rowPageBreak: 'avoid',
|
||||
showHead: 'firstPage',
|
||||
startY: einschraenkungen ? doc.lastAutoTable.finalY + spaceBetween : 20,
|
||||
columnStyles: {
|
||||
0: {
|
||||
lineWidth: { bottom: 0.1 },
|
||||
lineColor: '#d1d1d1',
|
||||
fillColor: [255, 255, 255],
|
||||
},
|
||||
1: {
|
||||
lineWidth: { bottom: 0.1 },
|
||||
lineColor: '#d1d1d1',
|
||||
fillColor: [255, 255, 255],
|
||||
},
|
||||
},
|
||||
willDrawCell: function (data) {
|
||||
if (data.section === 'head') {
|
||||
doc.setFillColor(5, 46, 55);
|
||||
data.cell.text = 'Hinweise';
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// calculations input table
|
||||
if (computationResult) {
|
||||
if (einschraenkungen || hinweise) {
|
||||
doc.addPage();
|
||||
}
|
||||
|
||||
doc.autoTable({
|
||||
html: '#calculations-input-table',
|
||||
rowPageBreak: 'avoid',
|
||||
showHead: 'firstPage',
|
||||
startY: hinweise ? 20 : doc.lastAutoTable.finalY + spaceBetween,
|
||||
columnStyles: {
|
||||
0: {
|
||||
lineWidth: { bottom: 0.1 },
|
||||
lineColor: '#d1d1d1',
|
||||
fillColor: [255, 255, 255],
|
||||
},
|
||||
},
|
||||
willDrawCell: function (data) {
|
||||
if (data.section === 'head') {
|
||||
doc.setFillColor(5, 46, 55);
|
||||
data.cell.text[0] = 'Berechnungsergebnisse';
|
||||
}
|
||||
|
||||
if (
|
||||
data.cell.text[0] === 'Berechnungsvorgaben' ||
|
||||
data.cell.text[0] === 'Benutzereingabe' ||
|
||||
data.cell.text[0] === 'Standortabhängige Parameter'
|
||||
) {
|
||||
data.cell.styles.halign = 'center';
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// borefield map
|
||||
if (computationResult && image_borefield) {
|
||||
const imgProps = doc.getImageProperties(image_borefield.current);
|
||||
const width = doc.internal.pageSize.getWidth() - 100;
|
||||
const totalHeight = doc.internal.pageSize.getHeight();
|
||||
let height = (imgProps.height * width) / imgProps.width;
|
||||
if (height > totalHeight - doc.lastAutoTable.finalY - 10) {
|
||||
doc.addPage();
|
||||
doc.addImage(image_borefield.current, 'PNG', 50, 20, width, height);
|
||||
} else {
|
||||
doc.addImage(image_borefield.current, 'PNG', 50, doc.lastAutoTable.finalY + 5, width, height);
|
||||
}
|
||||
}
|
||||
|
||||
// start new page if theme is EWS
|
||||
// user input table is longer than for GWWP
|
||||
if (computationResult && theme === 'EWS') {
|
||||
doc.addPage();
|
||||
}
|
||||
if (computationResult) {
|
||||
doc.autoTable({
|
||||
html: '#calculations-output-table',
|
||||
rowPageBreak: 'avoid',
|
||||
showHead: 'firstPage',
|
||||
startY: theme === 'EWS' ? 20 : doc.lastAutoTable.finalY + spaceBetween,
|
||||
columnStyles: {
|
||||
0: {
|
||||
lineWidth: { bottom: 0.1 },
|
||||
lineColor: '#d1d1d1',
|
||||
fillColor: [255, 255, 255],
|
||||
},
|
||||
},
|
||||
willDrawCell: (data) => {
|
||||
if (data.cell.text[0] === 'Benutzerdefinierte Vorgaben') {
|
||||
data.cell.styles.halign = 'center';
|
||||
}
|
||||
|
||||
if (
|
||||
data.cell.text[0].startsWith('Berechnungsergebnisse') ||
|
||||
data.cell.text[0].startsWith('Heizbetrieb') ||
|
||||
data.cell.text[0].startsWith('Kühlbetrieb')
|
||||
) {
|
||||
doc.setFillColor(255, 255, 255);
|
||||
data.cell.styles.halign = 'center';
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// plot graph for user defined input
|
||||
let height = 0;
|
||||
if (computationResult && image_userdefined) {
|
||||
const imgProps = doc.getImageProperties(image_userdefined.current);
|
||||
const width = doc.internal.pageSize.getWidth() - 60;
|
||||
const totalHeight = doc.internal.pageSize.getHeight();
|
||||
height = (imgProps.height * width) / imgProps.width;
|
||||
if (height > totalHeight - doc.lastAutoTable.finalY - 10) {
|
||||
doc.addPage();
|
||||
doc.addImage(image_userdefined.current, 'PNG', 30, 20, width, height);
|
||||
} else {
|
||||
doc.addImage(image_userdefined.current, 'PNG', 30, doc.lastAutoTable.finalY + 5, width, height);
|
||||
}
|
||||
}
|
||||
|
||||
// calculations output for automatic input
|
||||
if (computationResult && image_bal && image_bal.current) {
|
||||
doc.addPage();
|
||||
doc.autoTable({
|
||||
html: '#calculations-bal-output-table',
|
||||
rowPageBreak: 'avoid',
|
||||
showHead: 'firstPage',
|
||||
startY: 20,
|
||||
columnStyles: {
|
||||
0: {
|
||||
lineWidth: { bottom: 0.1 },
|
||||
lineColor: '#d1d1d1',
|
||||
fillColor: [255, 255, 255],
|
||||
},
|
||||
},
|
||||
willDrawCell: (data) => {
|
||||
if (data.section === 'head') {
|
||||
doc.setFillColor(5, 46, 55);
|
||||
data.cell.text[0] = 'Berechnungsergebnisse für den saisonalen Speicherbetrieb';
|
||||
}
|
||||
|
||||
if (
|
||||
data.cell.text[0] === 'Berechnungsergebnisse' ||
|
||||
data.cell.text[0].startsWith('Heizbetrieb') ||
|
||||
data.cell.text[0].startsWith('Kühlbetrieb')
|
||||
) {
|
||||
data.cell.styles.halign = 'center';
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// plot graph for automatic input
|
||||
if (computationResult && image_bal && image_bal.current) {
|
||||
const imgProps = doc.getImageProperties(image_bal.current);
|
||||
const width = doc.internal.pageSize.getWidth() - 60;
|
||||
const totalHeight = doc.internal.pageSize.getHeight();
|
||||
height = (imgProps.height * width) / imgProps.width;
|
||||
if (height > totalHeight - doc.lastAutoTable.finalY - 10) {
|
||||
doc.addPage();
|
||||
doc.addImage(image_bal.current, 'PNG', 30, 20, width, height);
|
||||
} else {
|
||||
doc.addImage(image_bal.current, 'PNG', 30, doc.lastAutoTable.finalY + 5, width, height);
|
||||
}
|
||||
}
|
||||
|
||||
// show glossary
|
||||
doc.addPage();
|
||||
if (computationResult && theme === 'EWS') {
|
||||
doc.autoTable({
|
||||
startY: 20,
|
||||
head: [
|
||||
[
|
||||
{
|
||||
content: 'Glossar',
|
||||
colSpan: 2,
|
||||
styles: { fillColor: [5, 46, 55] },
|
||||
},
|
||||
],
|
||||
],
|
||||
body: [
|
||||
['COP', 'Leistungszahl der Wärmepumpe im Heizbetrieb (Coefficient of Performance)'],
|
||||
['JAZ', 'Jahresarbeitszahl oder saisonale Leistungszahl der Wärmepumpe im Heizbetrieb'],
|
||||
['EER', 'Leistungszahl der Wärmepumpe im Kühlbetrieb (Energy Efficiency Rating)'],
|
||||
['SEER', 'Saisonale Leistungszahl der Wärmepumpe im Kühlbetrieb (Seasonal Energy Efficiency Rating)'],
|
||||
],
|
||||
columnStyles: {
|
||||
0: {
|
||||
lineWidth: { bottom: 0.1 },
|
||||
lineColor: '#d1d1d1',
|
||||
fillColor: [255, 255, 255],
|
||||
},
|
||||
1: {
|
||||
lineWidth: { bottom: 0.1 },
|
||||
lineColor: '#d1d1d1',
|
||||
fillColor: [255, 255, 255],
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// disclaimer
|
||||
doc.autoTable({
|
||||
html: '#disclaimer',
|
||||
rowPageBreak: 'avoid',
|
||||
startY: computationResult && theme === 'EWS' ? doc.lastAutoTable.finalY + spaceBetween : 20,
|
||||
columnStyles: { 0: { fillColor: [255, 255, 255] } },
|
||||
willDrawCell: function (data) {
|
||||
if (data.section === 'head') {
|
||||
doc.setFillColor(5, 46, 55);
|
||||
data.cell.text = 'Haftungsausschluss';
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
// contact
|
||||
doc.autoTable({
|
||||
html: '#contact',
|
||||
rowPageBreak: 'avoid',
|
||||
startY: doc.lastAutoTable.finalY + spaceBetween,
|
||||
columnStyles: { 0: { fillColor: [255, 255, 255] } },
|
||||
willDrawCell: (data) => {
|
||||
if (data.section === 'head') {
|
||||
doc.setFillColor(5, 46, 55);
|
||||
data.cell.text = 'Kontakt';
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
// print page numbers and number of total pages
|
||||
const pageCount = doc.internal.getNumberOfPages();
|
||||
for (let i = 1; i <= pageCount; i++) {
|
||||
// go to page i
|
||||
doc.setPage(i);
|
||||
|
||||
// set font size
|
||||
doc.setFontSize(8);
|
||||
|
||||
// print text
|
||||
doc.text('Seite ' + i, 190, 283, {
|
||||
align: 'right',
|
||||
});
|
||||
}
|
||||
|
||||
doc.save('Bericht.pdf');
|
||||
}
|
81
app/utils/queryCadastre.js
Normal file
81
app/utils/queryCadastre.js
Normal file
|
@ -0,0 +1,81 @@
|
|||
import esriRequest from '@arcgis/core/request';
|
||||
import * as geometryEngine from '@arcgis/core/geometry/geometryEngine';
|
||||
import Polygon from '@arcgis/core/geometry/Polygon';
|
||||
import Graphic from '@arcgis/core/Graphic';
|
||||
|
||||
import { updateCadastralData } from '@/redux/cadastreSlice';
|
||||
import { calculateGrid } from '../ews/potenzialberechnung/gridcomputer';
|
||||
import { BEV_KATASTER_URL } from '@/app/config/config';
|
||||
|
||||
export default function queryCadastre(
|
||||
view,
|
||||
polygonGraphicsLayer,
|
||||
mapPoint,
|
||||
dispatch,
|
||||
setPolygon,
|
||||
setPoints,
|
||||
gridSpacing = 10
|
||||
) {
|
||||
const { x, y } = view.toScreen(mapPoint);
|
||||
|
||||
let url =
|
||||
BEV_KATASTER_URL +
|
||||
'?SERVICE=WMS&VERSION=1.3.0&REQUEST=GetFeatureInfo&LAYERS=DKM_GST&QUERY_LAYERS=DKM_GST&CRS=EPSG:31256&INFO_FORMAT=application/json';
|
||||
const { xmin, ymin, xmax, ymax } = view.extent;
|
||||
const width = view.width;
|
||||
const height = view.height;
|
||||
url +=
|
||||
'&BBOX=' +
|
||||
ymin +
|
||||
',' +
|
||||
xmin +
|
||||
',' +
|
||||
ymax +
|
||||
',' +
|
||||
xmax +
|
||||
'&WIDTH=' +
|
||||
width +
|
||||
'&HEIGHT=' +
|
||||
height +
|
||||
'&I=' +
|
||||
Math.round(x) +
|
||||
'&J=' +
|
||||
Math.round(y);
|
||||
|
||||
esriRequest(url, { responseType: 'json' }).then((response) => {
|
||||
if (response.data && response.data.features && response.data.features.length > 0) {
|
||||
const feature = response.data.features[0];
|
||||
let KG = feature.properties.KG;
|
||||
let GNR = feature.properties.GNR;
|
||||
|
||||
let polygon = new Polygon({
|
||||
rings: feature.geometry.coordinates,
|
||||
spatialReference: view.spatialReference,
|
||||
});
|
||||
|
||||
const polygonSymbol = {
|
||||
type: 'simple-fill',
|
||||
color: [51, 51, 150, 0],
|
||||
style: 'solid',
|
||||
outline: {
|
||||
color: 'blue',
|
||||
width: '2px',
|
||||
},
|
||||
};
|
||||
|
||||
const polygonGraphic = new Graphic({
|
||||
geometry: polygon,
|
||||
symbol: polygonSymbol,
|
||||
});
|
||||
|
||||
polygonGraphicsLayer.add(polygonGraphic);
|
||||
|
||||
let FF = geometryEngine.planarArea(polygon, 'square-meters');
|
||||
|
||||
calculateGrid(polygon, gridSpacing, setPoints);
|
||||
|
||||
setPolygon(polygon);
|
||||
dispatch(updateCadastralData({ KG, GNR, FF }));
|
||||
}
|
||||
});
|
||||
}
|
59
app/utils/screenshot.js
Normal file
59
app/utils/screenshot.js
Normal file
|
@ -0,0 +1,59 @@
|
|||
import { updateScreenshot } from '@/redux/screenshotSlice';
|
||||
|
||||
// take screenshot for info panel
|
||||
export default function takeScreenshot(view, mapPoint, dispatch, withMarker = false) {
|
||||
const screenPoint = view.toScreen(mapPoint);
|
||||
const width = 1000;
|
||||
const height = 500;
|
||||
|
||||
if (withMarker) {
|
||||
view
|
||||
.takeScreenshot({
|
||||
area: {
|
||||
x: screenPoint.x - width / 2,
|
||||
y: screenPoint.y - height / 2,
|
||||
width: width,
|
||||
height: height,
|
||||
},
|
||||
})
|
||||
.then((screenshot) => {
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
|
||||
const context = canvas.getContext('2d');
|
||||
|
||||
const img = new Image();
|
||||
img.width = width;
|
||||
img.height = height;
|
||||
img.src = screenshot.dataUrl;
|
||||
|
||||
img.onload = () => {
|
||||
context.drawImage(img, 0, 0);
|
||||
context.strokeStyle = '#4090D0';
|
||||
context.lineWidth = 10;
|
||||
context.beginPath();
|
||||
context.moveTo(width / 2, height / 2);
|
||||
context.lineTo(width / 2 + 10, height / 2 - 40);
|
||||
context.lineTo(width / 2 - 10, height / 2 - 40);
|
||||
context.closePath();
|
||||
context.stroke();
|
||||
|
||||
dispatch(updateScreenshot(canvas.toDataURL()));
|
||||
};
|
||||
});
|
||||
} else {
|
||||
view
|
||||
.takeScreenshot({
|
||||
area: {
|
||||
x: screenPoint.x - width / 2,
|
||||
y: screenPoint.y - height / 2,
|
||||
width: width,
|
||||
height: height,
|
||||
},
|
||||
})
|
||||
.then((screenshot) => {
|
||||
dispatch(updateScreenshot(screenshot.dataUrl));
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,4 +1,6 @@
|
|||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {}
|
||||
const nextConfig = {
|
||||
basePath: '',
|
||||
};
|
||||
|
||||
module.exports = nextConfig
|
||||
module.exports = nextConfig;
|
||||
|
|
1156
package-lock.json
generated
1156
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
23
package.json
23
package.json
|
@ -3,25 +3,34 @@
|
|||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"dev": "npm run copy && next dev",
|
||||
"build": "npm run copy && next build",
|
||||
"start": "next start",
|
||||
"lint": "next lint"
|
||||
"lint": "next lint",
|
||||
"copy": "ncp ./node_modules/@arcgis/core/assets ./public/assets"
|
||||
},
|
||||
"dependencies": {
|
||||
"@arcgis/core": "^4.27.6",
|
||||
"@reduxjs/toolkit": "^1.9.7",
|
||||
"jspdf": "^2.5.1",
|
||||
"jspdf-autotable": "^3.7.0",
|
||||
"next": "13.5.4",
|
||||
"python-shell": "^5.0.0",
|
||||
"react": "^18",
|
||||
"react-dom": "^18",
|
||||
"next": "13.5.4"
|
||||
"react-redux": "^8.1.3",
|
||||
"react-responsive": "^9.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^5",
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^18",
|
||||
"@types/react-dom": "^18",
|
||||
"autoprefixer": "^10",
|
||||
"eslint": "^8",
|
||||
"eslint-config-next": "13.5.4",
|
||||
"ncp": "^2.0.0",
|
||||
"postcss": "^8",
|
||||
"tailwindcss": "^3",
|
||||
"eslint": "^8",
|
||||
"eslint-config-next": "13.5.4"
|
||||
"typescript": "^5"
|
||||
}
|
||||
}
|
||||
|
|
BIN
public/EWS.jpg
Normal file
BIN
public/EWS.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 798 KiB |
BIN
public/GWWP.jpg
Normal file
BIN
public/GWWP.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 849 KiB |
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"expand": "Expand",
|
||||
"collapse": "Collapse"
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"expand": "توسيع",
|
||||
"collapse": "طي"
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"expand": "Разгъване",
|
||||
"collapse": "Сгъване"
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"expand": "Proširi",
|
||||
"collapse": "Sažmi"
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"expand": "Amplia",
|
||||
"collapse": "Redueix"
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"expand": "Rozbalit",
|
||||
"collapse": "Sbalit"
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"expand": "Udvid",
|
||||
"collapse": "Skjul"
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"expand": "Einblenden",
|
||||
"collapse": "Ausblenden"
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"expand": "Ανάπτυξη",
|
||||
"collapse": "Σύμπτυξη"
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"expand": "Expand",
|
||||
"collapse": "Collapse"
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"expand": "Expandir",
|
||||
"collapse": "Contraer"
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"expand": "Laienda",
|
||||
"collapse": "Ahenda"
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"expand": "Laajenna",
|
||||
"collapse": "Kutista"
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"expand": "Développer",
|
||||
"collapse": "Réduire"
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"expand": "הרחב",
|
||||
"collapse": "צמצם"
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"expand": "Proširi",
|
||||
"collapse": "Sažmi"
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"expand": "Kibontás",
|
||||
"collapse": "Összecsukás"
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"expand": "Bentang",
|
||||
"collapse": "Tutup"
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"expand": "Espandi",
|
||||
"collapse": "Comprimi"
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"expand": "展開",
|
||||
"collapse": "折りたたむ"
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"expand": "확장",
|
||||
"collapse": "축소"
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"expand": "Išskleisti",
|
||||
"collapse": "Suskleisti"
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"expand": "Izvērst",
|
||||
"collapse": "Sakļaut"
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"expand": "Uitklappen",
|
||||
"collapse": "Inklappen"
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"expand": "Utvid",
|
||||
"collapse": "Skjul"
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"expand": "Rozwiń",
|
||||
"collapse": "Zwiń"
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"expand": "Expandir",
|
||||
"collapse": "Recolher"
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"expand": "Expandir",
|
||||
"collapse": "Recolher"
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"expand": "Extindere",
|
||||
"collapse": "Restrângere"
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"expand": "Развернуть",
|
||||
"collapse": "Свернуть"
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"expand": "Rozbaliť",
|
||||
"collapse": "Zbaliť"
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"expand": "Razširi",
|
||||
"collapse": "Strni"
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"expand": "Proširi",
|
||||
"collapse": "Skupi"
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"expand": "Expandera",
|
||||
"collapse": "Dölj"
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"expand": "ขยาย",
|
||||
"collapse": "ย่อลงมา"
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"expand": "Genişlet",
|
||||
"collapse": "Daralt"
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"expand": "Розширити",
|
||||
"collapse": "Згорнути"
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"expand": "Mở rộng",
|
||||
"collapse": "Thu gọn"
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"expand": "展开",
|
||||
"collapse": "折叠"
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"expand": "展開",
|
||||
"collapse": "摺疊"
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"expand": "展開",
|
||||
"collapse": "摺疊"
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"more": "More"
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"more": "المزيد"
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"more": "Още"
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"more": "Više"
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"more": "Més"
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"more": "Více"
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"more": "Mere"
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"more": "Mehr"
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"more": "Περισσότερα"
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"more": "More"
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"more": "Más"
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"more": "Rohkem"
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"more": "Enemmän"
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"more": "Plus"
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"more": "עוד"
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"more": "Više"
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user