Perfil de elevação 10 km de cada lado de uma linha


15

Como posso obter um perfil de elevação para uma faixa de terreno?

A elevação mais alta dentro de 10 km (em cada lado da linha definida) deve ser levada em consideração.

Espero que minha pergunta seja clara. Muito obrigado antecipadamente.


A linha que define seu perfil é uma linha reta simples ou consiste em vários segmentos com cantos?
Jake

A linha consiste em vários segmentos. Mas todos os segmentos são retos. :) Quero dizer, não há curvas.
Kara

Apenas ... como eles dizem ... spitballing ... mas você pode proteger a linha com um buffer de 10 km. depois selecione todos os recursos dentro do buffer ... depois selecione os valores mais altos?
6133 Ger

1
Você poderia fornecer uma imagem do que deseja obter?
Alexandre Neto

@ Alex: o resultado que eu quero é um gráfico de elevação regular. Mas com um buffer de 10 km, para que o valor mais alto de 10 km de cada lado do caminho selecionado seja mostrado no gráfico.
Kara

Respostas:


14

Seguindo os comentários, aqui está uma versão que funciona com segmentos de linha perpendiculares. Por favor, use com cuidado, pois eu não o testei completamente!

Esse método é muito mais desajeitado do que a resposta do @ whuber - em parte porque eu não sou um programador muito bom e em parte porque o processamento de vetores é um pouco complicado. Espero que pelo menos o inicie se você precisar de segmentos de linhas perpendiculares.

Você precisará ter os pacotes Shapely , Fiona e Numpy Python instalados (junto com suas dependências) para executar isso.

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

A imagem abaixo mostra um exemplo da saída do script. Você alimenta um shapefile que representa sua linha central e especifica o comprimento das linhas perpendiculares e seu espaçamento. A saída é um novo shapefile que contém as linhas vermelhas nesta imagem, cada uma com um atributo associado especificando sua distância desde o início do perfil.

Exemplo de saída de script

Como o @whuber disse nos comentários, quando você chega a esse estágio, o resto é bastante fácil. A imagem abaixo mostra outro exemplo com a saída adicionada ao ArcMap.

insira a descrição da imagem aqui

Use a ferramenta Feature to Raster para converter as linhas perpendiculares em uma varredura categórica. Defina a varredura VALUEcomo o Distcampo no shapefile de saída. Lembre-se também para definir a ferramenta Environmentspara que Extent, Cell sizee Snap rastersão os mesmos que para o seu DEM subjacente. Você deve terminar com uma representação rasterizada de suas linhas, algo como isto:

insira a descrição da imagem aqui

Por fim, converta essa varredura em uma grade inteira (usando a ferramenta Int ou a calculadora de varredura) e use-a como as zonas de entrada da ferramenta Zonal Statistics as Table . Você deve terminar com uma tabela de saída como esta:

insira a descrição da imagem aqui

O VALUEcampo nesta tabela fornece a distância desde o início da linha de perfil original. As outras colunas fornecem várias estatísticas (máximo, média etc.) para os valores em cada transação. Você pode usar esta tabela para plotar seu perfil de resumo.

NB: Um problema óbvio com esse método é que, se a sua linha original é muito ondulada, algumas das linhas de transecção podem se sobrepor. As ferramentas de estatística zonal no ArcGIS não podem lidar com zonas sobrepostas; portanto, quando isso acontece, uma das suas linhas de transecção terá precedência sobre a outra. Isso pode ou não ser um problema para o que você está fazendo.

Boa sorte!


3
+1 É um bom começo para uma grande contribuição! Se você olhar atentamente para a segunda figura, notará alguns trechos mais curtos: são eles que cruzam perto das curvas. Isso ocorre porque seu algoritmo para calcular as transecções incorretamente assume que o deslocamento de cada segmento será igual spc, mas as dobras diminuem os deslocamentos. Em vez disso, você deve normalizar o vetor de direção transversal (dividir seus componentes pelo comprimento do vetor) e depois multiplicá-lo pelo raio desejado do transecto.
whuber

Você está certo - obrigado pelo feedback @whuber! Esperemos que seja corrigido agora ... #
1111 JamesS

Caro James, vou tentar isso muito obrigado. Esta solução combina perfeitamente.
Kara

11

A elevação mais alta dentro de 10 km é o valor máximo da vizinhança calculado com um raio circular de 10 km; portanto, basta extrair um perfil da grade máxima dessa vizinhança ao longo da trajetória.

Exemplo

Aqui está um DEM sombreado com uma trajetória (linha preta correndo de baixo para cima):

DEM

Esta imagem é de aproximadamente 17 por 10 quilômetros. Eu escolhi um raio de apenas 1 km em vez de 10 km para ilustrar o método. Seu buffer de 1 km é mostrado delineado em amarelo.

O limite máximo de um DEM da vizinhança sempre parecerá um pouco estranho, pois tenderá a aumentar de valor em pontos onde um máximo (talvez um topo de uma colina) caia pouco além de 10 km e outro máximo em uma elevação diferente chegará a 10 km . Em particular, os morros que dominam seus arredores contribuirão com círculos perfeitos de valores centralizados no ponto de elevação máxima local:

Bairro máximo

Mais escuro é mais alto neste mapa.

Aqui está um gráfico dos perfis do DEM original (azul) e do bairro máximo (vermelho):

Perfis

Foi calculado dividindo a trajetória em pontos regularmente espaçados a 0,1 km de distância (começando na ponta sul), extraindo as elevações nesses pontos e fazendo um gráfico de dispersão unido dos triplos resultantes (distância do início, elevação, elevação máxima). O espaçamento de pontos de 0,1 km foi escolhido para ser substancialmente menor que o raio do buffer, mas grande o suficiente para acelerar o cálculo (era instantâneo).


Isso não é totalmente preciso, certo? Em vez de um buffer circular em torno de cada ponto, uma linha ortogonal de 20 km de comprimento não deve ser usada para amostrar a varredura subjacente? Pelo menos é assim que eu interpretaria o requisito de Kara de "o valor mais alto dentro de 10 km de cada lado da linha" ser levado em consideração.
Jake

4
@jake Eu não diria "impreciso": você apenas oferece uma interpretação alternativa. "Em cada lado de" é um termo vago que poderia usar uma melhor qualificação. Posso propor soluções para interpretações como a sua; um método usa um máximo de zona. No entanto, é mais complicado e muito mais lento na execução. Por que não vemos o que o OP pensa dessa solução simples?
whuber

Má escolha de palavras, eu não deveria ter usado "precisas" - sinto muito por isso
Jake

1
Como você sabe como usar a ferramenta Perfil, está quase pronto. O QGIS possui interfaces para o GRASS, que incluem operações de vizinhança. Basta aplicar a operação máxima da vizinhança usando r.neighbors e traçar o perfil do resultado.
whuber

1
@ JamesS Você não quer fazer uma mudança paralela, mas sim tornar cada perfil cruzado perpendicular à linha central. (A abordagem de deslocamento paralelo pode ser implementada exatamente como eu descrevo aqui, usando uma vizinhança longa e fina apropriada para o cálculo máximo da vizinhança.) Tenho certeza de que você pode encontrar código neste site para construir conjuntos de segmentos de linha perpendicular igualmente espaçados ao longo de uma polilinha; essa é a parte mais difícil. Tudo o resto é apenas uma questão de extrair os valores de DEM ao longo desses segmentos e resumi-los.
whuber

6

Eu tive o mesmo problema e tentei a solução de James S, mas não consegui que o GDAL trabalhasse com Fiona.

Então eu descobri o algoritmo SAGA "Cross Profiles" no QGIS 2.4 e obtive exatamente o resultado desejado e presumo que você também esteja procurando (veja abaixo).

insira a descrição da imagem aqui


Olá, acabei de encontrar este post de alguns anos atrás. Estou enfrentando o mesmo problema que o iniciador de threads e, sendo muito novo no (Q) GIS, estou feliz por ter chegado até a foto acima. Como trabalho com os dados? A camada de perfis cruzados mostra a elevação para cada ponto amostrado, mas eu gostaria de pedir ajuda em 1) encontrar a elevação máxima para cada linha cruzada 2) encontrar as coordenadas da interseção com o caminho original 3) associar as elevações máximas de 1 com as coordenadas de 2. Alguém pode ajudar, por favor? Muito obrigado antecipadamente! Mal
Cpt Reynolds

6

Para quem estiver interessado, aqui está uma versão modificada do código JamesS, criando linhas perpendiculares usando apenas as bibliotecas numpy e osgeo. Graças a JamesS, sua resposta me ajudou muito hoje!

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

Obrigado ket - Eu tentei isso, mas infelizmente não está funcionando totalmente para mim. Dei ao script um shapefile com um único recurso de polilinha, mas minha saída é apenas a tabela de atributos com muitos zeros para o valor "M100" - nenhum recurso é exibido no mapa. Ideias?
davehughes87

Não importa - percebi agora que seu script parece calcular linhas perpendiculares nas extremidades de cada segmento da polilinha, nem todos os medidores "spc". Isso significa que eu estava ficando sem polilinha para trabalhar antes que o n_prof fosse alcançado no loop e os valores "nan" estivessem sendo produzidos.
davehughes87
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.