Як підключити дорожню мережу до шестикутної сітки в QGIS?


13

Я намагаюся використовувати QGIS 2.14, щоб підключити дорожню мережу до шестикутної сітки, але я отримую дивні артефакти.

Я створив шестигранну сітку з MMQGIS , осередки розміром приблизно 20 х 23 м. Я забудував дорожню мережу на 1 м і ущільнив її, щоб на кожному метрі був вузол. Нижче ви бачите, чого я намагаюся досягти. Як бачите, я можу змусити його працювати в деяких випадках: -

  • синій колір - ущільнена дорога (буферна лінія)
  • червоний - це «шестигранна» версія - ось що я хочу знайти
  • сірий - це шестигранна сітка

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

Потім я використав нову функцію геометрії Snap, щоб прив’язати вузли до найближчого шестикутника. Результати є багатообіцяючими, але, мабуть, є деякі крайні випадки, коли лінія розширюється, щоб заповнити шестикутник (або його частину): -

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

Причина буфера полягає в тому, що геометричні елементи Snap не дозволяють вам прив’язатись до шару, геометрія якого відрізняється. Наприклад, не можна прив'язувати вузли на шарі LINE до точок на шарі POINT). Здається, це найщасливіше прив’язати ПОЛІГОН до ПОЛІГОН.

Я підозрюю, що дороги розширюються, коли одна сторона захищеної лінії дороги стрибає на одну сторону шестигранної комірки, а інша сторона переходить на іншу сторону шестигранної комірки. На моєму прикладі дороги, які під гострим кутом перетинають захід-схід, здаються найгіршими.

Те, що я намагався, без успіху: -

  • буферизація дорожньої мережі невеликою кількістю, тому вона залишається багатокутником, але дуже тонка.
  • ущільнення шістнадцяткових клітин (тому уздовж країв є вузли, а не лише на кутах)
  • варіювання максимальної відстані оснащення (це має найбільший ефект, але я не можу знайти ідеальне значення)
  • використовуючи шари LINE, а не ПОЛІГОНИ

Я вважаю, що якщо я перейду до використання лише шарів LINE, він працює деякий час, а потім виходить з ладу. Здається, це врятує її роботу - деякі рядки були частково оброблені.

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

Хтось знає про будь-який інший спосіб прив’язати точки на лінії до найближчої точки на іншому шарі лінії / полігону, в ідеалі без необхідності використовувати postgres / postgis (хоча рішення з postgis також вітається)?

EDIT

Для всіх, хто хотів би поїхати, я поставив тут проект для запуску QGIS на Dropbox . Сюди входять шари шестигранної сітки та ущільнені лінії. (Дорожня мережа створена з ОСМ, тому її можна завантажити за допомогою QuickOSM, наприклад, якщо вам потрібно отримати оригінал для несанкціонованого руху доріг).

Зауважте, що це в OSGB (epsg: 27700), який є локалізованим UTM для Великобританії, з одиницями в метрах.


3
Не могли б ви поділитися зразком набору даних? Я хотів би спробувати, але не хочу переглядати процес створення зразкових даних з нуля.
Герман Каррільо

@ GermánCarrillo - дякую. До питання я додав посилання на зразок проекту.
Стівен Кей

Відповіді:


14

Моє рішення передбачає сценарій PyQGIS, який є більш швидким та ефективним, ніж робочий процес, що включає оснащення (я також спробував це). Використовуючи свій алгоритм, я отримав такі результати:

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

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

Ви можете запустити такі фрагменти коду послідовно зсередини QGIS (у консолі QGIS Python). В кінці ви отримуєте шар пам'яті з оснащеними маршрутами, завантаженими в QGIS.

Єдина необхідна умова - створити багатосторонній Shapefile дороги (використання Processing->Singleparts to multipart, я використовував поле fictitiuosяк Unique ID fieldпараметр). Це дасть нам roads_multipart.shpфайл з однією функцією.

Ось пояснений алгоритм:

  1. Отримайте найближчі шестикутні сторони, де перетинаються маршрути. Для кожного шестикутника ми створюємо 6 трикутників між кожною парою сусідніх вершин і відповідним центроїдом. Якщо будь-яка дорога перетинає трикутник, відрізок, що ділиться шестикутником, і трикутник додається до остаточного відрізаного маршруту. Це найважча частина всього алгоритму. На моїй машині працює 35 секунд. У перших двох рядках є 2 контури Shapefile, ви повинні налаштувати їх так, щоб вони відповідали вашим власним шляху до файлів.

    hexgrid = QgsVectorLayer("/docs/borrar/hex_grid_question/layers/normal-hexgrid.shp", "hexgrid", "ogr")
    roads = QgsVectorLayer("/docs/borrar/hex_grid_question/layers/roads_multipart.shp", "roads", "ogr")  # Must be multipart!
    
    roadFeat = roads.getFeatures().next() # We just have 1 geometry
    road = roadFeat.geometry() 
    indicesHexSides = ((0,1), (1,2), (2,3), (3,4), (4,5), (5,0))
    
    epsilon = 0.01
    # Function to compare whether 2 segments are equal (even if inverted)
    def isSegmentAlreadySaved(v1, v2):
        for segment in listSegments:        
            p1 = QgsPoint(segment[0][0], segment[0][1])
            p2 = QgsPoint(segment[1][0], segment[1][1])
            if v1.compare(p1, epsilon) and v2.compare(p2, epsilon) \
                or v1.compare(p2, epsilon) and v2.compare(p1, epsilon):
                return True
        return False
    
    # Let's find the nearest sides of hexagons where routes cross
    listSegments = []
    for hexFeat in hexgrid.getFeatures():
        hex = hexFeat.geometry()
        if hex.intersects( road ):
            for side in indicesHexSides:
                triangle = QgsGeometry.fromPolyline([hex.centroid().asPoint(), hex.vertexAt(side[0]), hex.vertexAt(side[1])])
                if triangle.intersects( road ):
                    # Only append new lines, we don't want duplicates!!!
                    if not isSegmentAlreadySaved(hex.vertexAt(side[0]), hex.vertexAt(side[1])): 
                        listSegments.append( [[hex.vertexAt(side[0]).x(), hex.vertexAt(side[0]).y()], [hex.vertexAt(side[1]).x(),hex.vertexAt(side[1]).y()]] )  
  2. Позбудьтесь відключених (або "відкритих") сегментів, використовуючи списки, кортежі та словники Python . На цьому етапі залишилися деякі від’єднані сегменти, тобто сегменти, у яких одна вершина від’єднана, а інша пов'язана щонайменше з двома сегментами (див. Червоні сегменти на наступному малюнку). Нам потрібно позбутися їх.

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

    # Let's remove disconnected/open segments
    lstVertices = [tuple(point) for segment in listSegments for point in segment]
    dictConnectionsPerVertex = dict((tuple(x),lstVertices.count(x)-1) for x in set(lstVertices))
    
    # A vertex is not connected and the other one is connected to 2 segments
    def segmentIsOpen(segment):
        return dictConnectionsPerVertex[tuple(segment[0])] == 0 and dictConnectionsPerVertex[tuple(segment[1])] >= 2 \
            or dictConnectionsPerVertex[tuple(segment[1])] == 0 and dictConnectionsPerVertex[tuple(segment[0])] >= 2
    
    # Remove open segments
    segmentsToDelete = [segment for segment in listSegments if segmentIsOpen(segment)]        
    for toBeDeleted in segmentsToDelete:
        listSegments.remove( toBeDeleted )
  3. Тепер ми можемо створити векторний шар зі списку координат і завантажити його на карту QGIS :

    # Create a memory layer and load it to QGIS map canvas
    vl = QgsVectorLayer("LineString", "Snapped Routes", "memory")
    pr = vl.dataProvider()
    features = []
    for segment in listSegments:
        fet = QgsFeature()
        fet.setGeometry( QgsGeometry.fromPolyline( [QgsPoint(segment[0][0], segment[0][1]), QgsPoint(segment[1][0], segment[1][1])] ) )
        features.append(fet)
    
    pr.addFeatures( features )
    vl.updateExtents()
    QgsMapLayerRegistry.instance().addMapLayer(vl)

Інша частина результату:

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

Якщо вам потрібні атрибути в оснащених маршрутах, ми можемо використовувати просторовий індекс для швидкої оцінки перехресть (наприклад, на /gis//a/130440/4972 ), але це вже інша історія.

Сподіваюся, це допомагає!


1
дякую, що прекрасно працює! Були проблеми з вставкою його в консоль python ... Я зберег її як .py файл у редакторі qgis python, і він непогано пробіг звідти. Крок з багаточастинкою видаляє атрибути, але буфер / просторове з'єднання це виправить!
Стівен Кей

1
Чудово! Радий, що нарешті вирішив проблему, з якою ви стикалися. Мені цікаво дізнатися, у чому випадку використання ви маєте справу. Як ви думаєте, ми могли б використати це, щоб стати плагіном QGIS або, можливо, сценарієм, який включений до сценаріїв обробки?
Герман Каррілло

1
У випадку використання я мав на увазі карти громадського транспорту, такі як карта трубки, де вам потрібно прив’язати лінії до теселізованої сітки або до обмеженого набору кутів. Це можна зробити вручну шляхом оцифрування, але мені було цікаво подивитися, чи можна його автоматизувати. Я використовував шестигранники, оскільки їх було легко створити, візуально цікаві та мали кути, які не були прямими. Я думаю, що це варто переглянути докладніше, особливо якщо це можна було б узагальнити для роботи з іншими теселяціями ...
Стівен Кей

1
Ідея сценарію буде працювати над сітками трикутників, квадратів, п’ятикутників, шестикутників тощо.
Герман Каррілло

6

Я робив це в ArcGIS, безумовно, може бути реалізований за допомогою QGIS або просто python з пакетом, здатним читати геометрії. Переконайтесь, що дороги представляють мережу, тобто перетинаються лише одна з кінців. Ви маєте справу з ОСМ, я вважаю, що це так.

  • Перетворіть багатокутники наближення до ліній і плануйте їх, щоб вони також стали геометричною мережею.
  • Розмістіть точки на їх кінцях - пункти Вороного: введіть тут опис зображення
  • Розміщуйте точки на дорозі через регулярний інтервал 5 м, переконайтесь, що мережеві дороги мають гарну унікальну назву:

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

  • Для кожного дорожнього пункту знайдіть координати найближчої точки Вороного: введіть тут опис зображення
  • Створіть "Дороги", з'єднавши найближчі точки в тому ж порядку: введіть тут опис зображення

Якщо ви не хочете цього бачити: введіть тут опис зображення

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


це чудово, дякую! Ви згадуєте про використання рядків Voronoi, не надто знайомих з цим (Воронуа з точок, я можу зрозуміти). Ви маєте на увазі, що кожна лінія оточена багатокутником усіх точок, найближчих до цієї лінії? (Мені невідомий спосіб зробити це в QGIS). Або ви маєте на увазі прикордонні лінії від звичайної вороної сітки на основі точок?
Стівен Кей

Прикордонні лінії полігонів близькості. До речі, я зупинився занадто рано. Для виконання завдання достатньо розділити 1-й результат у вершині, додати точку посередині та повторити процес
FelixIP

4

Я розумію, що ви просите метод QGIS, але попросіть мене отримати аркпійну відповідь:

roads = 'clipped roads' # roads layer
hexgrid = 'normal-hexgrid' # hex grid layer
sr = arcpy.Describe('roads').spatialReference # spatial reference
outlines = [] # final output lines
points = [] # participating grid vertices
vert_dict = {} # vertex dictionary
hex_dict = {} # grid dictionary
with arcpy.da.SearchCursor(roads,["SHAPE@","OID@"], spatial_reference=sr) as r_cursor: # loop through roads
    for r_row in r_cursor:
        with arcpy.da.SearchCursor(hexgrid,["SHAPE@","OID@"], spatial_reference=sr) as h_cursor: # loop through hex grid
            for h_row in h_cursor:
                if not r_row[0].disjoint(h_row[0]): # check if the shapes overlap
                    hex_verts = []
                    for part in h_row[0]:
                        for pnt in part:
                            hex_verts.append(pnt) # add grid vertices to list
                    int_pts = r_row[0].intersect(h_row[0],1) # find all intersection points between road and grid
                    hex_bnd = h_row[0].boundary() # convert grid to line
                    hex_dict[h_row[1]] = hex_bnd # add grid geometry to dictionary
                    for int_pt in int_pts: # loop through intersection points
                        near_dist = 1000 # arbitrary large number
                        int_pt = arcpy.PointGeometry(int_pt,sr)
                        for hex_vert in hex_verts: # loop through hex vertices
                            if int_pt.distanceTo(hex_vert) < near_dist: # find shortest distance between intersection point and grid vertex
                                near_vert = hex_vert # remember geometry
                                near_dist = int_pt.distanceTo(hex_vert) # remember distance
                        vert_dict.setdefault(h_row[1],[]).append(arcpy.PointGeometry(near_vert,sr)) # store geometry in dictionary
                        points.append(arcpy.PointGeometry(near_vert,sr)) # add to points list
for k,v in vert_dict.iteritems(): # loop through participating vertices
    if len(v) < 2: # skip if there was only one vertex
        continue
    hex = hex_dict[k] # get hex grid geometry
    best_path = hex # longest line possible is hex grid boundary
    for part in hex:
        for int_vert in v: # loop through participating vertices
            for i,pnt in enumerate(part): # loop through hex grid vertices
                if pnt.equals(int_vert): # find vertex index on hex grid corresponding to current point
                    start_i = i
                    if start_i == 6:
                        start_i = 0
                    for dir in [[0,6,1],[5,-1,-1]]: # going to loop once clockwise, once counter-clockwise
                        past_pts = 0 # keep track of number of passed participating vertices
                        cur_line_arr = arcpy.Array() # polyline coordinate holder
                        cur_line_arr.add(part[start_i]) # add starting vertex to growing polyline
                        for j in range(dir[0],dir[1],dir[2]): # loop through hex grid vertices
                            if past_pts < len(v): # only make polyline until all participating vertices have been visited
                                if dir[2] == 1: # hex grid vertex index bookkeeping
                                    if start_i + j < 6:
                                        index = start_i + j
                                    else:
                                        index = (start_i - 6) + j
                                else:
                                    index = j - (5 - start_i)
                                    if index < 0:
                                        index += 6
                                cur_line_arr.add(part[index]) # add current vertex to growing polyline
                                for cur_pnt in v:
                                    if part[index].equals(cur_pnt): # check if the current vertex is a participating vertex
                                        past_pts += 1 # add to counter
                        if cur_line_arr.count > 1:
                            cur_line = arcpy.Polyline(cur_line_arr,sr)
                            if cur_line.length < best_path.length: # see if current polyline is shorter than any previous candidate
                                best_path = cur_line # if so, store polyline
    outlines.append(best_path) # add best polyline to list
arcpy.CopyFeatures_management(outlines, r'in_memory\outlines') # write list
arcpy.CopyFeatures_management(points, r'in_memory\mypoints') # write points, if you want

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

Примітки:

  • Цей скрипт містить багато циклів у циклах та вкладений курсор. Однозначно є можливість для оптимізації. Я пробіг ваші набори даних за пару хвилин, але більше функцій ускладнить проблему.

Дякую за це, дуже вдячний. Це точно показує ефект, який я візуалізував. Широкі коментарі означають, що я можу отримати суть того, що ви робите, навіть якщо я не можу запустити код. Хоча це arcpy, я впевнений, що це можна зробити в pyqgis. Ідеї ​​алгоритму тут цікаві (особливо дивлячись як за годинниковою стрілкою, так і проти годинникової стрілки навколо кожної шестигранниці, і вибираючи найкоротший шлях раунду)
Стівен Кей

2

Якби ви розділили лінію дороги на відрізки, де кожен сегмент повністю містився шестикутником, ваше рішення щодо того, який сегмент лінії шестикутника використовувати, було б, чи відстань від центральної частини сегмента дороги до середини кожного шестикутника менше половини половини діаметр шестикутника (або менший за радіус кола, що вписується всередину шестикутника).

Таким чином, якби ви (один відрізок одночасно) вибирали сегменти лінії шестикутника (де кожен сегмент є стороною шестикутника), які знаходяться в межах відстані від радіуса шестикутника, ви могли б скопіювати ці геометрії ліній і об'єднати їх на незалежно від того, який унікальний ідентифікатор ви використовуєте для набору даних про дорогу.

Якщо у вас є проблеми з об'єднанням унікального ідентифікатора, ви можете застосувати буфер і вибрати за місцеположенням лише ті сегменти, щоб застосувати атрибути вашого набору даних про дороги; таким чином, вам не доведеться турбуватися про створення помилкових збігів із занадто великим буфером.

Проблема з інструментом "оснащення" полягає в тому, що він хапає точки без розбору; важко знайти ідеальну толерантність до використання. За допомогою цієї методології ви б правильно визначити, які сегменти лінії шестикутника використовувати, а потім замінити геометрію ваших дорожніх даних (або вставити геометрії в інший набір даних).

Крім того, якщо у вас все ще виникають проблеми з відрізками рядків, які стрибають з однієї сторони шестикутника на іншу, ви можете розділити лінію на сегменти за вершинами, обчислити довжину кожного рядка, а потім видалити будь-які сегменти рядка, більші ніж середня довжина однієї сторони шестикутника.


1

Фіксатор геометрії в qgis 3.0 був перероблений і тепер дозволяє перемикатися між різними типами геометрії. Він також має багато виправлень. Ви можете спробувати версію "щоденний знімок", щоб отримати доступ до вдосконаленої оснастки до офіційного виходу 3.0.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.