본문 바로가기
프로그래밍/기타 Python 모듈

shapely 모듈에 대해서 알아보자 - 응용편

by 부자 꽁냥이 2023. 3. 30.

지난 포스팅에서는 shapely 모듈의 기본적인 사용법과 도형을 그리는 방법을 알아보았다. 하지만 shapely는 더 놀라운 기능을 가지고 있다. 이번 포스팅에서는 shapely 모듈로 할 수 있는 일들에 대해서 알아본다.


   shapely 모듈 응용하기

1) 원 또는 타원 그리기 : Point

a. 원

shapely는 원(또는 타원)을 나타내는 객체가 없어서 직접적으로 호출할 수는 없다. 하지만 Point 객체를 이용하면 만들 수 있다. 먼저 Point 객체를 생성하여 원의 중심을 설정하고 buffer에 반지름 길이를 넣어주면 원을 만들 수 있다.

 

아래 그림은 Point를 이용하여 원을 만들고 그림을 그린다.

 

import matplotlib.pyplot as plt
plt.rcParams['axes.unicode_minus'] = False
from shapely import Point
from shapely.plotting import plot_polygon

circle = Point(2,3).buffer(3) ## 중심이 2, 3이고 반지름이 3인 원

min_val = min(circle.bounds)
max_val = max(circle.bounds)

fig = plt.figure(figsize=(8,8))
fig.set_facecolor('white')
ax = fig.add_subplot()
plot_polygon(circle, ax=ax, add_points=False)
ax.set_xlim(min_val, max_val)
ax.set_ylim(min_val, max_val)
plt.show()

 

b. 타원

타원은 원을 그리는 것보다 약간 복잡하다. 먼저 Point 객체와 buffer를 이용하여 단위원(Unit Circle)을 만들고 affinity의 scale을 이용하여 단위원을 x축, y축으로 늘려주면 된다.

 

from shapely import affinity

circle = Point(5,5).buffer(1) ## 중심이 (5, 5)이고 반지름이 1인 원
ellipse = affinity.scale(circle, 5, 10) ## x축 방향으로 5배 y축 방향으로 10배 확대
min_val = min(ellipse.bounds)
max_val = max(ellipse.bounds)

fig = plt.figure(figsize=(8,8))
fig.set_facecolor('white')
ax = fig.add_subplot()
plot_polygon(ellipse, ax=ax, add_points=False)
## xlim, ylim을 지정안하면 타원이 원처럼 보임
ax.set_xlim(min_val, max_val)
ax.set_ylim(min_val-2, max_val+2)
plt.show()

 


2) 두 도형 간 최소 거리 구하기 : nearest_points

nearest_points를 이용하면 두 도형 간 최소 거리를 계산할 수 있다. nearest_points에 도형을 나타내는 두 객체를 차례대로 넣어주면 각 객체의 바운더리 중에서 최소 거리가 되는 지점을 알려준다. 최소 거리는 도형 객체가 갖고 있는 distance를 이용하여 계산할 수도 있다.

 

from shapely import LineString
from shapely.ops import nearest_points
from shapely.plotting import plot_line, plot_polygon

circle = Point(5,5).buffer(2) ## 중심이 (5, 5)이고 반지름이 1인 원
ellipse = affinity.scale(Point(0,0).buffer(1), 2, 4)
min_distance_line = LineString(nearest_points(circle, ellipse))
print('두 도형간 최소 거리가 되는 점 :', nearest_points(circle, ellipse))
print('두 도형간 최소 거리 :', min_distance_line.length) ## 또는 circle.distance(ellipse)

 

 

nearest_points가 두 객체간 거리를 최소로 하는 점 2개를 반환한다는 것을 이용하여 두 도형 간 최소 직선 경로를 그릴 수도 있다.

 

fig = plt.figure(figsize=(6,6))
fig.set_facecolor('white')
ax = fig.add_subplot()
plot_polygon(circle, ax=ax, add_points=False)
plot_polygon(ellipse, ax=ax, add_points=False)
plot_line(min_distance_line, ax=ax)
ax.set_xlim(-5, 8)
ax.set_ylim(-5, 8)
plt.show()

 


3) 한 도형이 다른 도형 내부에 있는지 확인하기 : contains

shapely는 특정 포인트(점) 또는 도형이 다른 도형 내부에 있는지 확인하는 것도 가능하게 해준다. polygon 객체가 갖고 있는 contains 메서드를 이용하면 된다.

 

아래 코드는 두 점과 원이 다각형(Polygon) 내부에 있는지 여부를 출력하고 실제로 그림을 통해서 확인한다.

 

from shapely import Polygon
from shapely.plotting import plot_points, plot_polygon

p1 = Point(4, 4)
p2 = Point(1, 1)
circle = Point(3, 2).buffer(0.3) ## 중심이 (3, 2)이고 반지름이 0.3인 원

polygon = Polygon([(0, 0), (3, 4), (5, 2), (3, 1)])

print('점 p1을 포함하는가? :', polygon.contains(p1))
print('점 p2을 포함하는가? :', polygon.contains(p2))
print('원을 포함하는가? :', polygon.contains(circle))

fig = plt.figure(figsize=(6,6))
fig.set_facecolor('white')
ax = fig.add_subplot()
plot_polygon(polygon, ax=ax, add_points=False)
plot_polygon(circle, ax=ax, add_points=False, facecolor='r', edgecolor='none')
plot_points([p1, p2], ax=ax, color='k')
plt.show()

 


4) Polygon에서 interior 사용하기

Polygon은 holes 인자를 지정하여 좀 더 풍부한 도형을 표현할 수 있다. 이때 외곽쪽 경계를 나타내는 exterior 포인트는 shell 인자에 내부 경계를 나타내는 interior 포인트는 holes 인자에 넣어주면 된다. 이때 exterior 포인트와 interior 포인트가 서로 교차하지 않도록 해주어야 원하는 대로 잘 그려진다.

 

아래 코드는 exterior 포인트와 interior 포인트가 교차하지 않는 경우와 교차하는 경우에 대한 그림을 그린 것이다.

 

from shapely import Polygon
from shapely.plotting import plot_points, plot_polygon

exterior = [(0, 0), (0, 2), (2, 2), (2, 0), (0, 0)]
valid_interior = [(1, 0), (0.5, 0.5), (1, 1), (1.5, 0.5), (1, 0)][::-1]
## exterior와 valid_interior가 교차하지 않음
valid_polygon = Polygon(exterior, [valid_interior]) ## interior 포인트는 리스트로 감싸줘야함.

## exterior와 valid_interior가 교차
invalid_interior = [(1, 0), (0.5, 0.5), (1, 1), (1.5, 0.5), (1, 0)]
invalid_polygon = Polygon(exterior, [invalid_interior]) ## interior 포인트는 리스트로 감싸줘야함.

fig, axs = plt.subplots(1, 2, figsize=(10,5))
fig.set_facecolor('white')
ax1 = axs[0]
ax2 = axs[1]
plot_polygon(polygon, ax=ax1)
ax1.set_title('Valid Polygon')
plot_polygon(invalid_polygon, ax=ax2)
ax2.set_title('Invalid Polygon')
plt.show()

 

 

이러한 차이가 발생한 이유는 valid_polygon 객체(아래 그림 왼쪽)는 아래와 같은 진행 방향으로 exterior와 interior 포인트를 지정했고 서로 교차가 되지 않기 때문에 자연스럽게 빈 공간(hole)이 생긴 것이다. 반면 invalid_polygon 객체(아래 그림 오른쪽)은 4->5 경로와 8->9 경로가 서로 교차하므로 빈 공간이 생기지 않은 것이다.


4) 도형 병합, 빼기, 교집합: union, difference, intersection

나는 개인적으로 이 부분이 가장 강력한 기능이라고 생각한다. shapely는 두 도형의 병합, 빼기, 교집합에 대응하는 도형을 생성할 수 있다. 병합은 union, 빼기는 difference, 교집합은 intersection을 이용하면 된다.

 

아래 코드는 두 원에 대한 병합, 빼기 그리고 교집합에 대응하는 도형을 구하고 이를 그린 것이다.

 

from shapely import Point
from shapely.plotting import plot_polygon

left_circle = Point(0,0).buffer(3)
right_circle = Point(2.5,0).buffer(3)
union_circles = left_circle.union(right_circle) ## 도형 병합
left_right_circle = left_circle.difference(right_circle) ## 왼쪽에서 오른쪽 빼기
right_left_circle = right_circle.difference(left_circle) ## 오른쪽에서 왼쪽 빼기
intersection_circles = left_circle.intersection(right_circle) ## 교집합

fig, axs = plt.subplots(2, 2, figsize=(8,8))
fig.set_facecolor('white')
ax1 = axs[0, 0]
ax2 = axs[0, 1]
ax3 = axs[1, 0]
ax4 = axs[1, 1]

plot_polygon(left_right_circle, ax=ax1, add_points=False)
plot_polygon(right_left_circle, ax=ax2, add_points=False)
plot_polygon(union_circles, ax=ax3, add_points=False)
plot_polygon(intersection_circles, ax=ax4, add_points=False)

total_axs = [ax1, ax2, ax3, ax4]
titles = ['left-right', 'right-left', 'union', 'intersection']
min_x, max_x = -4, 6
min_y, max_y = -5, 5

for i, ax in enumerate(total_axs):
    ax.set_xlim(min_x, max_x)
    ax.set_ylim(min_y, max_y)
    ax.set_title(titles[i])
    
plt.show()

 


5) 두 도형이 겹치는지 여부 : disjoint

shapely 객체가 가진 disjoint를 이용하면 두 도형이 겹치는지 안겹치는지 알 수 있다. disjoint를 이용하여 True가 나오면 안 겹치고 False가 나오면 겹치는 것을 의미한다.

 

아래 코드는 원 세개를 생성하고 두 원에 대한 겹침 여부를 출력하고 이를 그림으로 확인한다.

 

from shapely import Point
from shapely.plotting import plot_polygon

c1 = Point(0,0).buffer(3)
c2 = Point(2.5,0).buffer(3)
c3 = Point(5,5).buffer(3)

print('c1과 c2는 겹치는가? :', not c1.disjoint(c2))
print('c1과 c3는 겹치는가? :', not c1.disjoint(c3))

fig, axs = plt.subplots(1, 2, figsize=(10,5))
fig.set_facecolor('white')
ax1 = axs[0]
ax2 = axs[1]

plot_polygon(c1, ax=ax1, add_points=False)
plot_polygon(c2, ax=ax1, add_points=False)
plot_polygon(c1, ax=ax2, add_points=False)
plot_polygon(c3, ax=ax2, add_points=False)

ax1.set_title('c1, c2')
ax2.set_title('c1, c3')
    
plt.show()

 


댓글


맨 위로