Flächenberechnung und Darstellung von Grundstücken

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
feldmaus
User
Beiträge: 287
Registriert: Donnerstag 12. Oktober 2006, 16:48

Guten abend,

ich bastel seit ein paar Wochen an einem Python Script, welches meine dokumentierten GPS Koordinaten, meines Grundstücks, mit <svgwrite> als Grafik darstellt und dazu auch die Fläche berechnet. Die GPS Koordinaten habe ich in einem UTM GPS Format in einer JSON Datei gespeichert. Jedes Koordinaten Paar bekommt auch noch eine eindeutige ID als Referenz.

Code: Alles auswählen

{
    "offset": {
        "lat": 475,
        "lon": 587
    },
    "areas": [
        {
            "type": "Gesamter Bereich",
            "coords": [
                [
                    210.1349343594,
                    1836.642079159,
                    1
                ],
                [
                    165.32144854986,
                    1775.800042744,
                    2
                ],
                [
                    162.4250563345,
                    1773.812589498,
                    3
                ],
Allerdings habe ich auch einen Mechanismus eingebaut, damit statt wiederkehrenden Koordinaten Paaren nur die ID angeben brauche:

Code: Alles auswählen

                [
                    36
                ],
Würde hier oben bedeuten, eine Referenz auf das 36te Koordinaten-Paar.

Diese Referenzen werden aufgelöst auf ihre Koordinaten. Anschliessend werden alle Koordinaten-Punkte in ein Dictionary gespeichert:

Code: Alles auswählen

def create_dict_refs_coord(areas, offset_lat, offset_lon):
    dict_refs_coord = {}
    for area in areas:
        for coord in area['coords']:
            if len(coord) == 3:  # Falls eine Referenz-ID vorhanden ist
                ref_id = coord[2]
                
                # Offset und Koordinaten als Strings verketten, dann in float umwandeln
                utm_x = int(f"{int(offset_lon)}{int(coord[0])}")
                utm_y = int(f"{int(offset_lat)}{int(coord[1])}")


                dict_refs_coord[ref_id] = (utm_x, utm_y)
    return dict_refs_coord
Mit diesem Dictionary werden dann die nötigen Datenpunkte für das Polygon erstellt, welches ich an SVGWrite übergebe in dem folgenden Code:

Code: Alles auswählen

# Flächen zeichnen
def draw_areas(dwg, areas, dict_refs_coord, offset_lat, offset_lon, debug=False):
    legend_x, legend_y, font_size = 2000, 100, 40
    for i, area in enumerate(areas):
        points = []
        debug_coords = []
        for coord in area['coords']:
            coord_id = "-"
            if len(coord) == 1:
                ref_id = coord[0]
                if ref_id in dict_refs_coord:
                    utm_x, utm_y = dict_refs_coord[ref_id]
                    coord_id = ref_id
                else:
                    continue
            else:
                # Korrekte Konvertierung mit float
                utm_x = int(f"{int(offset_lon)}{int(coord[0])}")
                utm_y = int(f"{int(offset_lat)}{int(coord[1])}")


                if len(coord) == 3:
                    coord_id = coord[2]

            points.append((utm_x, utm_y))
            debug_coords.append((coord_id, utm_x, utm_y))

        if points:
            if points[0] != points[-1]:
                points.append(points[0])  # Polygon automatisch schließen

            clockwise = is_clockwise(points)
            if not clockwise:
                points.reverse()

            color = COLORS.get(area['type'], 'lightgrey')
            area_value = calculate_area(points, debug)

            # Wandle Punkte in eine SVG-gültige Liste um
            svg_points = [(int(round(x)), int(round(y))) for x, y in points]
            
            if debug:
                for point in svg_points:
                    print(f"DEBUG: Punkt: {point}, Typ: {type(point[0])}, {type(point[1])}")


            dwg.add(dwg.polygon(svg_points, fill=color, stroke='black', stroke_width=1))
            dwg.add(dwg.text(f"{area['comment']}: {area_value:.2f} m²", 
                             insert=(legend_x, legend_y + i * (font_size + 10)), 
                             fill="black", font_size=font_size))

            if debug:
                print(f"\nBereich: {area['comment']} mit Fläche: {area_value:.2f} m²")
                print("Koordinaten:")
                for coord_id, utm_x, utm_y in debug_coords:
                    print(f"  ID {coord_id}: {utm_x}, {utm_y}")
                print(f"Drehsinn: {'Uhrzeigersinn' if clockwise else 'Gegen den Uhrzeigersinn'}")
Die dabei erzeugte SVG Grafik Datei soll mir alle wichtigen Daten zu meinem Grundstück anzeigen. Leider hänge ich aktuell fest bei der Übergabe der Koordinaten an SVGwrite. Ich bekomme die ganze Zeit den Fehler, dass es sich um den falschen Typen für die übergebenen Koordinaten handelt.

Code: Alles auswählen

Flächenberechnung für folgende Koordinaten:
  (587210, 4751836)
  (587165, 4751775)
  (587162, 4751773)
...
DEBUG: Punkt: (587210, 4751836), Typ: <class 'int'>, <class 'int'>
DEBUG: Punkt: (587165, 4751775), Typ: <class 'int'>, <class 'int'>
DEBUG: Punkt: (587162, 4751773), Typ: <class 'int'>, <class 'int'>
...
Traceback (most recent call last):
  File "/home/markus/Nextcloud/Grundflaechenrechner/grundstueckshelfer-2025-02-12.py", line 155, in <module>
    main()
  File "/home/markus/Nextcloud/Grundflaechenrechner/grundstueckshelfer-2025-02-12.py", line 148, in main
    draw_areas(dwg, data["areas"], dict_refs_coord, offset_lat, offset_lon, debug=args.debug)
  File "/home/markus/Nextcloud/Grundflaechenrechner/grundstueckshelfer-2025-02-12.py", line 118, in draw_areas
    dwg.add(dwg.polygon(svg_points, fill=color, stroke='black', stroke_width=1))
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3/dist-packages/svgwrite/elementfactory.py", line 68, in __call__
    return self.cls(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3/dist-packages/svgwrite/shapes.py", line 122, in __init__
    self.validator.check_svg_type(x, 'coordinate')
  File "/usr/lib/python3/dist-packages/svgwrite/validator2.py", line 105, in check_svg_type
    raise TypeError("%s is not of type '%s'." % (value, typename))
TypeError: 587210 is not of type 'coordinate'.
Wäre super wenn ein Pro mir da weiterhelfen könnte. :D

Vielen lieben Dank
Benutzeravatar
__blackjack__
User
Beiträge: 13922
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Das kann man so ja nicht nachvollziehen. Grenz das mal ein und bastel ein minimal lauffähiges Beispiel, das dieses Verhalten zeigt.
“Java is a DSL to transform big Xml documents into long exception stack traces.”
— Scott Bellware
feldmaus
User
Beiträge: 287
Registriert: Donnerstag 12. Oktober 2006, 16:48

__blackjack__ hat geschrieben: Donnerstag 13. Februar 2025, 00:58 Das kann man so ja nicht nachvollziehen. Grenz das mal ein und bastel ein minimal lauffähiges Beispiel, das dieses Verhalten zeigt.
Konnte gerade nicht schlafen und habe mal Alles raus genommen was geht und dies nur als einfaches Script gespeichert:

Code: Alles auswählen

#!/usr/bin/python3

import svgwrite

# Direkte JSON-Daten ins Skript eingebaut
data = {
    "offset": {"lat": 475, "lon": 587},
    "areas": [
        {
            "type": "Wald",
            "coords": [
                [505, 1733, 93],
                [539, 1741, 94],
                [544, 1760, 95],
                [585, 1929, 96],
                [635, 1935, 97]
            ],
            "comment": "Wald neben Rasenkultur und östl. Pfad(r)."
        }
    ]
}

def draw_areas(dwg, areas, offset_lat, offset_lon):
    """Zeichnet die Flächen in das SVG."""
    points = []
    
    for area in areas:
        for coord in area['coords']:
            utm_x = int(f"{offset_lon}{int(coord[0])}")
            utm_y = int(f"{offset_lat}{int(coord[1])}")
            points.append((str(utm_x), str(utm_y)))

        points.append(points[0])  # Polygon schließen
        dwg.add(dwg.polygon(points, fill="green", stroke="black"))

def main():
    svg_file = "output.svg"
    
    offset_lat = data["offset"]["lat"]
    offset_lon = data["offset"]["lon"]
    
    dwg = svgwrite.Drawing(svg_file, profile="tiny", size=("1000px", "1000px"))
    draw_areas(dwg, data["areas"], offset_lat, offset_lon)
    dwg.save()
    print(f"SVG gespeichert als {svg_file}")

if __name__ == "__main__":
    main()
Sirius3
User
Beiträge: 18216
Registriert: Sonntag 21. Oktober 2012, 17:20

Ich weiß nicht wie man UTM GPS-Koordinaten umrechnet, da gibt es bestimmt schon was fertiges für Python.
Garantiert falsch ist es aber, verschiedene Zahlen in Strings zu verwandeln und einfach aneinander zu hängen.
Wo hast du die Anleitung für die Umrechnung her?
Wenn Du Linien zeichnen willst, mußt Du Koordinaten als Zahlen übergeben und nicht als Strings.
Benutzeravatar
__blackjack__
User
Beiträge: 13922
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Die Dokumentation sagt zum "tiny"-Profil:
https://svgwrite.readthedocs.io/en/latest/overview.html#numbers hat geschrieben:tiny profile: numbers must not have more than 4 decimal digits in the fractional part of their decimal expansion and must be in the range -32,767.9999 to +32,767.9999, inclusive
SVG Tiny macht IMHO auch nicht wirklich Sinn. Selbst aus heutiger Sicht schwache Smartphones kommen mit dem normalen SVG-Standard klar. Einfach das ``profile="tiny"`` da rausnehmen und dann sind auch Koordinaten grösser als 32.768 kein Problem mehr.

Anmerkungen: `svg_file` sollte besser `svg_filename` heissen. Bei `file` erwartet der Leser ein Datei-Objekt mit `read()` oder `write()` und `close()`-Methoden.

Es gibt hier nur eine Fläche darum fällt der Fehler nicht auf: Die Initialisierung von `points` mit einer leeren Liste gehört in die erste ``for``-Schleife, nicht davor.

Den Inhalt der `area`-Elemente würde ich schon in der ``for``-Schleife auf sprechende Bezeichner entpacken statt im Schleifenkörper ”magische” Indexwerte zu verwenden.

Die Koordinaten sind schon ganze Zahlen, da noch mal `int()` drauf anzuwenden macht keinen Sinn.

Das mit dem Offset als Zeichenkette vor die Koordinaten zu pappen und diese Zeichenkette wieder in eine Zahl umzuwandeln ist nicht wirklich robust. Rechnungen über Zeichenkettenoperationen auszudrücken, ist an sich ja schon ein Hack — hier ist die Korrektheit des Ergebnisses auch von der Anzahl der Ziffern im zweiten Operanden abhängig.

Die Koordinaten nach dem `int()` um aus der Zeichenkette eine Zahl zu machen sofort im nächsten Schritt wieder mit `str()` in eine Zeichenkette zu wandeln, macht nicht so wirklich Sinn.

Wenn man das ändert, wird die Berechnung deutlich einfacher und man `points` dann auch mit einer lesbaren „list comprehension“ erstellen.

Code: Alles auswählen

#!/usr/bin/python3

import svgwrite

# Direkte JSON-Daten ins Skript eingebaut
DATA = {
    "offset": {"lat": 475, "lon": 587},
    "areas": [
        {
            "type": "Wald",
            "coords": [
                [505, 1733, 93],
                [539, 1741, 94],
                [544, 1760, 95],
                [585, 1929, 96],
                [635, 1935, 97],
            ],
            "comment": "Wald neben Rasenkultur und östl. Pfad(r).",
        }
    ],
}


def draw_areas(drawing, areas, offset_lat, offset_lon):
    """
    Zeichnet die Flächen in das SVG.
    """
    for area in areas:
        points = [
            (offset_lon + x, offset_lat + y) for x, y, _ in area["coords"]
        ]
        points.append(points[0])  # Polygon schließen.
        drawing.add(drawing.polygon(points, fill="green", stroke="black"))


def main():
    svg_filename = "output.svg"
    drawing = svgwrite.Drawing(svg_filename, size=("1000px", "1000px"))
    offset_data = DATA["offset"]
    draw_areas(
        drawing,
        DATA["areas"],
        offset_data["lat"] * 10000,
        offset_data["lon"] * 1000,
    )
    drawing.save()
    print(f"SVG gespeichert als {svg_filename}")


if __name__ == "__main__":
    main()
@Sirius: Die Bibliothek kommt auch mit Zeichenketten zurecht, denn die kann auch Einheiten, also man kann 42, "42", "42px", oder auch "10cm" übergeben wenn Koordinaten oder Längen erwartet werden. Wie bei SVG selbst auch.

Edit: Was das umrechnen angeht: Wenn ich mir die Werte so anschaue sollte der Offset wahrscheinlich eher von den Koordinatenwerten abgezogen werden. Ist aber nur eine Vermutung.
“Java is a DSL to transform big Xml documents into long exception stack traces.”
— Scott Bellware
feldmaus
User
Beiträge: 287
Registriert: Donnerstag 12. Oktober 2006, 16:48

Danke, das hat mich schon mal echt weiter gebracht. Nun habe ich den Offset, in der Datendatei, auf die richtige Zehnerpotenz angehoben. Gerne würde ich die Einheit Meter[m] mit beifügen zu den Zahlen in der Datendatei, aber dann kann ich den Offset und die Koordinaten nicht addieren. Oder bietet da <svg*> eine Lösung?

Code: Alles auswählen

data = {
    "offset": {"lat": "475000m", "lon": "5870000m"},
    "areas": [
        {
            "type": "Gesamter Bereich",
            "coords": [
                ["210.13m", "1836.64m", 1],
                ["165.32m", "1775.80m", 2],
                ["162.42m", "1773.81m", 3]
            ],
            "comment": "Gesamter Bereich"
        }
    ]
}
Benutzeravatar
__blackjack__
User
Beiträge: 13922
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@feldmaus: Sind das denn Meter? Und wenn alles in Metern ist, dann macht das nicht wirklich Sinn sich das Leben unnötig schwer zu machen in dem man überall an jede Zahl noch mal dran schreibt, das es Meter sind. Ist auch etwas ungewöhnlich für Länge und Breite Meter anzugeben.
“Java is a DSL to transform big Xml documents into long exception stack traces.”
— Scott Bellware
Benutzeravatar
noisefloor
User
Beiträge: 4149
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,

ich verstehe das (in Teilen selbst kreiert) Datenformat nicht bzw. das macht IMHO in Teilen keinen Sinn. Warum nummerierst du die Koordinaten durch? Und wenn das irgendwie wichtig wäre, dann wäre es IMHO sinnvoller, entweder die Nummerierung als Schlüssel für ein Dict zu nehmen und die Koordinaten als Werte oder man nimmt für jeden Punkt ein DataClass Objekt.
Allerdings habe ich auch einen Mechanismus eingebaut, damit statt wiederkehrenden Koordinaten Paaren nur die ID angeben brauche:
Wieso gibt es denn überhaupt wiederkehrende Koordinatenpaare? Dürfe es gar nicht geben... wenn man eine Fläche als Polygon darstellt, gibt es jeden Punkt genau ein Mal, nämlich die jeweiligen Eckpunkte des Polygons.

@Sirius3:
Ich weiß nicht wie man UTM GPS-Koordinaten umrechnet, da gibt es bestimmt schon was fertiges für Python.
ja, gibt es: https://pypi.org/project/utm/ . Das rechnet UTM Koordinaten in WGS84 um, womit sich IMHO einfacher rechnen lässt, weil das vom Prinzip einem "normalen" Koodinatensystem mit klassischem Nullpunkt entspricht. Bei UTM wusste ich bisher nur, dass es schon länger im militärischen Bereich gängig ist. Glaubt man Wikipedia, wird UTM in der Kartographie jetzt aber auch bevorzugt benutzt. Und was ich auch nicht wusste ist, dass UTM im 2. Weltkrieg von den Nazis erfunden wurde. Ich dachte immer, dass kam vom US Militär. Die haben das scheinbar aber nur adaptiert.

Gruß, noisefloor
Benutzeravatar
__blackjack__
User
Beiträge: 13922
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@noisefloor: Eigentlich sind es ja mehrere Flächen die direkt aneinander angrenzen, da hat man dann gemeinsame Punkte. Vielleicht war das gemeint.

Für diesen ganzen „geospatial“-Kram gibt es einiges an Bibliotheken was Projektionen/Umrechnungen von Koordinatensystemen aber auch beispielsweise Flächenberechnung angeht. Und es gibt Formate wie GeoJSON. Bevor man eigene Dateiformate erfindet, sollte man IMHO immer erst mal schauen was es da schon so gibt.
“Java is a DSL to transform big Xml documents into long exception stack traces.”
— Scott Bellware
Antworten