셰이프를 사용하여 가장 가까운 선 세그먼트를 찾으십니까?


17

배경

알려진 지점에서 다이어그램에 표시된 것처럼 MultiLineStrings 테이블에 대해 가장 가까운 주변 "가시 둘레"를 설정해야합니다.

이 사이트에서 여러 용어 (예 : 최소 모서리, 최소 둘레, 가장 가까운 이웃, 클립, 다각형 포함, 가시성, 스냅, 컷 노드, 광선 추적, 홍수 채우기, 내부 경계, 라우팅, 오목 선체)를 사용하여이 사이트를 검색했습니다. 이 시나리오와 일치하는 이전 질문을 찾을 수 없습니다.

도표

  • 녹색 원은 알려진 포인트입니다.
  • 검은 선은 알려진 MultiLineString입니다.
  • 회색 선은 알려진 점에서 방사형 스윕을 나타냅니다.
  • 빨간색 점은 방사형 스윕과 MultiLineString의 가장 가까운 교차점입니다.

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

매개 변수

  • Point는 MultiLineString과 교차하지 않습니다.
  • Point는 항상 MultiLineStrings의 중심에 위치합니다.
  • MultiLineString은 Point를 완전히 둘러싸 지 않으므로 둘레는 MultiLineString입니다.
  • 약 1,000 개의 MultiLineString을 포함하는 테이블이 있습니다 (일반적으로 약 100 포인트의 단일 라인을 포함 함).

고려 된 방법론

  • 알려진 점에서 일련의 선을 구성하여 방사형 스윕을 수행합니다 (예 : 1 도씩 증가).
  • MultiLineString과 각 방사형 스윕 라인의 가장 가까운 교차점을 설정하십시오.
  • 방사형 스윕 라인 중 하나가 MultiLineString과 교차하지 않는 경우 이는 경계 MultiLineString 구성에 수용 될 경계의 간격을 나타냅니다.

요약

이 기술은 가장 가까운 교차점을 찾는 반면, 방사형 스위프의 해상도에 따라 가장 가까운 주변 노드 지점을 반드시 찾을 필요는 없습니다. 누구든지 모든 주변 지점을 설정하거나 일부 형태의 버퍼링, 섹터 화 또는 오프셋으로 방사형 스윕 기술을 보완하는 대체 방법을 추천 할 수 있습니까?

소프트웨어

필자는 솔루션에 SpatiaLite 및 / 또는 Shapely를 사용하지만 오픈 소스 소프트웨어를 사용하여 구현할 수있는 제안을 환영합니다.

편집 : 작업 솔루션 (@gene의 답변을 기반으로 함)

from shapely.geometry import Point, LineString, mapping, shape
from shapely.ops import cascaded_union
from shapely import affinity
import fiona

sweep_res = 10  # sweep resolution (degrees)
focal_pt = Point(0, 0)  # radial sweep centre point
sweep_radius = 100.0  # sweep radius

# create the radial sweep lines
line = LineString([(focal_pt.x,focal_pt.y), \
                   (focal_pt.x, focal_pt.y + sweep_radius)])

sweep_lines = [affinity.rotate(line, i, (focal_pt.x, focal_pt.y)) \
               for i in range(0, 360, sweep_res)]

radial_sweep = cascaded_union(sweep_lines)

# load the input lines and combine them into one geometry
input_lines = fiona.open("input_lines.shp")
input_shapes = [shape(f['geometry']) for f in input_lines]
all_input_lines = cascaded_union(input_shapes)

perimeter = []
# traverse each radial sweep line and check for intersection with input lines
for radial_line in radial_sweep:
    inter = radial_line.intersection(all_input_lines)

    if inter.type == "MultiPoint":
       # radial line intersects at multiple points
       inter_dict = {}
       for inter_pt in inter:
           inter_dict[focal_pt.distance(inter_pt)] = inter_pt
       # save the nearest intersected point to the sweep centre point
       perimeter.append(inter_dict[min(inter_dict.keys())])

    if inter.type == "Point":
       # radial line intersects at one point only
       perimeter.append(inter)

    if inter.type == "GeometryCollection":
       # radial line doesn't intersect, so skip
       pass

# combine the nearest perimeter points into one geometry
solution = cascaded_union(perimeter)

# save the perimeter geometry
schema = {'geometry': 'MultiPoint', 'properties': {'test': 'int'}}
with fiona.open('perimeter.shp', 'w', 'ESRI Shapefile', schema) as e:
     e.write({'geometry':mapping(solution), 'properties':{'test':1}})

일반적으로 방사형 스윕에는 의미있는 "해상도"가 없습니다. 이벤트는 하나의 "이벤트"에서 다음 이벤트로 순서대로 스캔합니다. 여기서 이벤트는 폴리 라인의 원래 노드와 상호 교차로 구성됩니다 (원래의 스윕 중 동적으로 찾을 수 있음) 노드). 출력은 완벽하게 정확합니다.
whuber

답변:


17

shapefile로 예제를 재현했습니다.

ShapelyFiona 를 사용 하여 문제를 해결할 수 있습니다.

1) 문제 (매끈한 Point) :

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

2) 임의의 줄로 시작 (적절한 길이) :

from shapely.geometry import Point, LineString
line = LineString([(point.x,point.y),(final_pt.x,final_pt.y)])

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

3) shapely.affinity.rotate 를 사용하여 반지름을 만듭니다. (점에서 선을 회전하면 Python 에서 Mike Toews 의 답변을 볼 수 있습니다 .

from shapely import affinity
# Rotate i degrees CCW from origin at point (step 10°)
radii= [affinity.rotate(line, i, (point.x,point.y)) for i in range(0,360,10)]

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

4) 이제 shapely : cascaded_union (또는 shapely : unary_union )을 사용하여 MultiLineString을 가져옵니다.

from shapely.ops import cascaded_union
mergedradii = cascaded_union(radii)
print mergedradii.type
MultiLineString

5) 원래 줄 (shapefile)과 동일

import fiona
from shapely.geometry import shape
orlines = fiona.open("orlines.shp")
shapes = [shape(f['geometry']) for f in orlines]
mergedlines = cascaded_union(shapes)
print mergedlines.type
MultiLineString

6) 두 개의 다중 지오메트리 간의 교집합이 계산되고 결과가 쉐이프 파일에 저장됩니다.

 points =  mergedlines.intersection(mergedradii)
 print points.type
 MultiPoint
 from shapely.geometry import mapping
 schema = {'geometry': 'MultiPoint','properties': {'test': 'int'}}
 with fiona.open('intersect.shp','w','ESRI Shapefile', schema) as e:
      e.write({'geometry':mapping(points), 'properties':{'test':1}})

결과:

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

7) 그러나 문제는 반경을 더 길게 사용하면 결과가 다릅니다.

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

8) 결과를 얻으려면 반지름의 원래 지점에서 가장 짧은 거리의 지점 만 선택하면됩니다.

points_ok = []
for line in mergeradii:
   if line.intersects(mergedlines):
       if line.intersection(mergedlines).type == "MultiPoint":
          # multiple points: select the point with the minimum distance
          a = {}
          for pt in line.intersection(merged):
              a[point.distance(pt)] = pt
          points_ok.append(a[min(a.keys())])
       if line.intersection(mergedlines).type == "Point":
          # ok, only one intersection
          points_ok.append(line.intersection(mergedlines))
solution = cascaded_union(points_ok)
schema = {'geometry': 'MultiPoint','properties': {'test': 'int'}}
with fiona.open('intersect3.shp','w','ESRI Shapefile', schema) as e:
     e.write({'geometry':mapping(solution), 'properties':{'test':1}})

최종 결과:

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

나는 그것이 당신이 원하는 것이기를 바랍니다.


1
탁월한 답변-특히 shapefile을 통한 입출력에 대한 Fiona 사용에 관한 정보를 제공합니다. 내 질문에 귀하의 답변을 사용하는 코드를 추가하고 intersection필요한 계산 양을 줄이기 위해 수정했습니다 -감사합니다.
Rusty Magoo
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.