JPA Hibernate, QueryDSL 그리고 Spatial DB (4)

박양원·2024년 2월 20일
0

Trouble Shooting

목록 보기
7/16
post-thumbnail
post-custom-banner

해당 포스팅은 사이드 프로젝트 진행 중 겪은 크고 작은 이슈들에 대한 기록입니다.

현재 범위로 잡은 Polygon의 문제점

  • 현재 기준 Point에서 부터 Nkm 떨어진 공간을 표현하는 Geometry로 Polygon을 사용하고 있는데, 이는 대략 아래와 같은 그림이다.

polygon

  • 빨간점이 기준 Point라고 할 때, Point로부터 Nkm 떨어진 [북서 북동 남동 남서] 좌표를 찾아 이를 기준으로 정사각형 모양의 Polygon을 생성하고 있는 것을 알 수 있다.
  • 이제 우린 저 범위 내의 좌표점들을 얻을 수 있게 되는 것인데, 곰곰히 생각해보니 여기엔 문제점이 있다. 과연 우리가 얻고자 하는 Nkm의 모든 범위 이내의 모든 좌표를 얻을 수 있는 것일까?

polygon

  • 너무나 당연한 얘기겠지만 직각이등변삼각형에서 대각선의 길이는 두 밑변의 길이보다 무조건 길다. 따라서 같은 거리 Nkm에 위치한 파란점은 만들어진 Polygon의 내부에 포함되지 않아서 조건에 부합하지 않고 결국 Select 쿼리의 결괏값에 포함되지 않는다.
  • 따라서 아래와 같이 빨간점에서부터 4개의 기준 좌표들까지의 거리를 Nkm가 아닌 N√2로 잡고 Polygon을 생성한 후, 그 범위 내에서 Nkm안에 포함되는 좌표들을 구해야 한다. 즉, r=N인 원의 범위가 최종적으로 우리가 찾고자 하는 공간 데이터의 탐색 범위가 될 것이다. (피타고라스의 정리)

polygon


어떻게 원을 표현할 것인가?

  • 결국 정확한 거리를 기반으로 탐색을 수행하려면 탐색 범위를 원으로 만들어 사용하는 방법을 찾아야했고 현재까지 할 수 있는 방법을 총 2가지 찾았다.

1. org.locationtech.jts의 GeometricShapeFactory 활용

2. ST_Distance를 함께 사용

  • N√2로 4개 좌표를 구하여 이를 첫번째 탐색 범위로 잡고 (ST_Contains), 앞서 생성한 범위 중ST_Distance를 추가 조건으로 활용하여 Nkm 이내인 좌표들만 탐색한다.
  • QueryDSL 활용 예시
public List<Store> getStoresByStContains(Geometry<G2D> polygon) {
    return queryFactory
        .select(store)
        .from(store)
        .leftJoin(store.file, uploadFile)
        .fetchJoin()
        .where(stContains(polygon), stDistance(polygon).loe(3))
        .fetch();
}

private BooleanExpression stContains(Geometry<G2D> polygon) {
    return GeometryExpressions
        .asGeometry(polygon)
        .contains(store.point);
}

private NumberExpression<Double> stDistance(Geometry<G2D> polygon) {
    return GeometryExpressions
        .asGeometry(store.point)
        .distance(polygon);
}

Geolatte에서 원을 생성하는 방법은?

  • JTS의 GeometricShapeFactory처럼 Geolatte에도 비슷한 기능이 있을까 싶어 찾아보았으나 찾지 못하였다. 있을 것 같긴 한데 생각보다 내용이 복잡하여 보류하였다.
  • 비슷하게 org.geolatte.geom.cga 패키지 내부에 보면 Circle이라는 클래스가 있다. 이를 활용하면 Circle 객체를 만들 수는 있으나 얘를 어디에 사용할 수 있는지 찾지 못하였다... Circle 클래스는 Geometry를 상속받은 클래스가 아닌 독립적인 Class여서 (그냥 부모 클래스 자체가 하나도 없음) Geometry를 사용하는 메소드에 사용할 수도 없고, qureydsl-spatial 내부를 검색해봐도 Circle 객체가 사용되는 곳은 CircularArcLinearizer라는 클래스 밖에 없는데 이마저도 Circle 객체를 선형화 시키는 기능을 할 뿐이고 이를 Geometry로 사용하는 방법은 찾지 못하였다.
  • 아직 Spatial에 관하여 공부가 부족하다는 것을 느낀다.

어떤 방법을 사용할까?

  • 결론부터 말하자면 1번이 아닌 2번 방식을 사용하는게 좋은 것 같다고 생각한다.

1. 두가지 라이브러리에 종속적이지 않아도 된다.

  • GeometricShapeFactory 기능은 Geolatte의 기능이 아니다. 결국 JTS와 Geolatte 두 라이브러리 모두에 종속적인 기능이 만들어지게 되는데 개인적으로 난 대안이 있거나 성능상 확실한 이점이 없다면 여러가지 라이브러리를 쓰는 것을 선호하지 않는다.

2. 원하는 거리 기준이 뭔지 모르겠다.

private org.locationtech.jts.geom.Geometry createCircle(double x, double y, double radius) {
    GeometricShapeFactory factory = new GeometricShapeFactory();
    
    factory.setNumPoints(32);    						// ?
    factory.setCentre(new Coordinate(x, y));
    factory.setSize(radius * 2);						  // ?

    return factory.createCircle();
}
  • setNumPoints는 설명이라도 친절해서 활용은 못했지만 용도가 뭔지는 알 수 있다.
  • Sets the total number of points in the created {@link Geometry}. The created geometry will have no more than this number of points, unless more are needed to create a valid geometry.
  • 하지만 정작 중요한 거리 기준인 반지름(radius)의 값이 어떤 단위로 작동하는지 알 수 없어 정확한 거리 계산을 할 수가 없다.
  • 실제 테스트에서도 어림짐작하여 값을 넣어서 사용했을 뿐 아직까지 정확한 기준 단위를 찾지 못하였다.

3. 심지어 2번이 더 빠르다.

  • 몇번을 실행해봐도 쿼리 실행 속도도 더 빠르고 전체 로직 수행 시간 역시 더 빠르다.
  • GeometryShapeFactory로 Circle을 생성하는 시간이 생각보다 좀 걸리는 것 같다.
  • 전체 로직 수행 속도 비교

circle

  • 각각 쿼리 수행 속도 비교
    • 1번: 5343ms, 2번: 4461ms

polygon

circle


마무리

  • 최종적으로 Geolatte + QueryDSL Spatial + (ST_Contains + ST_Distance)를 사용하여 공간 데이터를 다루기로 결정하였습니다.
  • 제가 처음 Spatial DB를 사용하기로 마음 먹고 공부를 시작하였을 때 많이 막막하고 자료도 없어서 힘들었기에 저와 같은 초보이시거나 새로이 도입해보고자 하시는 분이 이 포스팅들을 보시면서 Spatial DB의 첫걸음 용 나침반으로나마 사용될 수 있었으면 좋겠습니다.
  • 아직 많이 미숙하고 저 또한 초보이기에 틀린 정보가 있거나 더 좋은 방법이 있다면 댓글 달아주시면 감사하겠습니다!
post-custom-banner

0개의 댓글