Профиль высоты 10 км с каждой стороны линии


15

Как я могу получить профиль высоты для полосы местности?

Наибольшая высота в пределах 10 км (на каждой стороне определенной линии) должна быть принята во внимание.

Я надеюсь, что мой вопрос ясен. Заранее большое спасибо.


Является ли линия, определяющая ваш профиль, простой прямой линией или она состоит из нескольких сегментов с углами?
Джейк

Линия состоит из нескольких сегментов. Но все отрезки прямые. :) Я имею в виду нет никаких кривых.
Кара

Просто ... как говорится .. спитболлинг ... но не могли бы вы буферизовать линию буфером 10 км. затем выберите все объекты в буфере ... затем выберите самые высокие значения?
Ger

1
Не могли бы вы предоставить изображение того, что вы хотите достичь?
Александр Нето

@ Алекс: результат, который я хочу, это обычный график возвышения. Но с 10-километровым буфером для того, чтобы на графике было показано максимальное значение 10 км с каждой стороны выбранного пути.
Кара

Ответы:


14

Исходя из комментариев, вот версия, которая работает с перпендикулярными отрезками. Пожалуйста, используйте с осторожностью, поскольку я не проверил это полностью!

Этот метод гораздо более неуклюжий, чем ответ @ whuber - отчасти потому, что я не очень хороший программист, а отчасти потому, что обработка векторов - это нечто вроде ошибки. Я надеюсь, что по крайней мере вы начнете, если вам нужны перпендикулярные отрезки.

Для этого вам нужно установить пакеты Shapely , Fiona и Numpy Python (вместе с их зависимостями).

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

На рисунке ниже показан пример вывода из скрипта. Вы вводите шейп-файл, представляющий вашу центральную линию, и указываете длину перпендикулярных линий и их расстояние. На выходе получается новый шейп-файл, содержащий красные линии на этом изображении, с каждым из которых связан атрибут, определяющий его расстояние от начала профиля.

Пример вывода скрипта

Как сказал @whuber в комментариях, как только вы дойдете до этого этапа, все остальное будет довольно просто. На рисунке ниже показан другой пример с выходом, добавленным в ArcMap.

введите описание изображения здесь

Используйте инструмент Feature to Raster для преобразования перпендикулярных линий в категориальный растр. Установите растр VALUEв качестве Distполя в выходном шейп-файле. Кроме того, помните , чтобы установить инструмент Environmentsтак , что Extent, Cell sizeи Snap rasterтакие же , как для базового DEM. Вы должны получить растровое представление ваших строк, что-то вроде этого:

введите описание изображения здесь

Наконец, преобразуйте этот растр в целочисленную сетку (используя инструмент Int или растровый калькулятор) и используйте его в качестве входных зон для Zonal Statistics как инструмента Table . Вы должны получить выходную таблицу, подобную этой:

введите описание изображения здесь

VALUEПоле в этой таблице дает расстояние от начала исходной линии профиля. Другие столбцы дают различную статистику (максимум, среднее и т. Д.) Для значений в каждом трансекте. Вы можете использовать эту таблицу для построения вашего итогового профиля.

NB. Одна очевидная проблема этого метода заключается в том, что если исходная линия очень волнистая, некоторые из линий разреза могут перекрываться. Инструменты зональной статистики в ArcGIS не могут работать с перекрывающимися зонами, поэтому, когда это происходит, одна из ваших линий разреза будет иметь приоритет над другой. Это может или не может быть проблемой для того, что вы делаете.

Удачи!


3
+1 Это хорошее начало для отличного вклада! Если вы внимательно посмотрите на вторую фигуру, вы заметите несколько более коротких разрезов: они пересекают изгибы. Это потому, что ваш алгоритм вычисления трансектов неверно предполагает, что смещение каждого сегмента будет равным spc, но изгибы сокращают смещения. Вместо этого вы должны нормализовать вектор поперечного направления (разделить его компоненты на длину вектора), а затем умножить его на желаемый радиус разреза.
whuber

Вы совершенно правы - спасибо за отзыв @whuber! Надеюсь, исправлено сейчас ...
JamesS

Дорогой Джеймс, я попробую это, большое спасибо. Это решение идеально подходит.
Кара

11

Наибольшее превышение в пределах 10 км является максимальным значением окрестности, вычисленным с круговым радиусом 10 км, поэтому просто извлеките профиль этой сетки максимума окрестности вдоль траектории.

пример

Вот высотная матрица с траекторией (черная линия, идущая снизу вверх):

DEM

Это изображение примерно 17 на 10 километров. Я выбрал радиус всего 1 км, а не 10 км, чтобы проиллюстрировать метод. Его 1-километровый буфер показан обведенным желтым цветом.

Максимум окрестности матрицы высот всегда будет выглядеть немного странно, потому что он будет стремиться подскочить в значениях в точках, где один максимум (возможно, вершина холма) падает чуть более 10 км, а другой максимум на другой высоте находится в пределах 10 км. , В частности, вершины холмов, которые доминируют в их окружении, будут создавать совершенные круги ценностей, центрированные в точке локального максимального подъема:

Окрестности макс

Темнее выше на этой карте.

Вот график профилей исходной матрицы высот (синий) и максимума окрестности (красный):

профили

Он был рассчитан путем деления траектории на равномерно расположенные точки на расстоянии 0,1 км (начиная от южной оконечности), извлечения возвышений в этих точках и составления объединенной диаграммы рассеяния из получаемых троек (расстояние от начала, превышение, максимальное превышение). Расстояние между точками в 0,1 км было выбрано существенно меньшим, чем радиус буфера, но достаточно большим, чтобы вычисление прошло быстро (оно было мгновенным).


Это не совсем точно, правда? Вместо кругового буфера вокруг каждой точки, не следует ли использовать ортогональную линию длиной 20 км для выборки базового растра? По крайней мере, именно так я бы истолковал требование Кара о «максимальном значении в пределах 10 км с каждой стороны линии», принимая во внимание.
Джейк

4
@ Джейк Я бы не сказал «неточно»: вы просто предлагаете альтернативную интерпретацию. «На каждой стороне» - это расплывчатый термин, который может использовать более высокую квалификацию. Я могу предложить решения для интерпретаций, подобных вашей; один метод использует зональный максимум. Тем не менее, это сложнее и намного медленнее в исполнении. Почему бы нам сначала не увидеть, что ОП думает об этом простом решении?
whuber

Плохой выбор слов, я не должен был использовать «точный» - извините за это
Джейк

1
Поскольку вы знаете, как использовать инструмент «Профиль», вы почти закончили. QGIS имеет интерфейсы для GRASS, которые включают операции с соседями. Просто примените операцию окрестности с максимальным использованием r.neighbors и профилируйте ее результат.
whuber

1
@JamesS Вы не хотите делать параллельное смещение, вы хотите, чтобы каждый поперечный профиль был перпендикулярен центральной линии. (Подход с параллельным сдвигом может быть реализован в точности так, как я описываю здесь, используя подходящие длинные и узкие окрестности для вычисления максимума окрестностей.) Я почти уверен, что на этом сайте вы можете найти код для построения наборов равноправно расположенных отрезков перпендикулярной линии вдоль полилинии; это сложная часть. Все остальное - всего лишь вопрос извлечения значений матрицы высот вдоль этих сегментов и их суммирования.
whuber

6

У меня была та же проблема, и я попробовал решение Джеймса С., но не смог заставить GDAL работать с Фионой.

Затем я обнаружил алгоритм SAGA "Cross Profiles" в QGIS 2.4 и получил именно тот результат, который мне нужен, и который, я полагаю, вы тоже ищете (см. Ниже).

введите описание изображения здесь


Привет, я только что наткнулся на этот пост несколько лет назад. Я сталкиваюсь с той же проблемой, что и начальный поток, и, будучи новичком в (Г) ГИС, я в некотором роде счастлив, что достиг уровня вышеизложенного. Как мне работать с данными? Слой Cross Profiles показывает высоту для каждой выбранной точки, но я хотел бы попросить о помощи: 1) найти максимальную высоту для каждой перекрестной линии 2) найти координаты для пересечения с исходной траекторией 3) связать максимальные высоты от 1 с координатами от 2. Может кто-нибудь помочь, пожалуйста? Большое спасибо заранее! Mal
Cpt Рейнольдс

6

Для всех, кто заинтересован, вот модифицированная версия кода JamesS, создающая перпендикулярные линии, используя только библиотеки numpy и osgeo. Благодаря JamesS, его ответ мне сегодня очень помог!

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

Спасибо, кет - я пробовал это, но, к сожалению, это не работает для меня полностью. Я дал сценарию шейп-файл с одной полилинейной характеристикой, но мой вывод - это просто таблица атрибутов с множеством нулей для значения «M100» - на карте нет объектов. Идеи?
davehughes87

Не берите в голову - теперь вы поняли, что ваш скрипт, кажется, рассчитывает перпендикулярные линии в конце каждого сегмента ломаной линии, а не в каждом метре «spc». Это означает, что у меня заканчивалась полилиния для работы до того, как n_prof был достигнут в цикле и были получены значения "nan".
davehughes87
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.