Profilo altimetrico 10 km per lato di una linea


15

Come posso ottenere un profilo altimetrico per una fascia di terreno?

L'altitudine massima entro 10 km (su ciascun lato della linea definita) deve essere presa in considerazione.

Spero che la mia domanda sia chiara. Grazie mille in anticipo.


La linea che definisce il tuo profilo è una semplice linea retta o è composta da più segmenti con angoli?
Jake,

La linea è composta da diversi segmenti. Ma tutti i segmenti sono in linea retta. :) Voglio dire, non ci sono curve.
Kara,

Solo ... come si dice ... sputa la palla ... ma potresti tamponare la linea con un buffer di 10 km. quindi selezionare tutte le funzionalità all'interno del buffer ... quindi selezionare i valori più alti?
Ger

1
Potresti fornire un'immagine di ciò che vuoi raggiungere?
Alexandre Neto,

@ Alex: il risultato che voglio è un grafico di elevazione regolare. Ma con un buffer di 10 km in modo che il valore più alto di 10 km ogni lato del percorso selezionato sia mostrato sul grafico.
Kara,

Risposte:


14

Seguendo i commenti, ecco una versione che funziona con segmenti di linea perpendicolari. Si prega di usare con cautela poiché non l'ho testato a fondo!

Questo metodo è molto più goffo della risposta di @ whuber - in parte perché non sono un programmatore molto bravo, e in parte perché l'elaborazione vettoriale è un po 'faff. Spero che ti inizi almeno se i segmenti di linea perpendicolare sono ciò di cui hai bisogno.

Per eseguire questo, avrai bisogno dei pacchetti Shapely , Fiona e Numpy Python installati (insieme alle loro dipendenze).

#-------------------------------------------------------------------------------
# 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()

L'immagine seguente mostra un esempio dell'output dello script. Inserite uno shapefile che rappresenta la vostra linea centrale e specificate la lunghezza delle linee perpendicolari e la loro spaziatura. L'output è un nuovo file di forma contenente le linee rosse in questa immagine, ognuna delle quali ha un attributo associato che specifica la sua distanza dall'inizio del profilo.

Esempio di output dello script

Come ha detto @whuber nei commenti, una volta arrivato a questo punto il resto è abbastanza facile. L'immagine seguente mostra un altro esempio con l'output aggiunto ad ArcMap.

inserisci qui la descrizione dell'immagine

Utilizzare lo strumento Feature to Raster per convertire le linee perpendicolari in un raster categoriale. Impostare il raster VALUEcome Distcampo nel file di forma di output. Ricorda anche di impostare lo strumento Environmentsin modo che Extent, Cell sizee Snap rastersono gli stessi che per il DEM sottostante. Dovresti finire con una rappresentazione raster delle tue linee, qualcosa del genere:

inserisci qui la descrizione dell'immagine

Infine, converti questo raster in una griglia intera (usando lo strumento Int o la calcolatrice raster) e usalo come zone di input per lo strumento Statistiche zonali come tabella . Dovresti finire con una tabella di output come questa:

inserisci qui la descrizione dell'immagine

Il VALUEcampo in questa tabella indica la distanza dall'inizio della linea del profilo originale. Le altre colonne forniscono varie statistiche (massimo, media ecc.) Per i valori in ciascun transetto. È possibile utilizzare questa tabella per tracciare il profilo di riepilogo.

NB: Un ovvio problema con questo metodo è che, se la linea originale è molto irregolare, alcune delle linee di transetto potrebbero sovrapporsi. Gli strumenti di statistica zonale in ArcGIS non sono in grado di gestire le zone sovrapposte, quindi quando ciò accade una delle tue linee di transetto avrà la precedenza sull'altra. Questo può o meno essere un problema per quello che stai facendo.

In bocca al lupo!


3
+1 È un buon inizio per un grande contributo! Se osservi attentamente la seconda figura, noterai alcuni transetti più corti: sono quelli che attraversano vicino alle curve. Questo perché l'algoritmo per calcolare i transetti presuppone erroneamente che lo spostamento di ciascun segmento sarà uguale spc, ma le curve accorciano gli spostamenti. Invece, dovresti normalizzare il vettore di direzione trasversale (dividere i suoi componenti per la lunghezza del vettore) e quindi moltiplicarlo per il raggio desiderato del transetto.
whuber

Hai perfettamente ragione - grazie per il feedback @whuber! Speriamo di aver risolto il
problema

Caro James, proverò questo grazie mille. Questa soluzione si adatta perfettamente.
Kara,

11

L'altitudine più alta entro 10 km è il valore massimo del quartiere calcolato con un raggio circolare di 10 km, quindi basta estrarre un profilo di questa griglia massima del quartiere lungo la traiettoria.

Esempio

Ecco un DEM con le colline con una traiettoria (linea nera che va dal basso verso l'alto):

DEM

Questa immagine è di circa 17 per 10 chilometri. Ho scelto un raggio di solo 1 km anziché 10 km per illustrare il metodo. Il suo buffer di 1 km è mostrato delineato in giallo.

Il massimo del vicinato di un DEM sembrerà sempre un po 'strano, perché tenderà a saltare di valore in punti in cui un massimo (una cima di una collina, forse) cade appena oltre 10 km e un altro massimo a una diversa altitudine arriva appena entro 10 km . In particolare, le colline che dominano l'ambiente circostante contribuiranno a cerchi perfetti di valori centrati nel punto di massima elevazione locale:

Quartiere max

Più scuro è più alto su questa mappa.

Ecco una trama dei profili del DEM originale (blu) e del massimo del quartiere (Rosso):

Profili

È stato calcolato dividendo la traiettoria in punti regolarmente distanziati a 0,1 km di distanza (a partire dalla punta meridionale), estraendo le quote in quei punti e creando un diagramma a dispersione unito delle triple risultanti (distanza dall'inizio, elevazione, elevazione massima). La distanza tra i punti di 0,1 km è stata scelta per essere sostanzialmente più piccola del raggio del buffer ma abbastanza grande da rendere il calcolo veloce (era istantaneo).


Non è del tutto esatto, giusto? Invece di un buffer circolare attorno a ciascun punto, non dovrebbe essere usata una linea ortogonale di 20 km per campionare il raster sottostante? Almeno è così che interpreterei il requisito di Kara di "prendere in considerazione il valore più alto entro 10 km su ciascun lato della linea".
Jake,

4
@jake Non direi "impreciso": offri semplicemente un'interpretazione alternativa. "Da ogni lato di" è un termine vago che potrebbe utilizzare una migliore qualificazione. Posso proporre soluzioni per interpretazioni come la tua; un metodo utilizza un massimo zonale. Tuttavia, è più complicato e molto più lento nell'esecuzione. Perché non vediamo prima cosa pensa l'OP di questa semplice soluzione?
whuber

Cattiva scelta delle parole, non avrei dovuto usare "preciso" - mi dispiace per quello
Jake

1
Poiché sai come utilizzare lo strumento Profilo, hai quasi finito. QGIS ha interfacce per GRASS che includono operazioni di vicinato. Basta applicare l'operazione massima di vicinato usando r.neighbors e profilarne il risultato.
whuber

1
@JamesS Non vuoi fare uno spostamento parallelo, vuoi rendere ogni profilo incrociato perpendicolare alla linea centrale. (L'approccio a spostamento parallelo può essere implementato esattamente come descrivo qui usando un quartiere lungo e magro appropriato per il calcolo del vicinato massimo.) Sono abbastanza sicuro che puoi trovare codice su questo sito per costruire set di segmenti di linea perpendicolari equidistanti lungo una polilinea; questa è la parte difficile. Tutto il resto è solo una questione di estrazione dei valori DEM lungo quei segmenti e riassumendoli.
whuber

6

Ho avuto lo stesso problema e ho provato la soluzione di James S, ma non sono riuscito a far funzionare GDAL con Fiona.

Poi ho scoperto l'algoritmo SAGA "Cross Profiles" in QGIS 2.4, e ho ottenuto esattamente il risultato che volevo e presumo che anche tu stia cercando (vedi sotto).

inserisci qui la descrizione dell'immagine


Ciao, mi sono appena imbattuto in questo post di qualche anno fa. Sto affrontando lo stesso problema dell'avviatore di thread e, essendo molto nuovo in (Q) GIS, sono abbastanza felice di essere arrivato fino all'immagine sopra. Come posso lavorare con i dati, però? Il livello Profili incrociati mostra l'elevazione per ciascun punto campionato, ma chiedo gentilmente aiuto in 1) trovare l'elevazione massima per ciascuna linea trasversale 2) trovare le coordinate dell'intersezione con il percorso originale 3) associando le elevazioni massime da 1 con le coordinate da 2. Qualcuno può aiutare, per favore? Grazie mille in anticipo! Mal
Cpt Reynolds,

6

Per chiunque sia interessato, ecco una versione modificata del codice JamesS che crea linee perpendicolari utilizzando solo le librerie numpy e osgeo. Grazie a JamesS, la sua risposta mi ha aiutato molto oggi!

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()

Grazie, ho provato, ma sfortunatamente non funziona completamente per me. Ho dato allo script un file di forma con una singola funzione polilinea, ma il mio output è solo la tabella degli attributi con molti zeri per il valore "M100" - nessuna funzionalità mostrata nella mappa. Idee?
davehughes87,

Poco male - ho capito ora che la tua sceneggiatura sembra calcolare linee perpendicolari alla fine di ogni segmento della polilinea, non tutti i misuratori "spc". Ciò significa che stavo esaurendo la polilinea con cui lavorare prima che n_prof venisse raggiunto nel loop e che venissero prodotti valori "nan".
davehughes87,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.