Hibernate Spatial은 지리 데이터 작업을 위한 표준 인터페이스를 제공한다 .

 

지리적 데이터에는 점, 선, 다각형 과 같은 엔터티의 표현이 포함됨 .

 

Hibernate Spatial은 기하학 모델로 JTS(Java Topology Suite)를 사용

 

Geometry 는 JTS의 모든 공간 유형에 대한 기본 유형.
ex)  Point , Polygon 및 기타 유형과 같은 다른 유형이 Geometry 에서 확장됨을 의미합니다 . 

Java의 Geometry 유형은 MySql  GEOMETRY 유형에도 해당됨

 

유형의 String 표현을 구문 분석하여 Geometry 인스턴스를 얻는다. 

JTS에서 제공하는 유틸리티 클래스 WKTReader를 사용하여 텍스트를 Geometry 유형으로 변환할 수 있다 .

public Geometry wktToGeometry(String wellKnownText) 
  throws ParseException {
 
    return new WKTReader().read(wellKnownText);
}

실제로 동작하는 것을 살펴보자

@Test
public void shouldConvertWktToGeometry() {
    Geometry geometry = wktToGeometry("POINT (2 5)");
 
    assertEquals("Point", geometry.getGeometryType());
    assertTrue(geometry instanceof Point);
}

보다시피 메소드의 반환 유형이 read() 메소드 인 경우에도 실제 인스턴스는 Point 의 인스턴스임

 

 

Point는 두 개의 좌표로 표시

 

public void insertPoint(String point) {
    PointEntity entity = new PointEntity();
    entity.setPoint((Point) wktToGeometry(point));
    session.persist(entity);
}

insertPoint() 메서드는 Point 의 텍스트(WKT) 표현을 받아 이를 Point 인스턴스 로 변환 하고 DB에 저장

 

 

Point 에서 toString() 을 호출하면 Point 의 WKT 표현이 반환

@Test
public void shouldInsertAndSelectPoints() {
    PointEntity entity = new PointEntity();
    entity.setPoint((Point) wktToGeometry("POINT (1 1)"));

    session.persist(entity);
    PointEntity fromDb = session
      .find(PointEntity.class, entity.getId());
 
    assertEquals("POINT (1 1)", fromDb.getPoint().toString());
    assertTrue(geometry instanceof Point);
}

Geometry 클래스가 toString() 메서드를 재정의 하고 내부적으로 이전에 본 WKTReader 의 보완 클래스인 WKTWriter를 사용하기 때문

 

공간함수 활용

 

MySQL의 이러한 함수 중 하나는 한 Geometry가 다른 Geometry 내에 있는지 여부를 알려주는 ST_WITHIN() 

주어진 반경 내의 모든 점을 찾아보자

 

원을 만드는 방법부터 보자

public Geometry createCircle(double x, double y, double radius) {
    GeometricShapeFactory shapeFactory = new GeometricShapeFactory();
    shapeFactory.setNumPoints(32);
    shapeFactory.setCentre(new Coordinate(x, y));
    shapeFactory.setSize(radius * 2);
    return shapeFactory.createCircle();
}

원은 setNumPoints() 메서드로 지정된 유한한 점 집합으로 표현된다 . 

setSize() 메서드를 호출할때 *2를 하는 이유는
중심 주위에 원을 두 방향으로 그려야 하므로 반지름의 두 배가 된다.

 

이제 주어진 반경 내에서 포인트를 가져오는 방법을 보자

@Test
public void shouldSelectAllPointsWithinRadius() throws ParseException {
    insertPoint("POINT (1 1)");
    insertPoint("POINT (1 2)");
    insertPoint("POINT (3 4)");
    insertPoint("POINT (5 6)");

    Query query = session.createQuery("select p from PointEntity p where 
      within(p.point, :circle) = true", PointEntity.class);
    query.setParameter("circle", createCircle(0.0, 0.0, 5));

    assertThat(query.getResultList().stream()
      .map(p -> ((PointEntity) p).getPoint().toString()))
      .containsOnly("POINT (1 1)", "POINT (1 2)");
    }

Hibernate는 자신의 within() 함수를 MySql의 ST_WITHIN() 함수 에 매핑

within () 함수는 주어진 Geometry가 다른 Geometry 내에 완전히 있는 경우에만 true를 반환

따라서 원의 경계면에 있는 점(3 4)는 반환하지 않음

 

 

이번에는 DB에 point대신 polygon을 삽입하고 폴리곤에 인접한 폴리곤을 가져와보자

@Entity
public class PolygonEntity {

    @Id
    @GeneratedValue
    private Long id;

    private Polygon polygon;

    // standard getters and setters
}
@Test
public void shouldSelectAdjacentPolygons() throws ParseException {
    insertPolygon("POLYGON ((0 0, 0 5, 5 5, 5 0, 0 0))");
    insertPolygon("POLYGON ((3 0, 3 5, 8 5, 8 0, 3 0))");
    insertPolygon("POLYGON ((2 2, 3 1, 2 5, 4 3, 3 3, 2 2))");

    Query query = session.createQuery("select p from PolygonEntity p 
      where touches(p.polygon, :polygon) = true", PolygonEntity.class);
    query.setParameter("polygon", wktToGeometry("POLYGON ((5 5, 5 10, 10 10, 10 5, 5 5))"));
    assertThat(query.getResultList().stream()
      .map(p -> ((PolygonEntity) p).getPolygon().toString())).containsOnly(
      "POLYGON ((0 0, 0 5, 5 5, 5 0, 0 0))", "POLYGON ((3 0, 3 5, 8 5, 8 0, 3 0))");
}

 

touches() 함수를 통해 주어진 폴리곤에 인접한(닿아 있는) 폴리곤을 찾는다.

여기서 3번째 폴리곤은 주어진 폴리곤에 닿지 않기 때문에 결화에 반환되지 않는다.

 

 

참고

https://www.baeldung.com/hibernate-spatial

복사했습니다!