【GIS】Hibernate-Spatial

1.简介

在本文中,我们将了解Hibernate的空间扩展,hibernate-spatial。Hibernate Spatial提供了用于处理地理数据的标准接口。

2.Hibernate Spatial的背景

地理数据包括诸如Point,Line,Polygon(多边形)等实体的表示。此类数据类型不是JDBC规范的一部分,因此JTS(JTS拓扑套件)已成为表示空间数据类型的标准。

除了JTS之外,Hibernate空间还支持Geolatte-geom,这是一个最新的库,具有一些JTS中不可用的功能。

这两个库都已包含在休眠空间项目中。使用一个库而不是另一个库仅仅是我们要从哪个jar导入数据类型的问题。

尽管Hibernate空间支持Oracle,MySQL,PostgreSQLql / PostGIS和其他一些不同的数据库,但对数据库特定功能的支持并不统一。

最好参考最新的Hibernate文档,以检查hibernate为给定数据库提供支持的功能列表。

在本文中,我们将使用内存中的Mariadb4j –它维护MySQL的全部功能。

Mariadb4j和MySql的配置相似,即使mysql-connector库也适用于这两个数据库。

3. Maven依赖

<dependency>
    <groupId>org.hibernategroupId>
    <artifactId>hibernate-entitymanagerartifactId>
    <version>5.2.12.Finalversion>
dependency>
<dependency>
    <groupId>org.hibernategroupId>
    <artifactId>hibernate-spatialartifactId>
    <version>5.2.12.Finalversion>
dependency>
<dependency>
    <groupId>mysqlgroupId>
    <artifactId>mysql-connector-javaartifactId>
    <version>6.0.6version>
dependency>
<dependency>
    <groupId>ch.vorburger.mariaDB4jgroupId>
    <artifactId>mariaDB4jartifactId>
    <version>2.2.3version>
dependency>

4.配置

新建一个hibernate.properties文件

hibernate.dialect=org.hibernate.spatial.dialect.postgis.PostgisDialect

5.了解Geometry类型

Geometry是JTS中所有空间类型的基本类型。这意味着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()方法为Geometry,实际的实例还是Point的实例。

6.在数据库中存储一个Point

现在,我们对什么是Geometry类型以及如何从String中获取Point有了一个很好的了解,让我们看一下PointEntity:

@Entity
public class PointEntity {
 
    @Id
    @GeneratedValue
    private Long id;
 
    private Point point;
 
    // standard getters and setters
}

请注意,实体PointEntity包含空间类型Point。如前所述,一个点由两个坐标表示:

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

方法insertPoint()接受Point的熟知文本(WKT)表示形式,将其转换为Point实例,然后保存在DB中。

提醒一下,该会话不是特定于hibernamte-spatial的,而是以类似于另一个hibernate项目的方式创建的。

我们可以在这里注意到,一旦创建了Point的实例,存储PointEntity的过程便与任何常规实体相似。
让我们看一些测试:

@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);
}

在一个Point上调用的toString()返回的WKT表示此Point。这是因为Geometry类覆盖的toString()方法和内部使用WKTWriter,免费类WKTReader我们前面看到的。

一旦运行此测试,hibernate将为我们创建PointEntity表。

让我们看一下该表:

desc PointEntity;
Field    Type          Null    Key
id       bigint(20)    NO      PRI
point    geometry      YES

正如预期的那样,该Point的类型是Geometry。因此,在使用我们的SQL编辑器(例如MySql工作台)获取数据时,我们需要将此GEOMETRY类型转换为人类可读的文本:

select id, astext(point) from PointEntity;
 
id      astext(point)
1       POINT(2 4)

不过,当我们在Geometry或其任何子类上调用toString()方法时,hibernate已经返回了WKT表示形式,因此我们无需为这种转换而烦恼。

7.使用空间功能

7.1 ST_WITHIN()示例

现在,我们将研究与空间数据类型一起使用的数据库功能的用法。

MySQL中的此类函数之一就是ST_WITHIN(),它告诉一个Geometry是否在另一个Geometry中。一个很好的例子是找出给定半径内的所有点。

让我们从如何创建一个圆开始:

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()方法之前,半径已加倍,因为我们需要在两个方向上围绕中心绘制圆。

现在,让我们继续前进,看看如何获​​取给定半径内的点:

@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()函数对应ST_WITHIN()的MySQL的功能。

有趣的观察是,点(3,4)恰好落在圆上。不过,查询不会返回这一点。这是因为只有当给定的几何形状完全是另一几何内within()函数才会返回true。

7.2.ST_TOUCHES()示例

这里,我们将呈现一个示例,插入一组多边形在数据库中,然后Select一个多边形与给定的多边形相邻。让我们快速看一下PolygonEntity类:

@Entity
public class PolygonEntity {
 
    @Id
    @GeneratedValue
    private Long id;
 
    private Polygon polygon;
 
    // standard getters and setters
}

与以前的PointEntity唯一不同的是,我们使用的是Polygon类型而不是Point。

现在让我们进行测试:

@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))");
}

该insertPolygon()方法类似于在insertPoint()方法,我们前面看到的。源包含此方法的完整实现。

我们使用的touches()函数来找到多边形小号相邻给定的多边形。显然,第三个多边形没有返回结果,因为没有边缘接触所给的多边形。

8.结论

在本文中,我们已经看到,休眠空间使处理空间数据类型变得更加简单,因为它处理了底层细节。

即使本文使用Mariadb4j,我们也可以将其替换为MySql,而无需修改任何配置。

与往常一样,可以在GitHub上找到本文的完整源代码。

你可能感兴趣的:(gis)