HibernateSpatialの概要

1.はじめに

この記事では、Hibernate、hibernate-spatialの空間拡張について見ていきます。

バージョン5以降、HibernateSpatialは地理データを操作するための標準インターフェースを提供します

2. HibernateSpatialの背景

地理データには、ポイント、ライン、ポリゴンなどのエンティティの表現が含まれます。このようなデータ型はJDBC仕様の一部ではないため、JTS(JTS Topology Suite)は空間データ型を表すための標準になりました。

JTSとは別に、Hibernate SpatialはGeolatte-geomもサポートしています。これは、JTSでは利用できないいくつかの機能を備えた最近のライブラリです。

両方のライブラリは、hibernate-spatialプロジェクトにすでに含まれています。あるライブラリを他のライブラリよりも使用することは、データ型をどのjarからインポートするかという問題にすぎません。

Hibernate Spatialは、Oracle、MySQL、PostgreSQLql / PostGISなどのさまざまなデータベースをサポートしていますが、データベース固有の関数のサポートは統一されていません。

最新のHibernateのドキュメントを参照して、hibernateが特定のデータベースのサポートを提供する関数のリストを確認することをお勧めします。

この記事では、MySQLの全機能を維持するインメモリMariadb4jを使用します。

Mariadb4jとMySqlの構成は似ていますが、mysql-connectorライブラリでさえこれらのデータベースの両方で機能します。

3 。Mavenの依存関係

単純なHibernate-Spatialプロジェクトをセットアップするために必要なMavenの依存関係を見てみましょう。

 org.hibernate hibernate-core 5.2.12.Final   org.hibernate hibernate-spatial 5.2.12.Final   mysql mysql-connector-java 6.0.6   ch.vorburger.mariaDB4j mariaDB4j 2.2.3  

休止状態空間依存性は、空間データ型のサポートを提供するものです。hibernate-core、hibernate-spatial、mysql-connector-java、およびmariaDB4jの最新バージョンは、MavenCentralから入手できます。

4. HibernateSpatialの構成

最初のステップは、resourcesディレクトリにhibernate.propertiesを作成することです

hibernate.dialect=org.hibernate.spatial.dialect.mysql.MySQL56SpatialDialect // ...

hibernate-spatialに固有の唯一のものは、MySQL56SpatialDialect方言です。この方言は、MySQL55Dialect方言を拡張し、空間データ型に関連する追加機能を提供します。

プロパティファイルのロード、SessionFactoryの作成、およびMariadb4jインスタンスのインスタンス化に固有のコードは、標準の休止状態プロジェクトの場合と同じです。

5 ジオメトリタイプを理解する

ジオメトリは、JTSのすべての空間タイプの基本タイプです。これは、PointPolygonなどの他のタイプがGeometryから拡張されることを意味します。JavaのGeometryタイプは、MySqlのGEOMETRYタイプにも対応しています。

タイプの文字列表現を解析することにより、Geometryのインスタンスを取得します。JTSが提供するユーティリティクラスWKTReaderを使用して、既知のテキスト表現をジオメトリタイプに変換できます。

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.DBにポイントを保存する

ジオメトリタイプとは何か、文字列からポイントを取得する方法がわかったので、PointEntityを見てみましょう。

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

エンティティPointEntityには空間タイプPointが含まれていることに注意してください。前に示したように、ポイントは2つの座標で表されます。

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

メソッドinsertPoint()は、PointのWell- Known Text(WKT)表現を受け入れ、それをPointインスタンスに変換して、DBに保存します。

注意として、セッションはHibernate-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); }

呼び出しのtoString()上のポイントは、のWKT表現を返しポイント。これは、GeometryクラスがtoString()メソッドをオーバーライドし、前に見たWKTReaderの補完クラスあるWKTWriterを内部的に使用するためです。

このテストを実行すると、hibernateはPointEntityテーブルを作成します。

その表を見てみましょう:

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

予想したように、タイプフィールドポイントがあるGEOMETRY。このため、SQLエディター(MySqlワークベンチなど)を使用してデータをフェッチするときに、このGEOMETRYタイプを人間が読めるテキストに変換する必要があります。

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

However, as hibernate already returns WKT representation when we call toString() method on Geometry or any of its subclasses, we don't need to bother about this conversion.

7. Using Spatial Functions

7.1. ST_WITHIN() Example

We'll now have a look at the usage of database functions that work with spatial data types.

One of such function in MySQL is ST_WITHIN() that tells whether one Geometry is within another. A good example here would be to find out all the points within a given radius.

Let's start by looking at how to create a circle:

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

A circle is represented by a finite set of points specified by the setNumPoints() method. The radius is doubled before calling the setSize() method as we need to draw the circle around the center, in both the directions.

Let's now move forward and see how to fetch the points within a given radius:

@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 maps its within() function to the ST_WITHIN() function of MySql.

An interesting observation here is that the Point (3, 4) falls exactly on the circle. Still, the query doesn't return this point. This is because the within() function returns true only if the given Geometry is completely within another Geometry.

7.2. ST_TOUCHES() Example

Here, we'll present an example that inserts a set of Polygons in the database and select the Polygons that are adjacent to a given Polygon. Let's have a quick look at the PolygonEntity class:

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

The only thing different here from the previous PointEntity is that we're using the type Polygon instead of the Point.

Let's now move towards the test:

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

The insertPolygon() method is similar to the insertPoint() method that we saw earlier. The source contains the full implementation of this method.

touches()関数を使用して、特定のポリゴンに隣接するポリゴンを検索しています。明らかに、指定されたポリゴンに接触するエッジがないため、3番目のポリゴンは結果に返されません。

8.結論

この記事では、hibernate-spatialを使用すると、低レベルの詳細が処理されるため、空間データ型の処理が非常に簡単になることを確認しました。

この記事ではMariadb4jを使用していますが、構成を変更せずにMySqlに置き換えることができます。

いつものように、この記事の完全なソースコードはGitHubにあります。