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, 필드 fictitiuosUnique 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.