Höhenprofil 10 km pro Seite einer Linie


15

Wie kann ich ein Höhenprofil für ein Geländeband erhalten?

Die höchste Erhebung innerhalb von 10 km (auf jeder Seite der definierten Linie) sollte berücksichtigt werden.

Ich hoffe meine Frage ist klar. Vielen Dank im Voraus.


Ist die Linie, die Ihr Profil definiert, eine einfache gerade Linie oder besteht sie aus mehreren Segmenten mit Ecken?
Jake

Die Linie besteht aus mehreren Segmenten. Aber alle Segmente sind gerade Linien. :) Ich meine, es gibt keine Kurven.
Kara

Nur ... wie sie sagen ... Spitballing ... aber könnten Sie die Linie mit einem 10 km Puffer puffern. dann alle Features im Puffer auswählen ... dann die höchsten Werte auswählen?
Ger

1
Könnten Sie ein Bild davon liefern, was Sie erreichen möchten?
Alexandre Neto

@ Alex: das Ergebnis, das ich will, ist ein regulärer Höhenverlauf. Bei einem Puffer von 10 km wird der höchste Wert 10 km auf jeder Seite des ausgewählten Pfads in der Grafik angezeigt.
Kara

Antworten:


14

Nach den Kommentaren folgt hier eine Version, die mit senkrechten Liniensegmenten arbeitet. Bitte mit Vorsicht verwenden, da ich es nicht gründlich getestet habe!

Diese Methode ist weitaus umständlicher als die Antwort von @ whuber - teils, weil ich kein sehr guter Programmierer bin, und teils, weil die Vektorverarbeitung ein bisschen kompliziert ist. Ich hoffe, es wird Ihnen zumindest den Einstieg erleichtern, wenn Sie senkrechte Liniensegmente benötigen.

Hier finden Sie die haben müssen Shapely , Fiona und Numpy Python - Pakete installiert (zusammen mit ihren Abhängigkeiten) dies auszuführen.

#-------------------------------------------------------------------------------
# Name:        perp_lines.py
# Purpose:     Generates multiple profile lines perpendicular to an input line
#
# Author:      JamesS
#
# Created:     13/02/2013
#-------------------------------------------------------------------------------
""" Takes a shapefile containing a single line as input. Generates lines
    perpendicular to the original with the specified length and spacing and
    writes them to a new shapefile.

    The data should be in a projected co-ordinate system.
"""

import numpy as np
from fiona import collection
from shapely.geometry import LineString, MultiLineString

# ##############################################################################
# User input

# Input shapefile. Must be a single, simple line, in projected co-ordinates
in_shp = r'D:\Perp_Lines\Centre_Line.shp'

# The shapefile to which the perpendicular lines will be written
out_shp = r'D:\Perp_Lines\Output.shp'

# Profile spacing. The distance at which to space the perpendicular profiles
# In the same units as the original shapefile (e.g. metres)
spc = 100

# Length of cross-sections to calculate either side of central line
# i.e. the total length will be twice the value entered here.
# In the same co-ordinates as the original shapefile
sect_len = 1000
# ##############################################################################

# Open the shapefile and get the data
source = collection(in_shp, "r")
data = source.next()['geometry']
line = LineString(data['coordinates'])

# Define a schema for the output features. Add a new field called 'Dist'
# to uniquely identify each profile
schema = source.schema.copy()
schema['properties']['Dist'] = 'float'

# Open a new sink for the output features, using the same format driver
# and coordinate reference system as the source.
sink = collection(out_shp, "w", driver=source.driver, schema=schema,
                  crs=source.crs)

# Calculate the number of profiles to generate
n_prof = int(line.length/spc)

# Start iterating along the line
for prof in range(1, n_prof+1):
    # Get the start, mid and end points for this segment
    seg_st = line.interpolate((prof-1)*spc)
    seg_mid = line.interpolate((prof-0.5)*spc)
    seg_end = line.interpolate(prof*spc)

    # Get a displacement vector for this segment
    vec = np.array([[seg_end.x - seg_st.x,], [seg_end.y - seg_st.y,]])

    # Rotate the vector 90 deg clockwise and 90 deg counter clockwise
    rot_anti = np.array([[0, -1], [1, 0]])
    rot_clock = np.array([[0, 1], [-1, 0]])
    vec_anti = np.dot(rot_anti, vec)
    vec_clock = np.dot(rot_clock, vec)

    # Normalise the perpendicular vectors
    len_anti = ((vec_anti**2).sum())**0.5
    vec_anti = vec_anti/len_anti
    len_clock = ((vec_clock**2).sum())**0.5
    vec_clock = vec_clock/len_clock

    # Scale them up to the profile length
    vec_anti = vec_anti*sect_len
    vec_clock = vec_clock*sect_len

    # Calculate displacements from midpoint
    prof_st = (seg_mid.x + float(vec_anti[0]), seg_mid.y + float(vec_anti[1]))
    prof_end = (seg_mid.x + float(vec_clock[0]), seg_mid.y + float(vec_clock[1]))

    # Write to output
    rec = {'geometry':{'type':'LineString', 'coordinates':(prof_st, prof_end)},
           'properties':{'Id':0, 'Dist':(prof-0.5)*spc}}
    sink.write(rec)

# Tidy up
source.close()
sink.close()

Das folgende Bild zeigt ein Beispiel für die Ausgabe des Skripts. Sie fügen ein Shapefile ein, das Ihre Mittellinie darstellt, und geben die Länge der senkrechten Linien und deren Abstand an. Die Ausgabe ist ein neues Shapefile mit den roten Linien in diesem Bild, denen jeweils ein Attribut zugeordnet ist, das den Abstand vom Anfang des Profils angibt.

Beispiel für eine Skriptausgabe

Wie @whuber in den Kommentaren gesagt hat, ist der Rest ziemlich einfach, sobald Sie zu diesem Zeitpunkt sind. Das folgende Bild zeigt ein weiteres Beispiel, bei dem die Ausgabe zu ArcMap hinzugefügt wurde.

Bildbeschreibung hier eingeben

Verwenden Sie das Werkzeug Feature zu Raster , um die senkrechten Linien in ein kategoriales Raster zu konvertieren. Legen Sie das Raster VALUEals DistFeld im Ausgabe-Shapefile fest. Denken Sie auch daran das Werkzeug zu setzen , Environmentsso dass Extent, Cell sizeund Snap rastersind die gleichen wie für die zugrunde liegende DEM. Am Ende sollten Sie eine Rasterdarstellung Ihrer Linien erhalten, etwa so:

Bildbeschreibung hier eingeben

Konvertieren Sie dieses Raster in ein ganzzahliges Raster (mit dem Int- Tool oder dem Raster-Rechner) und verwenden Sie es als Eingabezone für das Zonal Statistics- Tool als Tabelle . Sie sollten eine Ausgabetabelle wie diese erhalten:

Bildbeschreibung hier eingeben

Das VALUEFeld in dieser Tabelle gibt den Abstand vom Anfang der ursprünglichen Profillinie an. Die anderen Spalten geben verschiedene Statistiken (Maximum, Mittelwert usw.) für die Werte in jedem Transekt an. Sie können diese Tabelle verwenden, um Ihr Zusammenfassungsprofil zu zeichnen.

NB: Ein offensichtliches Problem bei dieser Methode ist, dass sich einige der Transect-Linien überlappen können, wenn Ihre ursprüngliche Linie sehr verwackelt ist. Die Tools für Zonenstatistiken in ArcGIS können keine überlappenden Zonen verarbeiten. In diesem Fall hat eine Ihrer Transaktionslinien Vorrang vor der anderen. Dies kann ein Problem für das sein, was Sie tun.

Viel Glück!


3
+1 Das ist ein schöner Anfang für einen tollen Beitrag! Wenn Sie sich die zweite Abbildung genauer ansehen, werden Sie einige kürzere Schnitte bemerken: Sie kreuzen sich in der Nähe von Kurven. Dies liegt daran, dass Ihr Algorithmus zur fehlerhaften Berechnung der Transekte davon ausgeht, dass die Verschiebung jedes Segments gleich spcist, Biegungen jedoch die Verschiebungen verkürzen. Stattdessen sollten Sie den Querrichtungsvektor normalisieren (seine Komponenten durch die Länge des Vektors dividieren) und diesen dann mit dem gewünschten Radius des Transektors multiplizieren.
Whuber

Du hast völlig recht - danke für das Feedback @whuber! Hoffentlich jetzt behoben ...
JamesS

Lieber James, ich werde es versuchen, vielen Dank. Diese Lösung passt perfekt.
Kara

11

Die höchste Erhebung innerhalb von 10 km ist der mit einem kreisförmigen Radius von 10 km berechnete Nachbarschaftsmaximalwert. Extrahieren Sie daher einfach ein Profil dieses Nachbarschaftsmaximalrasters entlang der Flugbahn.

Beispiel

Hier ist ein hügeliger DEM mit einer Flugbahn (schwarze Linie von unten nach oben):

DEM

Dieses Bild ist ungefähr 17 mal 10 Kilometer groß. Ich habe einen Radius von nur 1 km anstelle von 10 km gewählt, um die Methode zu veranschaulichen. Der 1 km lange Puffer ist gelb umrandet.

Das Nachbarschaftsmaximum eines DEM sieht immer etwas seltsam aus, da es dazu neigt, an Punkten an Wert zu gewinnen, an denen ein Maximum (vielleicht eine Bergspitze) knapp über 10 km und ein anderes Maximum auf einer anderen Höhe knapp unter 10 km liegt . Insbesondere Hügel, die ihre Umgebung dominieren, tragen zu perfekten Wertekreisen bei, die auf dem Punkt der lokalen Maximalhöhe zentriert sind:

Wohngegend max

Dunkler ist höher auf dieser Karte.

Hier ist eine Darstellung der Profile des ursprünglichen DEM (blau) und des Nachbarschaftsmaximums (rot):

Profile

Es wurde berechnet, indem die Flugbahn in regelmäßig beabstandete Punkte mit einem Abstand von 0,1 km (beginnend an der Südspitze) aufgeteilt wurde, die Höhen an diesen Punkten extrahiert wurden und ein verbundenes Streudiagramm der resultierenden Dreifachwerte (Abstand vom Anfang, Höhe, maximale Höhe) erstellt wurde. Der Punktabstand von 0,1 km wurde so gewählt, dass er wesentlich kleiner als der Pufferradius ist, aber groß genug, um die Berechnung schnell durchzuführen (es war augenblicklich).


Das ist aber nicht ganz richtig, oder? Sollte nicht anstelle eines kreisförmigen Puffers um jeden Punkt eine orthogonale Linie mit einer Länge von 20 km verwendet werden, um das zugrunde liegende Raster abzutasten? Zumindest würde ich so Karas Forderung interpretieren, "den höchsten Wert innerhalb von 10 km auf jeder Seite der Linie" zu berücksichtigen.
Jake

4
@Jake Ich würde nicht sagen "ungenau": Sie bieten nur eine alternative Interpretation. "Auf jeder Seite von" ist ein vager Begriff, der eine bessere Qualifikation gebrauchen könnte. Ich kann Lösungen für Interpretationen wie Ihre vorschlagen. Eine Methode verwendet ein zonales Maximum. Es ist jedoch komplizierter und viel langsamer in der Ausführung. Warum sehen wir nicht zuerst, was das OP von dieser einfachen Lösung hält?
Whuber

Schlechte Wortwahl, ich hätte "genau" nicht verwenden sollen - tut mir leid
Jake

1
Da Sie mit dem Profil-Tool vertraut sind, sind Sie fast fertig. QGIS verfügt über Schnittstellen zu GRASS, die Nachbarschaftsoperationen umfassen. Wenden Sie einfach die Nachbarschaftsmaximaloperation mit r.Nachbarn an und profilieren Sie das Ergebnis.
Whuber

1
@JamesS Sie möchten keine Parallelverschiebung durchführen, sondern jedes Querprofil senkrecht zur Mittellinie ausrichten. (Der Ansatz der parallelen Verschiebung kann genau so implementiert werden, wie ich es hier beschreibe, indem eine angemessene lange und dünne Nachbarschaft für die Berechnung der maximalen Nachbarschaft verwendet wird.) Ich bin mir ziemlich sicher, dass Sie auf dieser Site Code zum Erstellen von Mengen von gleich beabstandeten senkrechten Liniensegmenten finden entlang einer Polylinie; Das ist der schwierige Teil. Alles andere ist nur eine Frage der Extraktion der DEM-Werte in diesen Segmenten und ihrer Zusammenfassung.
Whuber

6

Ich hatte das gleiche Problem und versuchte es mit der Lösung von James S. Aber ich brachte die GDAL nicht dazu, mit Fiona zusammenzuarbeiten.

Dann habe ich den SAGA-Algorithmus "Cross Profiles" in QGIS 2.4 entdeckt und genau das Ergebnis erhalten, das ich wollte und von dem ich annehme, dass Sie auch danach suchen (siehe unten).

Bildbeschreibung hier eingeben


Hallo, ich bin gerade auf diesen Beitrag von vor ein paar Jahren gestoßen. Ich stehe vor dem gleichen Problem wie der Thread-Starter, und da ich mit (Q) GIS noch nicht so weit gekommen bin, bin ich irgendwie froh, dass ich so weit gekommen bin wie auf dem Bild oben. Wie arbeite ich aber mit den Daten? Der Layer "Querprofile" zeigt die Höhe für jeden abgetasteten Punkt. Ich bitte Sie jedoch um Hilfe, 1) um die maximale Höhe für jede Querlinie zu ermitteln. 2) um die Koordinaten für den Schnittpunkt mit dem ursprünglichen Pfad zu ermitteln. 3) um die maximalen Höhen von 1 mit den Koordinaten von 2. Kann mir bitte jemand helfen? Vielen Dank im Voraus! Mal
Cpt Reynolds

6

Für alle Interessierten ist hier eine modifizierte Version von JamesS-Code, mit der senkrechte Linien nur mit Numpy- und Osgeo-Bibliotheken erstellt werden. Dank JamesS hat mir seine Antwort heute sehr geholfen!

import osgeo
from osgeo import ogr
import numpy as np

# ##############################################################################
# User input

# Input shapefile. Must be a single, simple line, in projected co-ordinates
in_shp = r'S:\line_utm_new.shp'

# The shapefile to which the perpendicular lines will be written
out_shp = r'S:\line_utm_neu_perp.shp'

# Profile spacing. The distance at which to space the perpendicular profiles
# In the same units as the original shapefile (e.g. metres)
spc = 100

# Length of cross-sections to calculate either side of central line
# i.e. the total length will be twice the value entered here.
# In the same co-ordinates as the original shapefile
sect_len = 1000
# ##############################################################################

# Open the shapefile and get the data
driverShp = ogr.GetDriverByName('ESRI Shapefile')
sourceShp = driverShp.Open(in_shp, 0)
layerIn = sourceShp.GetLayer()
layerRef = layerIn.GetSpatialRef()

# Go to first (and only) feature
layerIn.ResetReading()
featureIn = layerIn.GetNextFeature()
geomIn = featureIn.GetGeometryRef()

# Define a shp for the output features. Add a new field called 'M100' where the z-value 
# of the line is stored to uniquely identify each profile
outShp = driverShp.CreateDataSource(out_shp)
layerOut = outShp.CreateLayer('line_utm_neu_perp', layerRef, osgeo.ogr.wkbLineString)
layerDefn = layerOut.GetLayerDefn() # gets parameters of the current shapefile
layerOut.CreateField(ogr.FieldDefn('M100', ogr.OFTReal))

# Calculate the number of profiles/perpendicular lines to generate
n_prof = int(geomIn.Length()/spc)

# Define rotation vectors
rot_anti = np.array([[0, -1], [1, 0]])
rot_clock = np.array([[0, 1], [-1, 0]])

# Start iterating along the line
for prof in range(1, n_prof):
    # Get the start, mid and end points for this segment
    seg_st = geomIn.GetPoint(prof-1) # (x, y, z)
    seg_mid = geomIn.GetPoint(prof)
    seg_end = geomIn.GetPoint(prof+1)

    # Get a displacement vector for this segment
    vec = np.array([[seg_end[0] - seg_st[0],], [seg_end[1] - seg_st[1],]])    

    # Rotate the vector 90 deg clockwise and 90 deg counter clockwise
    vec_anti = np.dot(rot_anti, vec)
    vec_clock = np.dot(rot_clock, vec)

    # Normalise the perpendicular vectors
    len_anti = ((vec_anti**2).sum())**0.5
    vec_anti = vec_anti/len_anti
    len_clock = ((vec_clock**2).sum())**0.5
    vec_clock = vec_clock/len_clock

    # Scale them up to the profile length
    vec_anti = vec_anti*sect_len
    vec_clock = vec_clock*sect_len

    # Calculate displacements from midpoint
    prof_st = (seg_mid[0] + float(vec_anti[0]), seg_mid[1] + float(vec_anti[1]))
    prof_end = (seg_mid[0] + float(vec_clock[0]), seg_mid[1] + float(vec_clock[1]))

    # Write to output
    geomLine = ogr.Geometry(ogr.wkbLineString)
    geomLine.AddPoint(prof_st[0],prof_st[1])
    geomLine.AddPoint(prof_end[0],prof_end[1])
    featureLine = ogr.Feature(layerDefn)
    featureLine.SetGeometry(geomLine)
    featureLine.SetFID(prof)
    featureLine.SetField('M100',round(seg_mid[2],1))
    layerOut.CreateFeature(featureLine)

# Tidy up
outShp.Destroy()
sourceShp.Destroy()

Danke Ket - ich habe es versucht, aber es funktioniert leider nicht vollständig für mich. Ich habe dem Skript ein Shapefile mit einem einzelnen Polylinien-Feature gegeben, aber meine Ausgabe ist nur die Attributtabelle mit vielen Nullen für den Wert "M100" - es werden keine Features in der Karte angezeigt. Ideen?
Davehughes87

Es macht nichts - jetzt wird Ihnen klar, dass Ihr Skript am ENDE jedes Segments der Polylinie senkrechte Linien zu berechnen scheint, nicht alle "spc" -Meter. Das bedeutet, dass mir die Polylinie ausgeht, mit der ich arbeiten konnte, bevor n_prof in der Schleife erreicht wurde und "nan" -Werte erzeugt wurden.
Davehughes87
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.