지도로 말하기/튜토리얼 따라하기

AutoGIS Excercise-1

코드아키택트 2023. 12. 28. 00:00
반응형

안녕하세요 집 DS입니다. 

 

지난 포스팅에서는 Python을 통한 GIS 자동화와 관련된 렉쳐에 대해 소개해 드렸습니다. 이번에는 제가 직접 풀어본 후기를 써보려 합니다. 해당 문제원본은 아래 링크를 통해 확인 가능합니다

 

https://github.com/Automating-GIS-processes-II-2023/Exercise-1

 

GitHub - Automating-GIS-processes-II-2023/Exercise-1

Contribute to Automating-GIS-processes-II-2023/Exercise-1 development by creating an account on GitHub.

github.com

 

문제 1a)

x,y좌표를 받아 Point로 만들기

1주 차 문제를 보면 shapely의 기초적인 내용을 사용하도록 되어있습니다. 그중 1a)는 input에 대한 제한이 하나도 없습니다. 대부분 그냥 하실 수 있으리라 생가합니다. 그래도 스켈레톤 코드를 아래 써보도록 하겠습니다.

 

from shapely.geometry import Point

def create_point_geometry(x, y):
    return pt

 

x, y좌표를 받아 point를 만들면 됩니다. 요즘엔 튜토리얼을 보면서도 공식 문서를 자주 보려고 합니다. shapely 공식 문서 링크는 아래와 같습니다

 

https://shapely.readthedocs.io/en/stable/reference/shapely.Point.html#shapely.Point

 

shapely.Point — Shapely 0 documentation

True if geometries are equal at all coordinates to a specified decimal place. Deprecated since version 1.8.0: The ‘almost_equals()’ method is deprecated and will be removed in Shapely 2.1 because the name is confusing. The ‘equals_exact()’ method s

shapely.readthedocs.io

 

여기서 가장 핵심이 되는 패턴은 point를 만드는 것입니다.

 

 

영어로 설명된 부분은 읽기 힘들지만 그래도 대략 읽을 필요는 있습니다. 설명을 보면 포인트를 만들기 위해 파라미터를 1개 받거나 2개 또는 3개를 받는다고 써있습니다. 이미지로 설명하면 아래와 같습니다.

 

1개를 받는 경우, 해당 좌표를 array(list) 형태로 하면 된다고 쓰여있고, 2개 또는 3개인 경우 각 좌표를 넣어주면 됩니다. 예시를 통해 살펴보면 아래와 같습니다. 

# array-like로 point를 만드는 경우
p_from_array = Point([1.0,1.0])

# 2개 또는 3개로 만드는 경우
p_from_each = Point(1.0,1.0)

 

Z 좌표도 넣을 수 있을텐데 이번 예제에서 다루지 않아 일단 넘어가도록 하겠습니다. 기초적인 부분은 다들 이해가 가셨을 거라 생각하고 제가 푼 방식을 쓰면 아래와 같습니다.

 

from shapely.geometry import Point

def create_point_geometry(x:float, y:float)->Point:
    """Create a 2d point from x,y coordinate
    args:
        x : float. x coordinate
        y : float. y coordinate
    Returns:
        point : 2d point
    """
    pt = Point(x,y)
    return pt

 

문제에서 docstring(설명문)을 요구하기 때문에 위 내용을 넣었습니다. `"""`을 이용해서 doc string을 넣을 수 있습니다. 위 형식대로 하는 게 google에서 쓰는 방식이라고 들었습니다. """ 바로 다음에 나오는 설명은 해당 function을 호출할 때 나오는 설명문입니다. 그 아래 args와 return은 작동하는 데는 큰 의미는 없습니다. 다만 사람이 알아보기 편리하게 만든 형태입니다.

 

doc string을 작서하면 위와같이 설명이 나옵니다

 

코드를 보면 x:float, y:float, -> Point라고 써있는 부분을 볼 수 있습니다. 이를 type hint라고 합니다. 어떤 type의 파라미터를 넣어야 하는지 알려주는 역할을 합니다. 좋은 점인지 안 좋은 점인지 저도 헷갈릴 때가 많지만 무시하고 넣어도 프로그램은 작동하도록 되어있습니다. mypy나 pyright 등을 쓰면 잘못된 type을 input으로 했을 때 어마어마한 에러 메시지를 볼 수 있습니다. 저는 주로 nvim을 에디터로 쓰는데, 여기서는 ipynb를 사용해야 해서 vscode를 썼습니다. 제대로 설정을 안 해서 그런지 오류가 많이 나오지는 않았지만 여하튼 데이터 타입을 맞추는 연습을 해볼 필요가 있습니다.

 

문제 1b)

LineString 만들기

두번째 문제. Point list를 받아 LineString을 만드는 문제입니다. 

 

좀 더 속도감 있게 진행해 보죠.

 

from shapely.geometry import LineString
def create_line_geometry(pts:list[Point])->LineString:
    return line_string

 

스켈레톤 코드는 위와 같습니다. LineString doc을 잠시 읽어보겠습니다. https://shapely.readthedocs.io/en/stable/reference/shapely.LineString.html#shapely.LineString

 

shapely.LineString — Shapely 0 documentation

A sequence of (x, y, [,z]) numeric coordinate pairs or triples, or an array-like with shape (N, 2) or (N, 3). Also can be a sequence of Point objects.

shapely.readthedocs.io

 

Parameters:
coordinates : sequence
A sequence of (x, y, [,z]) numeric coordinate pairs or triples, or an array-like with shape (N, 2) or (N, 3). Also can be a sequence of Point objects.

 

이 어려운 이야기가 무슨 뜻이냐 하면 다음과 같습니다.

  1. [(x1,y1),(x2,y2),...,(xn,yn)]
  2. [[x1,y1],[x2,y2],...,[xn,yn]]
  3. [point1,point2,point3]

 

이것을 그림으로 표현하면 다음과 같습니다.

 

 

배열은 배열인데, point 배열, tuple 배열, 2개의 값을 가지는 list의 배열입니다. 이것만 놓고 보면 간단합니다. 하지만 function에서 요구하는 다음 두가지 조건이 추가도비니다.

 

  1. input은 list일 것
  2. list의 길이(length)는 2 이상일 것

 

어떤 요구조건을 강제하기 위해 많이 쓰이는 방식 중 하나는 assert입니다. 구문을 보면 assert 조건, 조건에 충족하지 않을 때 메시지 이런 식으로 되어 있습니다. 이를 예제로 살펴보면 아래와 같습니다.

 

assert isinstance(pts,list), "Input should be a list"
assert len(pts) >= 2, "At minimum two points are reuiqred for a LineString"

 

첫 번째 줄은 이런 의미입니다. pts라는 변수가 list객체가 아니면 "Input should be a list"라는 메시지를 띄우고 AssertionError를 발생시킵니다. 같은 원리로 두 번째 줄도 pts의 length가 2 미만이면 메시지와 에러를 발생시킵니다. 그럼 위 내용을 가지고 다시 function을 정의하면 아래와 같습니다.

 

from shapely.geometry import LineString
def create_line_geometry(pts:list[Point])->LineString:
    """ Create Line geometry from list of points
    Args:
        pts: list[Point]. Points to construct line stirng
    Returns:
        LienString: LineString made of points of input
    """
    assert isinstance(pts,list), "Input should be a list"
    assert len(pts) >= 2, "At minimum two points are reuiqred for a LineString"
    line_string = LineString(pts)
    return line_string

 

문제 1c)

폴리곤 만들기

 

이제 문제의 패턴이 조금씩 보입니다. Docs를 봅시다

shell이라는 친구와 holes라는 친구가 보입니다. shell의 input조건은 LineString과 동일함을 볼 수 있습니다. 문제에서 주어진 조건은 아래와 같습니다.

 

  1. input은 list
  2. list의 length는 3 이상
  3. list의 각 item은 length가 2인 tuple일 것
  4. tuple의 각 item은 Int 또는 float일 것

 

조건이 조금 많아졌습니다. 3번까지는 쉽게 누구나 예상가능하게 할 수 있으리라고 생각합니다. 4번은 본인마다 스타일이 있을 것이라고 생각합니다. 저는 private function을 정의해서 사용했습니다. 그리고 조건에 맞지 않는 경우 Error를 띄우는 방식으로 진행했습니다. 프로그램 상 test를 진행할 때 AssertionError를 사용하기 때문에 AssertionError를 사용했습니다.

 

코드를 바로 쓰면 아래와 같습니다.

 

from shapely.geometry import Polygon
# ADD YOUR OWN CODE HERE
def create_polygon_geometry(coords : list[tuple])-> Polygon:
    """ Create polygon from list of coordinate tuple
    Args:
        coords : list[tuple]. coordinate pairs
    Returns:
        Polygon : Polygon made by coordinates
    """
    assert isinstance(coords,list), "Input should be a list"
    assert len(coords) >= 3, "At minimum three points are required for a polygon"
    def intOrFloat(x): # private function
        if isinstance(x,int) or isinstance(x,float): #loop안에 사용하기 때문에, 조건에 맞는경우 return으로 했습니다.
            return
        else:
            raise AssertionError("Value need to be int or float") #조건에 맞지 않는경우 AssertionError를 띄웁니다.
    for tup in coords:
        assert isinstance(tup,tuple) and len(tup)==2,"All list values must be coordinate tuples"
        intOrFloat(tup[0])
        intOrFloat(tup[1])

    return Polygon(coords)

 

조금 더 깔끔하게 쓰고 싶은데 아쉽네요 계속 넘어갑니다.

문제 2a)

centroid 구하기

이 부분은 쉬우면서도 난해합니다. 그 이유는 공식 문서에는 BaseGeometry에 대한 설명이 자세히 없는데 반해 문제에서는 BaseGeometry에 관한 내용을 요구합니다. github를 찾아보니 아래와 같은 내용이 있었습니다.

 

https://github.com/shapely/shapely/blob/main/shapely/geometry/base.py#L369

 

shapely 안에는 BaseGeometry라는 객체가 존재합니다. 이 객체의 property 중에는 centroid라는 것이 존재합니다. 문서에는 centroid에 대한 다른 설명도 있던데 일단은 제치도록 하겠습니다. 

 

문제에서 요구하는 사항은 input이 BaseGeometry인지 확인한 후 centroid를 구하는 것입니다. 그렇다면 input의 타입은 BaseGeometry이고 return type은 Point여야 합니다. 그럼 type hint와 assertion을 조합해 보면 아래와 같은 코드를 만들 수 있습니다.

from shapely.geometry.base import BaseGeometry
def get_centroid(geom:BaseGeometry) -> Point:
    """ Get centroid of shapely base goemetry
    Args:
        geom: BaseGeometry. shapely geometry to compute centroid
    Return:
        centroid: Point. Calculated centroid
    """
    assert isinstance(geom,BaseGeometry), "Input mest be a shapely geometry"
    return geom.centroid

 

 

문제 2c)

길이 구하기

형상의 길이를 구한다. 우리가 고등학교 때 배운 지식으로 점, 선, 면, 부피(?)가 있습니다. 길이를 구하려면 선이 되어야 합니다. shapely의 깊은 부분까지는 모르지만 문제에서 요구하는 것은 input은 LineString 또는 Polygon이어야 한다는 것입니다. 이것도 크게 어려운 부분은 없습니다. 

 

제가 한 단계 더 나아가서 해보고 싶었던 것은, 여러 데이터 타입을 type hint로 쓰는 것이었습니다. python 내장 라이브러리 중 typing이라는 친구가 있습니다. 이 친구를 활용해 봅니다. typing 중 uninon이라는 모듈은 여러 클래스를 type hint로 할 수 있는 기능입니다. 빠르게 빠르게.

 

# ADD YOUR OWN CODE HERE
from typing import Union
def get_length(geom:Union[LineString,Polygon])->float:
    """ Get length of LienString or polygon
    Args:
        geom: Geometry to calculate length. follow the type hinting
    Returns:
        length : length of a geometry
     
    """
    assert isinstance(geom,LineString) or isinstance(geom,Polygon), "Geometry should be either a LineString or a Polygon"
    return geom.length

 

위에서 보면 Union[LineString,Polygon]을 통해 두 개의 input을 type hint로 줄 수 있습니다.

반응형