QGIS에서 도로 네트워크를 6 각형 그리드에 스냅하는 방법은 무엇입니까?


13

QGIS 2.14를 사용하여 도로 네트워크를 6 각형 그리드에 스냅하려고하는데 이상한 유물이 나타납니다.

MMQGIS 로 육각 격자를 만들었습니다 . 셀은 약 20 x 23m입니다. 도로 네트워크를 1m 버퍼링하고 밀도를 높였 으므로 몇 미터마다 노드가 있습니다. 아래에서 달성하려는 것을 볼 수 있습니다. 보시다시피, 경우에 따라 작동하도록 할 수 있습니다.

  • 파란색은 밀도가 높은 도로입니다 (버퍼링 된 선).
  • 빨간색은 '육각 화'버전입니다.
  • 회색은 육각 격자입니다

여기에 이미지 설명을 입력하십시오

그런 다음 새로운 스냅 지오메트리 기능을 사용 하여 가장 가까운 육각형 코너에 노드를 스냅합니다. 결과는 유망하지만 육각형 (또는 그 일부)을 채우기 위해 선이 확장되는 경우가 있습니다.

여기에 이미지 설명을 입력하십시오

버퍼의 이유는 스냅 지오메트리 로 지오메트리가 다른 레이어에 스냅 할 수 없기 때문입니다. 예를 들어, LINE 레이어의 노드를 POINT 레이어의 포인트로 스냅 할 수 없습니다. POLYGON을 POLYGON에 맞추는 것이 가장 행복한 것 같습니다.

완충 도로 노선의 한 쪽이 육각 셀의 한쪽으로 점프하고 다른 쪽이 육각 셀의 다른쪽으로 뛸 때 도로가 확장되는 것으로 의심됩니다. 나의 예에서, 예각으로 서동을 가로 지르는 도로가 최악 인 것 같습니다.

성공하지 않고 시도한 것 :-

  • 도로 네트워크를 소량 버퍼링하므로 다각형으로 유지되지만 매우 얇습니다.
  • 육각 셀 조밀화 (모서리뿐만 아니라 가장자리를 따라 노드가 있음)
  • 최대 스냅 거리 변경 (이것은 가장 큰 효과가 있지만 이상적인 값을 찾을 수없는 것 같습니다)
  • 폴리곤이 아닌 LINE 레이어 사용

LINE 레이어 만 사용하도록 변경하면 잠시 동안 작동 한 다음 충돌이 발생합니다. 일부 줄은 부분적으로 처리되었습니다.

여기에 이미지 설명을 입력하십시오

누구나 postgres / postgis를 사용하지 않고도 이상적으로는 선상의 점을 다른 선 / 다각형 레이어의 가장 가까운 점에 맞추는 다른 방법을 알고 있습니까?

편집하다

가고 싶은 사람이라면 Dropbox에 초보자 QGIS 프로젝트를 넣었습니다 . Hex Grid 및 Densified Lines 레이어가 포함됩니다. (도로 네트워크는 OSM에서 제공되므로 QuickOSM을 사용하여 다운로드 할 수 있습니다 (예 : 도로를 밀집 해제하기 위해 원본을 가져와야하는 경우)).

영국의 현지화 된 UTM 인 미터 단위의 OSGB (epsg : 27700)입니다.


3
샘플 데이터 세트를 공유 할 수 있습니까? 시도해보고 싶지만 샘플 데이터를 처음부터 만드는 과정을 거치고 싶지 않습니다.
— Germán Carrillo

@ GermánCarrillo-감사합니다. 질문에 샘플 프로젝트에 대한 링크를 추가했습니다.
— Steven Kay

답변:


14

내 솔루션에는 스냅 작업이 포함 된 워크 플로보다 빠르고 효과적인 PyQGIS 스크립트가 포함되어 있습니다 (시도했습니다). 내 알고리즘을 사용하여 다음과 같은 결과를 얻었습니다.

여기에 이미지 설명을 입력하십시오

여기에 이미지 설명을 입력하십시오

QGIS 내에서 (QGIS Python 콘솔에서) 다음 코드 스 니펫을 순서대로 실행할 수 있습니다. 결국 QGIS에 스냅 된 라우트가로드 된 메모리 레이어가 생깁니다.

유일한 전제 조건은 multipart road Shapefile을 작성하는 것입니다 (use 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
고마워요, 완벽하게 작동합니다! 파이썬 콘솔에 붙여 넣는 데 문제가 있습니다 ... qgis 파이썬 편집기에서 .py 파일로 저장했는데 거기에서 정상적으로 실행되었습니다. multipart 단계는 속성을 제거하지만 버퍼 / 공간 조인이이를 해결합니다!
— Steven Kay

1
큰! 마침내 당신이 직면 한 문제를 해결하게되어 기쁩니다. 처리중인 사용 사례가 무엇인지 알고 싶습니다. 이것을 활용하여 QGIS 플러그인 또는 스크립트 처리에 포함 된 스크립트가 될 수 있다고 생각하십니까?
— Germán Carrillo

1
내가 염두에 둔 유스 케이스는 튜브 맵과 같은 대중 교통 맵이었습니다. 여기서 선을 테셀 레이트 그리드 또는 제한된 각도 로 스냅해야합니다 . 이것은 디지털화를 통해 수동으로 수행 할 수 있지만 자동화 할 수 있는지 알고 싶었습니다. 육각형은 생성하기 쉽고 시각적으로 재미 있고 직각이 아닌 각도를 가졌기 때문에 육각형을 사용했습니다. 특히 다른 테셀레이션과 함께 작동하도록 일반화 할 수있는 경우에는 자세히 살펴볼 가치가 있다고 생각합니다.
— Steven Kay

1
스크립트 뒤에있는 아이디어는 삼각형, 사각형, 오각형, 육각형 등의 그리드에서 작동합니다.
— Germán Carrillo

6

ArcGIS에서 해냈습니다 .QGIS를 사용하여 구현하거나 단순히 기하학을 읽을 수있는 패키지로 파이썬을 구현할 수 있습니다. 도로가 네트워크를 나타내는 지 확인하십시오. 즉, 끝에서만 서로 교차합니다. 당신은 OSM을 다루고 있습니다.

  • 근접 다각형을 선으로 변환하고 평면화하면 기하학적 네트워크도됩니다.
  • 보로 노이 포인트 : 여기에 이미지 설명을 입력하십시오
  • 정기적으로 5m 간격으로 도로에 지점을두고 네트워크 도로의 고유 한 이름을 확인하십시오.

여기에 이미지 설명을 입력하십시오 여기에 이미지 설명을 입력하십시오

  • 모든 Road Point에 대해 가장 가까운 Voronoi Point의 좌표를 찾으십시오. 여기에 이미지 설명을 입력하십시오
  • 가장 가까운 점을 같은 순서로 연결하여 "도로"를 만듭니다. 여기에 이미지 설명을 입력하십시오

이것을보고 싶지 않다면 : 여기에 이미지 설명을 입력하십시오

Voronoi Lines에서 연결점을 사용하지 마십시오. 나는 그것이 더 나빠질 것을 두려워합니다. 따라서 귀하의 유일한 옵션은 Voronoi 라인에서 네트워크를 생성하고 도로 끝 지점 사이의 경로를 찾는 것입니다.


감사합니다. 감사합니다! 당신은 voronoi 라인을 사용하는 것에 대해 언급하지만 너무 익숙하지는 않습니다 (점에서 Voronois, 이해할 수 있습니다). 각 선이 해당 선에 가장 가까운 모든 점의 다각형으로 둘러싸여 있습니까? (QGIS에서 그렇게하는 방법을 모르겠습니다). 아니면 점을 기준으로 일반 보로 노이 메시의 경계선을 의미합니까?
— Steven Kay

근접 다각형의 경계선. Btw 나는 너무 일찍 멈췄다. 작업을 완료하려면 정점에서 첫 번째 결과를 분할하고 중간에 점을 추가하고 프로세스를 반복하는 것으로
— 충분

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

여기에 이미지 설명을 입력하십시오

노트:

  • 이 스크립트에는 루프 내의 많은 루프와 중첩 된 커서가 포함되어 있습니다. 최적화의 여지가 분명히 있습니다. 몇 분 안에 데이터 세트를 살펴 보았지만 더 많은 기능으로 인해 문제가 복잡해질 것입니다.

이것에 감사드립니다. 이것은 내가 시각화 한 효과를 정확하게 보여줍니다 . 풍부한 의견은 코드를 실행할 수 없어도 내가하는 일의 요지를 얻을 수 있음을 의미합니다. 그것은 비싸지 만, 이것이 pyqgis에서 가능할 것이라고 확신합니다. 여기에있는 알고리즘 아이디어는 흥미 롭습니다 (특히 각 16 진수로 시계 방향과 반 시계 방향으로보고 가장 짧은 방법 선택)
— Steven Kay

2

각 선분이 육각형에 완전히 포함 된 선분으로 도로 선을 분할하는 경우 사용할 육각 선 선분에 대한 결정은 분할 도로 선분의 중심에서 각 육각형 측의 중간 점까지의 거리가 절반 미만인지 여부입니다. 육각형의 직경 (또는 육각형 안에 맞는 원의 반경보다 작음).

따라서 (한 번에 하나의 세그먼트) 육각형의 반지름 거리 내에있는 육각형 선 세그먼트 (각 세그먼트가 육각형의 측면 임)를 선택한 경우 해당 선 형상을 복사하여 병합 할 수 있습니다. 도로 데이터 세트에 사용하는 고유 식별자

고유 식별자를 병합하는 데 문제가있는 경우 버퍼를 적용하고 해당 세그먼트의 위치 만 선택하여 도로 데이터 세트의 속성을 적용 할 수 있습니다. 그렇게하면 너무 큰 버퍼와 일치하지 않는 것에 대해 걱정할 필요가 없습니다.

스냅 도구의 문제점은 포인트를 무차별 적으로 스냅한다는 것입니다. 사용하기에 완벽한 내성을 찾는 것은 어렵습니다. 이 방법을 사용하면 사용할 육각 선 세그먼트를 올바르게 식별 한 다음 도로 데이터의 형상을 바꾸거나 형상을 다른 데이터 세트에 삽입 할 수 있습니다.

또한 육각형의 한 쪽에서 다른쪽으로 이동하는 선 세그먼트에 여전히 문제가있는 경우 선을 정점으로 세그먼트로 분할하고 각 선의 길이를 계산 한 다음보다 큰 선 세그먼트를 제거 할 수 있습니다 육각형의 한 변의 평균 길이


1

qgis 3.0의 지오메트리 스냅 퍼가 재 작업되었으며 이제 다른 지오메트리 유형간에 스냅 할 수 있습니다. 또한 많은 수정 사항이 있습니다. 3.0이 공식적으로 릴리스되기 전에 "매일 스냅 샷"버전을 사용해 향상된 스 내퍼에 액세스 할 수 있습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.