OG, EG und UG haben je ein Rendering in assets/img, Puppenhausschnitt von schraeg oben. Fuer AG gibt es so ein Bild nicht: die Planungssoftware kennt nur das einzelne Gebaeude, nicht das Grundstueck. Der Plan fuer das Aussengelaende entsteht deshalb als Vektor direkt im SVG von home.php. Diese Seite liefert die Koordinaten dafuer. Als Hintergrund liegt das amtliche Luftbild - DOP20 mit 20 cm Bodenaufloesung, live vom WMS der Bayerischen Vermessungsverwaltung, CC BY 4.0. Es liegt also keine Kopie im Repository, und weil es ein True Orthophoto ist, sitzen die Dachkanten ueber dem Grundriss statt perspektivisch daneben - bei Google zeichnet man sonst den Dachueberstand ab. Abgeklickt wird Flaeche fuer Flaeche; unten fallen die fertigen <polygon>-Zeilen fuer home.php heraus und dazu die x/y-Zeilen fuer rooms.php, deren Kachelmitte aus den Ecken gerechnet wird. Welche Flaechen es gibt, sagt die Raumtabelle - ein neuer AG-Raum taucht hier von selbst auf. Das Haus steht davor, obwohl es kein Raum ist: es ist der Umriss, an dem sich beim Zeichnen alles andere ausrichtet, und bekommt folgerichtig keine Kachelzeile. Der Ausschnitt laesst sich ueber ?lat=, ?lon= und ?breite= verschieben, ohne die Datei anzufassen. Die Umrechnung nach UTM32 steckt in der Seite, weil ein quadratischer Ausschnitt in Laengen- und Breitengraden keiner waere: ein Grad Laenge ist auf dieser Breite nur zwei Drittel so lang wie ein Grad Breite. Bauart und Aussehen wie tools/kachelpositionen.php, und aus demselben Grund kein Eintrag im Menue: die Seite wird einmal gebraucht und danach nie wieder. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
451 lines
18 KiB
PHP
451 lines
18 KiB
PHP
<?php
|
||
/**
|
||
* Messblatt für den Lageplan des Außengeländes.
|
||
*
|
||
* OG, EG und UG haben je ein Bild in assets/img - Renderings aus der
|
||
* Planungssoftware, Puppenhausschnitt von schräg oben. Für das Außengelände
|
||
* gibt es so ein Bild nicht: die Software kennt nur das einzelne Gebäude,
|
||
* nicht das Grundstück. Der Plan für AG entsteht deshalb als Vektor direkt im
|
||
* SVG von restricted/home.php - Haus, Carport, Garage, Pergola, Terrasse und
|
||
* Garten als Flächen.
|
||
*
|
||
* Diese Seite liefert die Koordinaten dafür. Als Hintergrund liegt das
|
||
* amtliche Luftbild (DOP20, 20 cm Bodenauflösung), abgeklickt wird darauf
|
||
* Fläche für Fläche; darunter fallen die fertigen <polygon>-Zeilen heraus.
|
||
*
|
||
* Warum nicht einfach am Bild abmessen: die Zeichenfläche ist 400 × 300 groß
|
||
* und hat mit Metern nichts zu tun. Wer das im Kopf umrechnet, verrechnet
|
||
* sich - und zwar in jeder Ecke anders.
|
||
*
|
||
* Das Luftbild kommt live vom WMS der Bayerischen Vermessungsverwaltung, es
|
||
* liegt also keine Kopie im Repository. Der Ausschnitt lässt sich über die
|
||
* Adresszeile verschieben, ohne diese Datei anzufassen:
|
||
*
|
||
* tools/lageplan.php?lat=47.5781&lon=10.2628&breite=120
|
||
*
|
||
* "breite" ist die Kantenlänge des Ausschnitts in Metern; die Höhe ergibt
|
||
* sich aus dem Seitenverhältnis 4:3 der Zeichenfläche.
|
||
*
|
||
* Aufruf: http://<host>/smart/tools/lageplan.php
|
||
*
|
||
* Bewusst eine eigene Seite und kein Eintrag im Menü: sie wird einmal
|
||
* gebraucht und danach nie wieder - genau wie kachelpositionen.php, mit der
|
||
* sie sich die Bauart teilt.
|
||
*/
|
||
|
||
require_once(__DIR__ . "/../helper.php");
|
||
require_once(__DIR__ . "/../restricted/rooms.php");
|
||
|
||
if (!checkLogin()) {
|
||
http_response_code(403);
|
||
exit;
|
||
}
|
||
|
||
/** So groß ist die Zeichenfläche - siehe restricted/home.php. */
|
||
const PLAN_BREIT = 400;
|
||
const PLAN_HOCH = 300;
|
||
|
||
/** So groß ist eine Kachel, für die vorgeschlagene x/y-Zeile. */
|
||
const KACHEL_BREIT = 24;
|
||
const KACHEL_HOCH = 19;
|
||
|
||
/** Welche Etage hier vermessen wird. */
|
||
const ETAGE = "AG";
|
||
|
||
/**
|
||
* Das Luftbild. CC BY 4.0 - die Namensnennung steht unten auf der Seite und
|
||
* gehört dort hin, auch wenn die Seite nur intern erreichbar ist.
|
||
*/
|
||
const WMS_URL = "https://geoservices.bayern.de/od/wms/dop/v1/dop20";
|
||
const WMS_LAYER = "by_dop20c";
|
||
|
||
// Wie viele Bildpunkte je Einheit der Zeichenfläche geholt werden. Drei ist
|
||
// scharf genug zum Abklicken und bleibt weit unter der Grenze des Dienstes.
|
||
const WMS_SCHAERFE = 3;
|
||
|
||
/**
|
||
* Mittelpunkt des Ausschnitts. Dieselben Koordinaten stehen in
|
||
* restricted/gartenbewaesserung/auto_watering.py, von wo die Regendaten
|
||
* geholt werden - es ist derselbe Ort.
|
||
*/
|
||
$lat = isset($_GET["lat"]) ? floatval($_GET["lat"]) : 47.5781;
|
||
$lon = isset($_GET["lon"]) ? floatval($_GET["lon"]) : 10.2628;
|
||
$breite = isset($_GET["breite"]) ? floatval($_GET["breite"]) : 120.0;
|
||
$breite = max(20.0, min(400.0, $breite));
|
||
$hoehe = $breite * PLAN_HOCH / PLAN_BREIT;
|
||
|
||
/**
|
||
* WGS84 nach UTM Zone 32 Nord (EPSG:25832).
|
||
*
|
||
* Der WMS kann auch mit Längen- und Breitengraden umgehen, aber dann ist ein
|
||
* quadratischer Ausschnitt keiner mehr: ein Grad Länge ist auf dieser Breite
|
||
* nur zwei Drittel so lang wie ein Grad Breite. In UTM ist ein Meter ein
|
||
* Meter, und der Maßstab der Seite stimmt ohne Umrechnung.
|
||
*/
|
||
function nachUtm32($lonGrad, $latGrad)
|
||
{
|
||
$a = 6378137.0;
|
||
$f = 1 / 298.257223563;
|
||
$k0 = 0.9996;
|
||
$e2 = $f * (2 - $f);
|
||
$ep2 = $e2 / (1 - $e2);
|
||
|
||
$lon0 = deg2rad(32 * 6 - 183);
|
||
$lat = deg2rad($latGrad);
|
||
$lon = deg2rad($lonGrad);
|
||
|
||
$N = $a / sqrt(1 - $e2 * pow(sin($lat), 2));
|
||
$T = pow(tan($lat), 2);
|
||
$C = $ep2 * pow(cos($lat), 2);
|
||
$A = cos($lat) * ($lon - $lon0);
|
||
|
||
$M = $a * ((1 - $e2 / 4 - 3 * $e2 ** 2 / 64 - 5 * $e2 ** 3 / 256) * $lat
|
||
- (3 * $e2 / 8 + 3 * $e2 ** 2 / 32 + 45 * $e2 ** 3 / 1024) * sin(2 * $lat)
|
||
+ (15 * $e2 ** 2 / 256 + 45 * $e2 ** 3 / 1024) * sin(4 * $lat)
|
||
- (35 * $e2 ** 3 / 3072) * sin(6 * $lat));
|
||
|
||
$x = $k0 * $N * ($A + (1 - $T + $C) * $A ** 3 / 6
|
||
+ (5 - 18 * $T + $T ** 2 + 72 * $C - 58 * $ep2) * $A ** 5 / 120) + 500000.0;
|
||
$y = $k0 * ($M + $N * tan($lat) * ($A ** 2 / 2
|
||
+ (5 - $T + 9 * $C + 4 * $C ** 2) * $A ** 4 / 24
|
||
+ (61 - 58 * $T + $T ** 2 + 600 * $C - 330 * $ep2) * $A ** 6 / 720));
|
||
|
||
return [$x, $y];
|
||
}
|
||
|
||
list($mx, $my) = nachUtm32($lon, $lat);
|
||
$bbox = sprintf("%.1f,%.1f,%.1f,%.1f",
|
||
$mx - $breite / 2, $my - $hoehe / 2,
|
||
$mx + $breite / 2, $my + $hoehe / 2);
|
||
|
||
$luftbild = WMS_URL . "?" . http_build_query([
|
||
"SERVICE" => "WMS", "VERSION" => "1.3.0", "REQUEST" => "GetMap",
|
||
"LAYERS" => WMS_LAYER, "STYLES" => "", "CRS" => "EPSG:25832",
|
||
"BBOX" => $bbox,
|
||
"WIDTH" => (int) (PLAN_BREIT * WMS_SCHAERFE),
|
||
"HEIGHT" => (int) (PLAN_HOCH * WMS_SCHAERFE),
|
||
"FORMAT" => "image/jpeg",
|
||
]);
|
||
|
||
/**
|
||
* Die Flächen, die abgeklickt werden.
|
||
*
|
||
* Sie kommen aus der Raumtabelle - ein neuer AG-Raum in rooms.php taucht hier
|
||
* von selbst auf. Das Haus steht davor, obwohl es kein Raum ist: es ist der
|
||
* Umriss, an dem sich beim Zeichnen alles andere ausrichtet.
|
||
*/
|
||
$flaechen = [["schluessel" => "haus", "name" => "Haus", "raum" => null]];
|
||
foreach (allRooms() as $raum) {
|
||
if ($raum["floor"] !== ETAGE) {
|
||
continue;
|
||
}
|
||
$flaechen[] = ["schluessel" => $raum["id"], "name" => $raum["mqtt"], "raum" => $raum];
|
||
}
|
||
|
||
// Genug unterscheidbare Farben, danach wiederholen sie sich - bei sieben
|
||
// Flächen kommt es dazu nicht.
|
||
$farben = ["#ffbe3c", "#35b6cf", "#7ed957", "#ff7a7a", "#c58cff", "#ffe066", "#6ee7c8"];
|
||
foreach ($flaechen as $i => $f) {
|
||
$flaechen[$i]["farbe"] = $farben[$i % count($farben)];
|
||
}
|
||
|
||
?><!doctype html>
|
||
<html lang="de">
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||
<title>Lageplan Außengelände</title>
|
||
<style>
|
||
:root {
|
||
--grund: #fbfbfa; --schrift: #1f2328; --leise: #6b7280;
|
||
--linie: #d8d8d4; --karte: #ffffff; --akzent: #00788f;
|
||
}
|
||
@media (prefers-color-scheme: dark) {
|
||
:root {
|
||
--grund: #16181c; --schrift: #e8eaed; --leise: #9aa0a6;
|
||
--linie: #2f333a; --karte: #1e2126; --akzent: #35b6cf;
|
||
}
|
||
}
|
||
body {
|
||
margin: 0; padding: 1.5rem 1rem 3rem;
|
||
background: var(--grund); color: var(--schrift);
|
||
font: 15px/1.55 -apple-system, "Segoe UI", Roboto, sans-serif;
|
||
}
|
||
.huelle { max-width: 900px; margin: 0 auto; }
|
||
h1 { font-size: 1.35rem; margin: 0 0 .3rem; }
|
||
.vorwort { color: var(--leise); margin: 0 0 1.2rem; max-width: 62ch; }
|
||
.vorwort code, .quelle code {
|
||
background: var(--karte); border: 1px solid var(--linie);
|
||
border-radius: .25rem; padding: .05rem .3rem; font-size: .88em;
|
||
}
|
||
.etage {
|
||
background: var(--karte); border: 1px solid var(--linie);
|
||
border-radius: .6rem; padding: .8rem; margin-bottom: 1.2rem;
|
||
}
|
||
.plan {
|
||
display: block; width: 100%; height: auto; border-radius: .4rem;
|
||
cursor: crosshair; touch-action: none; background: #0b0d10;
|
||
}
|
||
.achsen text { fill: #ffffffaa; font: 7px sans-serif; }
|
||
.stift { stroke: #ffbe3c; stroke-width: .4; stroke-dasharray: 3 3; }
|
||
.name { font: 5px sans-serif; text-anchor: middle; paint-order: stroke;
|
||
stroke: #000a; stroke-width: 1.4; }
|
||
|
||
.waehler { display: flex; flex-wrap: wrap; gap: .4rem; margin: .8rem 0 .2rem; }
|
||
.waehler button {
|
||
font: inherit; font-size: .88rem; cursor: pointer;
|
||
background: var(--karte); color: var(--schrift);
|
||
border: 1px solid var(--linie); border-radius: .35rem;
|
||
padding: .25rem .6rem; display: flex; align-items: center; gap: .4rem;
|
||
}
|
||
.waehler button[aria-pressed="true"] { border-color: var(--akzent); box-shadow: 0 0 0 1px var(--akzent); }
|
||
.waehler .tupfen { width: .7rem; height: .7rem; border-radius: 50%; }
|
||
.waehler .zahl { color: var(--leise); font-variant-numeric: tabular-nums; }
|
||
|
||
.knoepfe { display: flex; flex-wrap: wrap; gap: .4rem; margin: .6rem 0 0; }
|
||
.knoepfe button {
|
||
font: inherit; font-size: .88rem; cursor: pointer; padding: .25rem .6rem;
|
||
background: var(--karte); color: var(--schrift);
|
||
border: 1px solid var(--linie); border-radius: .35rem;
|
||
}
|
||
.hinweis { color: var(--leise); font-size: .88rem; margin: .5rem 0 0; min-height: 1.4em; }
|
||
.hinweis b { color: var(--schrift); font-variant-numeric: tabular-nums; }
|
||
|
||
h2 { font-size: .95rem; margin: 1.4rem 0 .4rem; font-weight: 600; }
|
||
textarea {
|
||
width: 100%; box-sizing: border-box; min-height: 9rem; resize: vertical;
|
||
background: var(--karte); color: var(--schrift);
|
||
border: 1px solid var(--linie); border-radius: .4rem; padding: .6rem;
|
||
font-family: ui-monospace, Consolas, monospace; font-size: .82rem; line-height: 1.5;
|
||
}
|
||
.quelle { color: var(--leise); font-size: .82rem; margin: 1.6rem 0 0; }
|
||
.quelle a { color: var(--akzent); }
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="huelle">
|
||
|
||
<h1>Lageplan Außengelände</h1>
|
||
<p class="vorwort">
|
||
Die Zeichenfläche ist <?= PLAN_BREIT ?> × <?= PLAN_HOCH ?> und zeigt hier
|
||
<b><?= rtrim(rtrim(number_format($breite, 1, ",", ""), "0"), ",") ?> ×
|
||
<?= rtrim(rtrim(number_format($hoehe, 1, ",", ""), "0"), ",") ?> m</b> –
|
||
eine Einheit sind <?= number_format($breite / PLAN_BREIT, 2, ",", "") ?> m.
|
||
Wähle eine Fläche und klick ihre Ecken der Reihe nach ab; <b>Backspace</b> nimmt
|
||
den letzten Punkt zurück. Unten stehen die fertigen Zeilen für
|
||
<code>restricted/home.php</code> und <code>restricted/rooms.php</code>.
|
||
Passt der Ausschnitt nicht, verschieb ihn über <code>?lat=</code>,
|
||
<code>?lon=</code> und <code>?breite=</code> in der Adresszeile.
|
||
</p>
|
||
|
||
<section class="etage">
|
||
<svg viewBox="0 0 <?= PLAN_BREIT ?> <?= PLAN_HOCH ?>" class="plan" id="plan">
|
||
<defs>
|
||
<pattern id="raster" width="10" height="10" patternUnits="userSpaceOnUse">
|
||
<path d="M10 0H0V10" fill="none" stroke="#ffffff22" stroke-width=".4"/>
|
||
</pattern>
|
||
</defs>
|
||
<image href="<?= htmlspecialchars($luftbild, ENT_QUOTES) ?>" x="0" y="0"
|
||
width="<?= PLAN_BREIT ?>" height="<?= PLAN_HOCH ?>"/>
|
||
<rect x="0" y="0" width="<?= PLAN_BREIT ?>" height="<?= PLAN_HOCH ?>" fill="url(#raster)"/>
|
||
|
||
<g class="achsen">
|
||
<?php for ($v = 50; $v < PLAN_BREIT; $v += 50): ?>
|
||
<text x="<?= $v ?>" y="9" text-anchor="middle"><?= $v ?></text>
|
||
<?php endfor; ?>
|
||
<?php for ($v = 50; $v < PLAN_HOCH; $v += 50): ?>
|
||
<text x="4" y="<?= $v + 3 ?>"><?= $v ?></text>
|
||
<?php endfor; ?>
|
||
</g>
|
||
|
||
<!-- Hierein zeichnet das Skript die abgeklickten Flächen. -->
|
||
<g id="gezeichnet"></g>
|
||
|
||
<g id="stift" style="display:none">
|
||
<line class="stift" x1="<?= -PLAN_BREIT ?>" x2="<?= PLAN_BREIT ?>"/>
|
||
<line class="stift" y1="<?= -PLAN_HOCH ?>" y2="<?= PLAN_HOCH ?>"/>
|
||
</g>
|
||
</svg>
|
||
|
||
<div class="waehler" id="waehler"></div>
|
||
<div class="knoepfe">
|
||
<button type="button" id="btnZurueck">Punkt zurück</button>
|
||
<button type="button" id="btnLeeren">Diese Fläche leeren</button>
|
||
<button type="button" id="btnAllesLeeren">Alles verwerfen</button>
|
||
</div>
|
||
<p class="hinweis" id="hinweis">Fläche wählen und die erste Ecke anklicken …</p>
|
||
</section>
|
||
|
||
<h2>Für <code>restricted/home.php</code> – der Plan</h2>
|
||
<textarea id="ausgabePlan" readonly spellcheck="false"></textarea>
|
||
|
||
<h2>Für <code>restricted/rooms.php</code> – die Kacheln</h2>
|
||
<textarea id="ausgabeRaeume" readonly spellcheck="false"></textarea>
|
||
|
||
<p class="quelle">
|
||
Luftbild: <b>Datenquelle Bayerische Vermessungsverwaltung</b> –
|
||
<a href="https://www.geodaten.bayern.de" target="_blank" rel="noopener">www.geodaten.bayern.de</a>,
|
||
DOP20 (20 cm), <a href="https://creativecommons.org/licenses/by/4.0/deed.de"
|
||
target="_blank" rel="noopener">CC BY 4.0</a>. Live vom WMS geholt, es liegt keine
|
||
Kopie im Repository. Ausschnitt <code><?= htmlspecialchars($bbox) ?></code> in EPSG:25832.
|
||
</p>
|
||
|
||
</div>
|
||
|
||
<script>
|
||
// Der Server schreibt hier hinein, was oben in den Konstanten steht.
|
||
const PLAN_B = <?= PLAN_BREIT ?>, PLAN_H = <?= PLAN_HOCH ?>;
|
||
const KACHEL_B = <?= KACHEL_BREIT ?>, KACHEL_H = <?= KACHEL_HOCH ?>;
|
||
const METER_JE_EINHEIT = <?= json_encode(round($breite / PLAN_BREIT, 4)) ?>;
|
||
const FLAECHEN = <?= json_encode($flaechen, JSON_UNESCAPED_UNICODE) ?>;
|
||
const ETAGE = <?= json_encode(ETAGE) ?>;
|
||
|
||
// Abgeklicktes überlebt einen Neuladen - sieben Flächen sind Arbeit, die
|
||
// man nicht wegen eines Fehlklicks auf F5 noch einmal machen will.
|
||
const SPEICHER = "lageplan-" + ETAGE;
|
||
let punkte = {};
|
||
try { punkte = JSON.parse(localStorage.getItem(SPEICHER)) || {}; } catch (e) { punkte = {}; }
|
||
FLAECHEN.forEach(f => { if (!Array.isArray(punkte[f.schluessel])) punkte[f.schluessel] = []; });
|
||
|
||
let aktiv = FLAECHEN[0].schluessel;
|
||
|
||
const svg = document.getElementById("plan");
|
||
const gezeichnet = document.getElementById("gezeichnet");
|
||
const stift = document.getElementById("stift");
|
||
const hinweis = document.getElementById("hinweis");
|
||
|
||
function flaeche(schluessel) {
|
||
return FLAECHEN.find(f => f.schluessel === schluessel);
|
||
}
|
||
|
||
/** Mittelpunkt einer Fläche - Mittel der Ecken, gut genug für eine Kachel. */
|
||
function mitte(liste) {
|
||
const s = liste.reduce((a, p) => [a[0] + p[0], a[1] + p[1]], [0, 0]);
|
||
return [s[0] / liste.length, s[1] / liste.length];
|
||
}
|
||
|
||
function element(name, attribute) {
|
||
const e = document.createElementNS("http://www.w3.org/2000/svg", name);
|
||
for (const [k, v] of Object.entries(attribute)) e.setAttribute(k, v);
|
||
return e;
|
||
}
|
||
|
||
function zeichne() {
|
||
gezeichnet.innerHTML = "";
|
||
FLAECHEN.forEach(f => {
|
||
const liste = punkte[f.schluessel];
|
||
if (!liste.length) return;
|
||
const zug = liste.map(p => p.join(",")).join(" ");
|
||
// Solange eine Fläche noch nicht drei Ecken hat, ist sie ein Linienzug
|
||
// und keine Fläche - ein Polygon sähe dann nach etwas aus, was es
|
||
// noch nicht ist.
|
||
gezeichnet.appendChild(element(liste.length > 2 ? "polygon" : "polyline", {
|
||
points: zug, fill: liste.length > 2 ? f.farbe + "44" : "none",
|
||
stroke: f.farbe, "stroke-width": f.schluessel === aktiv ? 1.1 : 0.7,
|
||
"stroke-linejoin": "round"
|
||
}));
|
||
liste.forEach(p => gezeichnet.appendChild(
|
||
element("circle", { cx: p[0], cy: p[1], r: 1.2, fill: f.farbe })));
|
||
if (liste.length > 2) {
|
||
const m = mitte(liste);
|
||
const t = element("text", { x: m[0], y: m[1] + 1.8, class: "name", fill: f.farbe });
|
||
t.textContent = f.name;
|
||
gezeichnet.appendChild(t);
|
||
}
|
||
});
|
||
zeichneWaehler();
|
||
schreibeAusgabe();
|
||
try { localStorage.setItem(SPEICHER, JSON.stringify(punkte)); } catch (e) { /* egal */ }
|
||
}
|
||
|
||
function zeichneWaehler() {
|
||
const ziel = document.getElementById("waehler");
|
||
ziel.innerHTML = "";
|
||
FLAECHEN.forEach(f => {
|
||
const b = document.createElement("button");
|
||
b.type = "button";
|
||
b.setAttribute("aria-pressed", f.schluessel === aktiv ? "true" : "false");
|
||
b.innerHTML = "<span class='tupfen' style='background:" + f.farbe + "'></span>"
|
||
+ f.name + " <span class='zahl'>" + punkte[f.schluessel].length + "</span>";
|
||
b.onclick = () => { aktiv = f.schluessel; zeichne(); };
|
||
ziel.appendChild(b);
|
||
});
|
||
}
|
||
|
||
function schreibeAusgabe() {
|
||
const plan = [];
|
||
const raeume = [];
|
||
FLAECHEN.forEach(f => {
|
||
const liste = punkte[f.schluessel];
|
||
if (liste.length < 3) return;
|
||
plan.push('<polygon class="ag-' + f.schluessel + '" points="'
|
||
+ liste.map(p => p.join(",")).join(" ") + '"/>');
|
||
if (!f.raum) return; // das Haus ist kein Raum und bekommt keine Kachel
|
||
const m = mitte(liste);
|
||
raeume.push(' ["floor" => "' + ETAGE + '", "mqtt" => "' + f.raum.mqtt
|
||
+ '", "id" => "' + f.raum.id + '",'
|
||
+ ' "x" => ' + Math.round(m[0] - KACHEL_B / 2)
|
||
+ ', "y" => ' + Math.round(m[1] - KACHEL_H / 2) + ', "werte" => []],');
|
||
});
|
||
document.getElementById("ausgabePlan").value = plan.length
|
||
? plan.join("\n")
|
||
: "Noch nichts abgeklickt - eine Fläche braucht mindestens drei Ecken.";
|
||
document.getElementById("ausgabeRaeume").value = raeume.length
|
||
? raeume.join("\n")
|
||
: "Noch nichts abgeklickt.";
|
||
}
|
||
|
||
function stelle(ereignis) {
|
||
const r = svg.getBoundingClientRect();
|
||
return [
|
||
Math.round((ereignis.clientX - r.left) / r.width * PLAN_B * 10) / 10,
|
||
Math.round((ereignis.clientY - r.top) / r.height * PLAN_H * 10) / 10
|
||
];
|
||
}
|
||
|
||
svg.addEventListener("pointermove", e => {
|
||
const [x, y] = stelle(e);
|
||
stift.style.display = "";
|
||
stift.querySelector("line:first-child").setAttribute("y1", y);
|
||
stift.querySelector("line:first-child").setAttribute("y2", y);
|
||
stift.querySelector("line:last-child").setAttribute("x1", x);
|
||
stift.querySelector("line:last-child").setAttribute("x2", x);
|
||
const liste = punkte[aktiv];
|
||
let text = "<b>" + flaeche(aktiv).name + "</b> – " + x + "," + y
|
||
+ " (" + (x * METER_JE_EINHEIT).toFixed(1) + " m, "
|
||
+ (y * METER_JE_EINHEIT).toFixed(1) + " m)";
|
||
if (liste.length) {
|
||
const l = liste[liste.length - 1];
|
||
const d = Math.hypot(x - l[0], y - l[1]) * METER_JE_EINHEIT;
|
||
text += " · letzte Kante <b>" + d.toFixed(1) + " m</b>";
|
||
}
|
||
hinweis.innerHTML = text;
|
||
});
|
||
|
||
svg.addEventListener("pointerleave", () => {
|
||
stift.style.display = "none";
|
||
hinweis.textContent = "Fläche wählen und die erste Ecke anklicken …";
|
||
});
|
||
|
||
svg.addEventListener("click", e => {
|
||
punkte[aktiv].push(stelle(e));
|
||
zeichne();
|
||
});
|
||
|
||
document.addEventListener("keydown", e => {
|
||
if (e.key === "Backspace") { e.preventDefault(); punkte[aktiv].pop(); zeichne(); }
|
||
});
|
||
|
||
document.getElementById("btnZurueck").onclick = () => { punkte[aktiv].pop(); zeichne(); };
|
||
document.getElementById("btnLeeren").onclick = () => { punkte[aktiv] = []; zeichne(); };
|
||
document.getElementById("btnAllesLeeren").onclick = () => {
|
||
if (!confirm("Alle abgeklickten Flächen verwerfen?")) return;
|
||
FLAECHEN.forEach(f => { punkte[f.schluessel] = []; });
|
||
zeichne();
|
||
};
|
||
|
||
zeichne();
|
||
</script>
|
||
</body>
|
||
</html>
|