diff --git a/restricted/aussenplan.php b/restricted/aussenplan.php new file mode 100644 index 0000000..1c816bf --- /dev/null +++ b/restricted/aussenplan.php @@ -0,0 +1,302 @@ + "#c8cdd6", + "Hof" => "#57585a", + "Garten" => "#2e5c33", + "Garage" => "#8a9099", + "Carport EG" => "#3f4a63", // nur Fundament, mit PV darauf + "Carport OG" => "#6f7680", + "Veranda EG" => "#9a7b52", + "Veranda OG" => "#c9a978", + "Terrasse EG" => "#9a7b52", + "Terrasse OG" => "#c9a978", + "Pergola" => "#7a6a8f", + ]; + return $farben[$name] ?? "#8a9099"; +} + +/** Blickrichtung: negativ heißt von Westen her. -35° ist Südwesten. */ +const PLAN_WINKEL = -35.0; + +/** Wie stark die Tiefe zusammengedrückt wird. 1 wäre die reine Draufsicht. */ +const PLAN_STAUCHUNG = 0.55; + +/** + * Wie hoch Höhen erscheinen. 1,4 heißt: ein Meter Höhe sieht aus wie 1,4 m + * Grundfläche. Überhöht, damit die drei Meter zwischen Terrasse EG und + * Terrasse OG deutlich werden - bei maßstäblicher Höhe klebten die beiden + * aufeinander, und genau das soll die Schrägsicht ja auflösen. + */ +const PLAN_UEBERHOEHUNG = 1.4; + +/** So groß ist die Zeichenfläche, in die alles hineinpassen muss. */ +const PLAN_BREITE = 400; +const PLAN_HOEHE = 300; + +/** Luft zwischen Zeichnung und Rand, in Einheiten der Zeichenfläche. */ +const PLAN_RAND = 12; + +/** + * Die Flächen des Außengeländes, in Metern. + * + * Herkunft: tools/lageplan.php - dort werden sie am amtlichen Luftbild + * abgeklickt, die Seite gibt diese Zeilen fertig aus. Von Hand zu ändern + * lohnt nur für Kleinigkeiten; wer etwas verschiebt, misst besser neu. + * + * raum Raumname aus rooms.php, oder null für Flächen, die kein Raum sind + * (Haus, Hof, Garten - sie tragen keine Kachel) + * ecken Grundriss als [x, y] in Metern, x nach Osten, y nach Süden + * z Höhe der Fläche über Gelände (0 = ebenerdig, ~3 = OG-Ebene) + * hoehe Bauhöhe darüber (0 = flache Fläche, sonst ein Körper) + * dach Satteldach über der Bauhöhe, 0 = flach + * farbe Deckfläche; die Seiten werden daraus abgedunkelt + * + * Solange die Liste leer ist, hat AG keinen Grundriss - die Etage taucht dann + * im Menü nicht auf (siehe floorsWithPlan() in rooms.php), Zuordnung und + * Automatiken funktionieren trotzdem. + */ +function aussenFlaechen() +{ + return [ + // Hier kommen die abgeklickten Flächen hinein. + ]; +} + +/** + * Die Projektion selbst. Liefert eine Funktion, die (x, y, z) in Metern auf + * (X, Y) in projizierten Metern abbildet - noch ohne Bezug zur Zeichenfläche. + */ +function aussenProjektor() +{ + $w = deg2rad(PLAN_WINKEL); + $c = cos($w); + $s = sin($w); + return function ($x, $y, $z) use ($c, $s) { + return [$x * $c - $y * $s, + ($x * $s + $y * $c) * PLAN_STAUCHUNG - $z * PLAN_UEBERHOEHUNG]; + }; +} + +/** + * Alles Gezeichnete in die Zeichenfläche einpassen. + * + * Erst wird projiziert, dann ein Maßstab gesucht, in den die ganze Zeichnung + * passt - nicht umgekehrt. Die Zeichenfläche ist 400 × 300 und hat mit Metern + * nichts zu tun; wer den Maßstab vorher festlegt, schneidet entweder etwas ab + * oder lässt die Hälfte leer. + * + * Wichtig: Messseite und Anzeige müssen dieselbe Einpassung benutzen, sonst + * sitzen die Kacheln woanders als die Flächen, auf denen sie liegen sollen. + * Deshalb steht sie hier und nicht zweimal. + */ +function aussenEinpassung($flaechen) +{ + $P = aussenProjektor(); + $xs = []; + $ys = []; + foreach ($flaechen as $f) { + $oben = ($f["z"] ?? 0) + ($f["hoehe"] ?? 0) + ($f["dach"] ?? 0); + foreach ($f["ecken"] as $e) { + foreach ([$f["z"] ?? 0, $oben] as $z) { + list($X, $Y) = $P($e[0], $e[1], $z); + $xs[] = $X; + $ys[] = $Y; + } + } + } + if (!$xs) { + return null; + } + $breite = max($xs) - min($xs); + $hoehe = max($ys) - min($ys); + // Der kleinere der beiden Maßstäbe passt in beide Richtungen. + $massstab = min(($breite > 0 ? (PLAN_BREITE - 2 * PLAN_RAND) / $breite : INF), + ($hoehe > 0 ? (PLAN_HOEHE - 2 * PLAN_RAND) / $hoehe : INF)); + if (!is_finite($massstab)) { + $massstab = 1.0; + } + return [ + "massstab" => $massstab, + // Zusätzlich zentrieren: was schmaler ist als die Fläche, soll nicht + // in der Ecke kleben. + "dx" => (PLAN_BREITE - $breite * $massstab) / 2 - min($xs) * $massstab, + "dy" => (PLAN_HOEHE - $hoehe * $massstab) / 2 - min($ys) * $massstab, + ]; +} + +/** Ein Punkt in Metern zu einem Punkt auf der Zeichenfläche. */ +function aussenPunkt($einpassung, $x, $y, $z) +{ + $P = aussenProjektor(); + list($X, $Y) = $P($x, $y, $z); + return [$X * $einpassung["massstab"] + $einpassung["dx"], + $Y * $einpassung["massstab"] + $einpassung["dy"]]; +} + +/** Mittelwert einer Punktliste - gut genug als Ankerpunkt einer Fläche. */ +function aussenMitte($punkte) +{ + $sx = 0.0; + $sy = 0.0; + foreach ($punkte as $p) { + $sx += $p[0]; + $sy += $p[1]; + } + $n = max(1, count($punkte)); + return [$sx / $n, $sy / $n]; +} + +/** Eine Farbe abdunkeln - die Seitenflächen liegen im Schatten. */ +function aussenSchatten($hex, $faktor) +{ + $n = hexdec(ltrim($hex, "#")); + return sprintf("#%02x%02x%02x", + (int) round(($n >> 16) * $faktor), + (int) round((($n >> 8) & 255) * $faktor), + (int) round(($n & 255) * $faktor)); +} + +/** + * Die Zeichnung als Liste von Polygonen, von hinten nach vorn sortiert. + * + * Sortiert wird nach dem hintersten Punkt der Grundfläche (Malerreihenfolge). + * Das reicht hier: die Körper stehen nebeneinander auf dem Boden und + * durchdringen sich nicht. Bei ineinander verschachtelten Körpern bräuchte es + * mehr, aber ein Grundstück ist keine Baugruppe. + * + * Returns: + * ["polygone" => [["punkte" => [[x,y],...], "farbe" => "#rrggbb"], ...], + * "anker" => [Raumname => [x, y], ...]] Mitte der Deckfläche + */ +function aussenZeichnung() +{ + $flaechen = aussenFlaechen(); + $einpassung = aussenEinpassung($flaechen); + if (!$einpassung) { + return ["polygone" => [], "anker" => []]; + } + + $stuecke = []; + foreach ($flaechen as $f) { + $z = $f["z"] ?? 0; + $hoehe = $f["hoehe"] ?? 0; + $dach = $f["dach"] ?? 0; + $farbe = $f["farbe"] ?? "#8a9099"; + + $tiefe = INF; + foreach ($f["ecken"] as $e) { + $p = aussenPunkt($einpassung, $e[0], $e[1], 0); + $tiefe = min($tiefe, $p[1]); + } + + $unten = []; + $oben = []; + foreach ($f["ecken"] as $e) { + $unten[] = aussenPunkt($einpassung, $e[0], $e[1], $z); + $oben[] = aussenPunkt($einpassung, $e[0], $e[1], $z + $hoehe); + } + + $polygone = []; + if ($hoehe > 0) { + $anzahl = count($f["ecken"]); + for ($i = 0; $i < $anzahl; $i++) { + $j = ($i + 1) % $anzahl; + $polygone[] = ["punkte" => [$unten[$i], $unten[$j], $oben[$j], $oben[$i]], + "farbe" => aussenSchatten($farbe, 0.62)]; + } + } + $polygone[] = ["punkte" => $oben, "farbe" => $farbe]; + + if ($dach > 0 && count($f["ecken"]) === 4) { + // Satteldach mit First über der Mitte der beiden Schmalseiten. + $e = $f["ecken"]; + $first1 = aussenPunkt($einpassung, ($e[0][0] + $e[3][0]) / 2, + ($e[0][1] + $e[3][1]) / 2, $z + $hoehe + $dach); + $first2 = aussenPunkt($einpassung, ($e[1][0] + $e[2][0]) / 2, + ($e[1][1] + $e[2][1]) / 2, $z + $hoehe + $dach); + $polygone[] = ["punkte" => [$oben[0], $oben[1], $first2, $first1], + "farbe" => $f["dachfarbe"] ?? "#b5563a"]; + $polygone[] = ["punkte" => [$oben[3], $oben[2], $first2, $first1], + "farbe" => aussenSchatten($f["dachfarbe"] ?? "#b5563a", 0.78)]; + } + + $stuecke[] = ["tiefe" => $tiefe, "polygone" => $polygone, + "raum" => $f["raum"] ?? null, "mitte" => aussenMitte($oben)]; + } + + usort($stuecke, function ($a, $b) { + return $a["tiefe"] <=> $b["tiefe"]; + }); + + $polygone = []; + $anker = []; + foreach ($stuecke as $s) { + foreach ($s["polygone"] as $p) { + $polygone[] = $p; + } + if ($s["raum"] !== null) { + $anker[$s["raum"]] = $s["mitte"]; + } + } + return ["polygone" => $polygone, "anker" => $anker]; +} + +/** Die Zeichnung als fertige SVG-Zeilen. */ +function aussenSvg() +{ + $zeichnung = aussenZeichnung(); + $svg = ""; + foreach ($zeichnung["polygone"] as $p) { + $punkte = []; + foreach ($p["punkte"] as $pt) { + $punkte[] = round($pt[0], 1) . "," . round($pt[1], 1); + } + $svg .= '' . "\n"; + } + return $svg; +} diff --git a/restricted/rooms.php b/restricted/rooms.php index 0aa7bd7..18c5fc5 100644 --- a/restricted/rooms.php +++ b/restricted/rooms.php @@ -97,9 +97,14 @@ $rooms = [ // restricted/config.php; am Carport OG haengt der go-e Charger. ["floor" => "AG", "mqtt" => "Carport EG", "id" => "carport_eg", "werte" => []], ["floor" => "AG", "mqtt" => "Carport OG", "id" => "carport_og", "werte" => []], - ["floor" => "AG", "mqtt" => "Veranda", "id" => "veranda", "werte" => []], + // Veranda und Terrasse gibt es zweimal, je Wohnpartei eine. In der + // Draufsicht laege das obere Paar genau auf dem unteren - deshalb die + // Schraegsicht, siehe restricted/aussenplan.php. + ["floor" => "AG", "mqtt" => "Veranda EG", "id" => "veranda_eg", "werte" => []], + ["floor" => "AG", "mqtt" => "Veranda OG", "id" => "veranda_og", "werte" => []], + ["floor" => "AG", "mqtt" => "Terrasse EG", "id" => "terrasse_eg", "werte" => []], + ["floor" => "AG", "mqtt" => "Terrasse OG", "id" => "terrasse_og", "werte" => []], ["floor" => "AG", "mqtt" => "Pergola", "id" => "pergola", "werte" => []], - ["floor" => "AG", "mqtt" => "Terrasse", "id" => "terrasse", "werte" => []], ["floor" => "AG", "mqtt" => "Garage", "id" => "garage", "werte" => []], // Der Garten ist kein Bauteil wie die anderen, aber ein Ort: hier haengen // der Regensensor und die beiden Ventilsteuerungen. diff --git a/tools/lageplan.php b/tools/lageplan.php index 839d36e..cc6c81c 100644 --- a/tools/lageplan.php +++ b/tools/lageplan.php @@ -2,16 +2,21 @@ /** * 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. + * OG, EG und UG haben je ein Rendering in assets/img - Puppenhausschnitt von + * schräg oben. Für das Außengelände gibt es so ein Bild nicht: die + * Planungssoftware kennt nur das einzelne Gebäude, nicht das Grundstück. Der + * Plan für AG entsteht deshalb als Vektor, gezeichnet aus der Geometrie in + * restricted/aussenplan.php - und die entsteht hier. * - * 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 -Zeilen heraus. + * Abgeklickt wird auf dem amtlichen Luftbild (DOP20, 20 cm Bodenauflösung). + * Daneben steht die Vorschau in derselben Schrägsicht von Südwesten, in der + * die Etage später gezeichnet wird: nur dort sieht man, ob Terrasse OG und + * Terrasse EG wirklich auseinandergehen. + * + * Warum das überhaupt geht: die Projektion in aussenplan.php ist eine + * Militärprojektion. Sie lässt den Grundriss maßhaltig, also reichen die + * Koordinaten aus der Draufsicht plus eine Höhe je Fläche. Eine echte + * Perspektive müsste man neu vermessen. * * 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 @@ -21,41 +26,31 @@ * 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 + * tools/lageplan.php?lat=47.5781&lon=10.2628&breite=70 * * "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:///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. + * Bewusst eine eigene Seite und kein Eintrag im Menü: sie wird gebraucht, + * wenn sich draußen etwas ändert, und das ist selten - genau wie + * kachelpositionen.php, mit der sie sich die Bauart teilt. */ require_once(__DIR__ . "/../helper.php"); require_once(__DIR__ . "/../restricted/rooms.php"); +require_once(__DIR__ . "/../restricted/aussenplan.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. - */ +/** Das Luftbild. CC BY 4.0 - die Namensnennung steht unten auf der Seite. */ const WMS_URL = "https://geoservices.bayern.de/od/wms/dop/v1/dop20"; const WMS_LAYER = "by_dop20c"; @@ -70,9 +65,9 @@ const WMS_SCHAERFE = 3; */ $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 = isset($_GET["breite"]) ? floatval($_GET["breite"]) : 70.0; $breite = max(20.0, min(400.0, $breite)); -$hoehe = $breite * PLAN_HOCH / PLAN_BREIT; +$hoehe = $breite * PLAN_HOEHE / PLAN_BREITE; /** * WGS84 nach UTM Zone 32 Nord (EPSG:25832). @@ -122,31 +117,56 @@ $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), + "WIDTH" => (int) (PLAN_BREITE * WMS_SCHAERFE), + "HEIGHT" => (int) (PLAN_HOEHE * 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. + * Die Räume kommen aus der Raumtabelle - ein neuer AG-Raum in rooms.php + * taucht hier von selbst auf. Davor stehen Haus und Hof: sie sind keine Räume + * und tragen keine Kachel, aber ohne sie wäre der Plan ein paar Flecken im + * Nichts. Das Haus ist zusätzlich der Umriss, an dem sich beim Zeichnen alles + * andere ausrichtet, deshalb steht es ganz vorn. + * + * "z" ist die Höhe über Gelände, "hoehe" die Bauhöhe darüber. Die Vorgaben + * sind das, was am häufigsten stimmt: eine ebenerdige, flache Fläche. */ -$flaechen = [["schluessel" => "haus", "name" => "Haus", "raum" => null]]; +$flaechen = [ + ["schluessel" => "haus", "name" => "Haus", "raum" => null, + "z" => 0, "hoehe" => 6, "dach" => 3], + ["schluessel" => "hof", "name" => "Hof", "raum" => null, + "z" => 0, "hoehe" => 0, "dach" => 0], +]; foreach (allRooms() as $raum) { if ($raum["floor"] !== ETAGE) { continue; } - $flaechen[] = ["schluessel" => $raum["id"], "name" => $raum["mqtt"], "raum" => $raum]; + // Was auf der OG-Ebene liegt, steht drei Meter höher - die Traufe liegt + // auf sechs Metern, bei zwei Geschossen ist das die halbe Höhe. Der + // Carport OG heisst zwar OG, steht aber auf dem Boden; deshalb entscheidet + // die Bauart und nicht der Name. + $obenAuf = in_array($raum["mqtt"], ["Veranda OG", "Terrasse OG"], true); + $mitDach = in_array($raum["mqtt"], ["Carport OG", "Garage"], true); + $flaechen[] = [ + "schluessel" => $raum["id"], + "name" => $raum["mqtt"], + "raum" => $raum, + "z" => $obenAuf ? 3.0 : 0.0, + "hoehe" => $mitDach ? 2.5 : 0.0, + "dach" => 0, + ]; } -// Genug unterscheidbare Farben, danach wiederholen sie sich - bei sieben -// Flächen kommt es dazu nicht. -$farben = ["#ffbe3c", "#35b6cf", "#7ed957", "#ff7a7a", "#c58cff", "#ffe066", "#6ee7c8"]; +// Genug unterscheidbare Farben für die Bedienung; gezeichnet wird später mit +// den Farben aus aussenFarbe(). +$stifte = ["#ffbe3c", "#35b6cf", "#7ed957", "#ff7a7a", "#c58cff", "#ffe066", + "#6ee7c8", "#ff9f5c", "#9ec5ff", "#f78fd0", "#b5e48c"]; foreach ($flaechen as $i => $f) { - $flaechen[$i]["farbe"] = $farben[$i % count($farben)]; + $flaechen[$i]["stift"] = $stifte[$i % count($stifte)]; + $flaechen[$i]["farbe"] = aussenFarbe($f["name"]); } ?> @@ -171,7 +191,7 @@ foreach ($flaechen as $i => $f) { background: var(--grund); color: var(--schrift); font: 15px/1.55 -apple-system, "Segoe UI", Roboto, sans-serif; } - .huelle { max-width: 900px; margin: 0 auto; } + .huelle { max-width: 1000px; 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 { @@ -182,14 +202,21 @@ foreach ($flaechen as $i => $f) { background: var(--karte); border: 1px solid var(--linie); border-radius: .6rem; padding: .8rem; margin-bottom: 1.2rem; } + .nebeneinander { display: grid; grid-template-columns: 1fr 1fr; gap: .8rem; } + @media (max-width: 720px) { .nebeneinander { grid-template-columns: 1fr; } } + .nebeneinander h3 { + font-size: .82rem; font-weight: 600; color: var(--leise); + margin: 0 0 .3rem; text-transform: uppercase; letter-spacing: .04em; + } .plan { display: block; width: 100%; height: auto; border-radius: .4rem; - cursor: crosshair; touch-action: none; background: #0b0d10; + touch-action: none; background: #0b0d10; } + #plan { cursor: crosshair; } .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; } + .name { font: 6px sans-serif; text-anchor: middle; paint-order: stroke; + stroke: #000a; stroke-width: 1.8; } .waehler { display: flex; flex-wrap: wrap; gap: .4rem; margin: .8rem 0 .2rem; } .waehler button { @@ -202,6 +229,13 @@ foreach ($flaechen as $i => $f) { .waehler .tupfen { width: .7rem; height: .7rem; border-radius: 50%; } .waehler .zahl { color: var(--leise); font-variant-numeric: tabular-nums; } + .hoehen { display: flex; flex-wrap: wrap; align-items: center; gap: .5rem; + margin: .7rem 0 0; font-size: .88rem; color: var(--leise); } + .hoehen input { + font: inherit; width: 4.2rem; padding: .15rem .35rem; text-align: right; + background: var(--grund); color: var(--schrift); + border: 1px solid var(--linie); border-radius: .3rem; + } .knoepfe { display: flex; flex-wrap: wrap; gap: .4rem; margin: .6rem 0 0; } .knoepfe button { font: inherit; font-size: .88rem; cursor: pointer; padding: .25rem .6rem; @@ -213,7 +247,7 @@ foreach ($flaechen as $i => $f) { h2 { font-size: .95rem; margin: 1.4rem 0 .4rem; font-weight: 600; } textarea { - width: 100%; box-sizing: border-box; min-height: 9rem; resize: vertical; + width: 100%; box-sizing: border-box; min-height: 11rem; 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; @@ -227,47 +261,59 @@ foreach ($flaechen as $i => $f) {

Lageplan Außengelände

- Die Zeichenfläche ist × und zeigt hier + Links das Luftbild von oben, rechts die Vorschau in der Schrägsicht von + Südwesten, in der die Etage später gezeichnet wird. Der Ausschnitt zeigt × m – - eine Einheit sind m. - Wähle eine Fläche und klick ihre Ecken der Reihe nach ab; Backspace nimmt - den letzten Punkt zurück. Unten stehen die fertigen Zeilen für - restricted/home.php und restricted/rooms.php. + eine Einheit sind m. + Wähle eine Fläche, klick ihre Ecken der Reihe nach ab und stell darunter ihre + Höhe ein; Backspace nimmt den letzten Punkt zurück. Unten stehen die + fertigen Zeilen für restricted/aussenplan.php. Passt der Ausschnitt nicht, verschieb ihn über ?lat=, ?lon= und ?breite= in der Adresszeile.

- - - - - - - - - - - - +
+
+

Draufsicht – hier wird geklickt

+ + + + + + + + + + + - - + + - - - - - - - + + + + +
+
+

Schrägsicht von Südwesten – so wird es aussehen

+ +
+
+
+ + + + +
@@ -276,11 +322,8 @@ foreach ($flaechen as $i => $f) {

Fläche wählen und die erste Ecke anklicken …

-

Für restricted/home.php – der Plan

- - -

Für restricted/rooms.php – die Kacheln

- +

Für restricted/aussenplan.php – in aussenFlaechen()

+

Luftbild: Datenquelle Bayerische Vermessungsverwaltung – @@ -293,36 +336,42 @@ foreach ($flaechen as $i => $f) {