Perfil de elevación 10 km a cada lado de una línea


15

¿Cómo puedo obtener un perfil de elevación para una banda de terreno?

Se debe tener en cuenta la elevación más alta dentro de los 10 km (a cada lado de la línea definida).

Espero que mi pregunta sea clara. Muchas gracias por adelantado.


¿La línea que define su perfil es una línea recta simple o consiste en varios segmentos con esquinas?
Jake

La línea consta de varios segmentos. Pero todos los segmentos son en línea recta. :) Quiero decir que no hay curvas.
Kara

Solo ... como dicen ... sppitballing ... pero ¿podrías amortiguar la línea con un buffer de 10 km? luego seleccione todas las características dentro del búfer ... luego seleccione los valores más altos?
Ger

1
¿Podrías proporcionar una imagen de lo que quieres lograr?
Alexandre Neto

@ Alex: el resultado que quiero es un gráfico de elevación normal. Pero con un búfer de 10 km para que el valor más alto de 10 km a cada lado de la ruta seleccionada se muestre en el gráfico.
Kara

Respuestas:


14

A continuación de los comentarios, aquí hay una versión que funciona con segmentos de línea perpendiculares. ¡Utilice con precaución ya que no lo he probado a fondo!

Este método es mucho más torpe que la respuesta de @ whuber, en parte porque no soy un buen programador y en parte porque el procesamiento de vectores es un poco falso. Espero que al menos te ayude a comenzar si los segmentos de línea perpendicular son lo que necesitas.

Deberá tener instalados los paquetes Shapely , Fiona y Numpy Python (junto con sus dependencias) para ejecutar esto.

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

La imagen a continuación muestra un ejemplo de la salida del script. Se alimenta en un archivo de forma que representa su línea central y especifica la longitud de las líneas perpendiculares y su espaciado. El resultado es un nuevo archivo de forma que contiene las líneas rojas en esta imagen, cada una de las cuales tiene un atributo asociado que especifica su distancia desde el inicio del perfil.

Ejemplo de salida de script

Como ha dicho @whuber en los comentarios, una vez que hayas llegado a esta etapa, el resto es bastante fácil. La imagen a continuación muestra otro ejemplo con la salida agregada a ArcMap.

ingrese la descripción de la imagen aquí

Use la herramienta Característica a ráster para convertir las líneas perpendiculares en un ráster categórico. Configure el ráster VALUEpara que sea el Distcampo en el archivo de forma de salida. Asimismo, recuerda a configurar la herramienta Environmentsde manera que Extent, Cell sizey Snap rasterson los mismos que para su DEM subyacente. Debería terminar con una representación ráster de sus líneas, algo como esto:

ingrese la descripción de la imagen aquí

Finalmente, convierta este ráster en una cuadrícula entera (usando la herramienta Int o la calculadora ráster), y úselo como zonas de entrada para la herramienta Estadística zonal como tabla . Debería terminar con una tabla de salida como esta:

ingrese la descripción de la imagen aquí

El VALUEcampo en esta tabla proporciona la distancia desde el inicio de la línea de perfil original. Las otras columnas dan varias estadísticas (máximo, promedio, etc.) para los valores en cada transecto. Puede usar esta tabla para trazar su perfil de resumen.

NB: Un problema obvio con este método es que, si su línea original es muy ondulada, algunas de las líneas transectas pueden superponerse. Las herramientas de estadísticas zonales en ArcGIS no pueden tratar con zonas superpuestas, por lo que cuando esto suceda, una de sus líneas de transecto tendrá prioridad sobre la otra. Esto puede o no ser un problema para lo que estás haciendo.

¡Buena suerte!


33
+1 ¡Es un buen comienzo para una gran contribución! Si observa de cerca la segunda figura, notará algunos transectos más cortos: son los que cruzan cerca de las curvas. Esto se debe a que su algoritmo para calcular los transectos supone incorrectamente que el desplazamiento de cada segmento será igual spc, pero las curvas acortan los desplazamientos. En su lugar, debe normalizar el vector de dirección transversal (dividir sus componentes por la longitud del vector) y luego multiplicarlo por el radio deseado del transecto.
whuber

Tienes toda la razón: ¡gracias por los comentarios @whuber! Con suerte arreglado ahora ...
JamesS

Querido James, lo intentaré muchas gracias. Esta solución se adapta perfectamente.
Kara

11

La elevación más alta dentro de los 10 km es el valor máximo de vecindad calculado con un radio circular de 10 km, por lo tanto, solo extraiga un perfil de esta cuadrícula máxima de vecindad a lo largo de la trayectoria.

Ejemplo

Aquí hay un DEM sombreado con una trayectoria (línea negra que va de abajo hacia arriba):

DEM

Esta imagen mide aproximadamente 17 por 10 kilómetros. Elegí un radio de solo 1 km en lugar de 10 km para ilustrar el método. Su amortiguador de 1 km se muestra delineado en amarillo.

El máximo de vecindario de un DEM siempre se verá un poco extraño, ya que tenderá a aumentar su valor en puntos donde un máximo (una cima de la colina, tal vez) cae un poco más allá de 10 km y otro máximo a una elevación diferente llega a menos de 10 km . En particular, las cimas de las colinas que dominan su entorno contribuirán a círculos perfectos de valores centrados en el punto de elevación máxima local:

Barrio max

Más oscuro es más alto en este mapa.

Aquí hay una gráfica de los perfiles del DEM original (azul) y el máximo del vecindario (Rojo):

Perfiles

Se calculó dividiendo la trayectoria en puntos regularmente espaciados a 0.1 km de distancia (comenzando en el extremo sur), extrayendo las elevaciones en esos puntos y haciendo un diagrama de dispersión unido de los triples resultantes (distancia desde el comienzo, elevación, elevación máxima). El espaciado de puntos de 0.1 km se eligió para que sea sustancialmente más pequeño que el radio del búfer, pero lo suficientemente grande como para hacer que el cálculo se realice rápidamente (fue instantáneo).


Sin embargo, eso no es del todo exacto, ¿verdad? En lugar de un búfer circular alrededor de cada punto, ¿no debería usarse una línea ortogonal de 20 km de longitud para muestrear el ráster subyacente? Al menos así es como interpretaría que se tiene en cuenta el requisito de Kara de "tener en cuenta" el valor más alto dentro de los 10 km a cada lado de la línea ".
Jake

44
@jake No diría "inexacto": simplemente ofrece una interpretación alternativa. "A cada lado de" es un término vago que podría usar una mejor calificación. Puedo proponer soluciones para interpretaciones como la suya; un método usa un máximo zonal. Sin embargo, es más complicado y mucho más lento en la ejecución. ¿Por qué no vemos primero lo que opina el OP de esta solución simple?
Whuber

Mala elección de palabras, no debería haber usado "precisa" - perdón por eso
Jake

1
Debido a que sabe cómo usar la herramienta Perfil, casi ha terminado. QGIS tiene interfaces para GRASS que incluyen operaciones de vecindario. Simplemente aplique la operación máxima de vecindario usando r.neighours y perfile su resultado.
whuber

1
@JamesS No desea hacer un desplazamiento paralelo, desea hacer que cada perfil cruzado sea perpendicular a la línea central. (El enfoque de desplazamiento paralelo se puede implementar exactamente como lo describo aquí mediante el uso de un vecindario largo y delgado apropiado para el cálculo máximo del vecindario). Estoy bastante seguro de que puede encontrar código en este sitio para construir conjuntos de segmentos de línea perpendiculares igualmente espaciados a lo largo de una polilínea; Esa es la parte difícil. Todo lo demás es solo cuestión de extraer los valores DEM a lo largo de esos segmentos y resumirlos.
whuber

6

Tuve el mismo problema y probé la solución de James S, pero no pude lograr que el GDAL funcionara con Fiona.

Luego descubrí el algoritmo SAGA "Cross Profiles" en QGIS 2.4, y obtuve exactamente el resultado que quería y supongo que también está buscando (ver más abajo).

ingrese la descripción de la imagen aquí


Hola, acabo de encontrar esta publicación de hace unos años. Estoy enfrentando el mismo problema que el iniciador de subprocesos y, al ser muy nuevo en (Q) GIS, estoy feliz de haber llegado tan lejos como en la imagen de arriba. Sin embargo, ¿cómo trabajo con los datos? La capa Perfiles cruzados muestra la elevación para cada punto muestreado, pero pediría ayuda en 1) encontrar la elevación máxima para cada línea cruzada 2) encontrar las coordenadas para la intersección con la ruta original 3) asociar las elevaciones máximas de 1 con las coordenadas de 2. ¿Alguien puede ayudar, por favor? Muchas gracias de antemano! Mal
Cpt Reynolds

6

Para cualquiera que esté interesado, aquí hay una versión modificada del código JamesS que crea líneas perpendiculares utilizando solo bibliotecas numpy y osgeo. ¡Gracias a JamesS, su respuesta me ayudó mucho hoy!

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

Gracias, ket. He intentado esto, pero desafortunadamente no me funciona por completo. Le he dado al script un archivo de forma con una única entidad de polilínea, pero mi salida es solo la tabla de atributos con muchos ceros para el valor "M100"; no se muestran características en el mapa. Ideas?
davehughes87

No importa: ahora me doy cuenta de que su script parece calcular líneas perpendiculares en los ENDS de cada segmento de la polilínea, no todos los medidores "spc". Esto significa que me estaba quedando sin polilínea para trabajar antes de que se alcanzara n_prof en el bucle y se produjeran valores "nan".
davehughes87
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.