1 Java Geometry 空間幾何データの加工アプリケーション
WKTは、ベクトル ジオメトリック オブジェクト、空間参照系、および空間参照系間の変換を表すために使用されるテキスト マークアップ言語です。そのバイナリ表現である WKB (well-known binary) は、同じ情報を送信とデータベースに格納するよりも優れています。この形式は、Open Geospatial Consortium (OGC) によって開発されました。
WKT で表現できる幾何学的オブジェクトには、点、線、多角形、TIN (不規則三角網)、および多面体が含まれます。さまざまな次元のジオメトリ オブジェクトは、ジオメトリ コレクションによって表すことができます。
ジオメトリック オブジェクトの座標は、2D(x,y)、3D(x,y,z)、4D(x,y,z,m)、および線形参照系に属する m 値です。
以下は、ジオメトリ WKT 文字列のサンプルです。
-
ポイント(6 10)
-
LINESTRING(3 4,10 50,20 25)
-
POLYGON((1 1,5 1,5 5,1 5,1 1),(2 2,2 3,3 3,3 2,2 2))
-
マルチポイント(3.5 5.6, 4.8 10.5)
-
MULTILINESTRING((3 4,10 50,20 25),(-5 -8,-10 -8,-15 -4))
-
MULTIPOLYGON(((1 1,5 1,5 5,1 5,1 1),(2 2,2 3,3 3,3 2,2 2)),(((6 3,9 2,9 4,6 3)))
-
GEOMETRYCOLLECTION(POINT(4 6),LINESTRING(4 6,7 10))
-
ポイント ZM (1 1 5 60)
-
ポイント M (1 1 80)
-
ポイントが空です
-
マルチポリゴン空
2 データを空間データベースに挿入する
--GEOM是类型为Geometry的字段--
--我们向该字段新增了一条3D的多边形数据--
--geometry :: STGeomFromText () 是由SQLSERVER提供的函数,它能将WKT文本转换为数据库geometry类型的数据--
INSERT INTO [dbo].[TEST_GEO_TABLE] ( [GEOM] )
VALUES
( geometry :: STGeomFromText (
'POLYGON ((
113.507259000000005 22.24814946 8,
113.507188600000006 22.248088559999999 9,
113.507117399999998 22.24802743 10,
113.507046099999997 22.24796624 11,
113.507017300000001 22.247888209999999 12
))',4326 )
);
つまり、座標を WKT テキストに変換することで、空間データを挿入できます。次に考慮しなければならないことは、WKT テキストを生成する方法です。
3 Java を使用して Geometry オブジェクトを作成する
3.1 共通ジオメトリ Java API
wkt テキストは単なる文字列です。座標点を WKT 形式に準拠した文字列に直接接合するだけで十分ではないでしょうか?
真実はこの真実ですが、それをうまく行うのは難しいです。
- スプライシングの作業負荷が大きい
- スプライシング プロセスはエラーが発生しやすい
- スプライシングの結果は合法的で使用できない可能性があります.
データを処理するには、Geometry オブジェクトを簡単に作成し、地理情報の描画、作成、検証などの機能を実行できる Java API のセットが必要です。
市場に出回っている一般的な GeometryApi は次のとおりです。
-
locationtech / jts (推奨)
Esri は Arcgis が提供する公式の javaSDK ですが、残念ながら機能が多くなく、基本的な空間計算機能も提供できません。
jts には、より完全な機能とより多くの情報があります。
3.2 JTSのAPI利用方法の一部
@Test
public void geoTest() throws ParseException {
/**
* GeometryFactory工厂,参数一:数据精度 参数二空间参考系SRID
*/
GeometryFactory geometryFactory = new GeometryFactory(new PrecisionModel(PrecisionModel.FLOATING), 4326);
/**
* 熟知文本WKT阅读器,可以将WKT文本转换为Geometry对象
*/
WKTReader wktReader = new WKTReader(geometryFactory);
/**
* Geometry对象,包含Point、LineString、Polygon等子类
*/
Geometry geometry = wktReader.read("POINT (113.53896635 22.36429837)");
/**
* 将二进制流的形式读取Geometry对象
*/
WKBReader wkbReader = new WKBReader(geometryFactory);
/**
* 单纯的一个坐标点,单点可以创建Point,多点可以创建LineString、Polygon等
*/
Coordinate coordinate = new Coordinate(1.00, 2.00);
Point point = geometryFactory.createPoint(coordinate);
Polygon polygon = geometryFactory.createPolygon(new Coordinate[]{
new Coordinate(1, 2),
new Coordinate(1, 2),
new Coordinate(1, 2),
new Coordinate(1, 2),
new Coordinate(1, 2),
});
Geometry geometry1 = point;
Geometry geometry2 = polygon;
/**
* WKT输出器,将Geometry对象写出为WKT文本
*/
WKTWriter wktWriter = new WKTWriter();
String write = wktWriter.write(point);
}
3.3 JTS の Geometry データ型のサブクラス
4 JAVA を使用して空間データベースにデータを追加する
上記のテストクラスでのApiの使用に従って、いくつかのポイントを要約しましょう
- ファクトリ クラス オブジェクトは 1 回だけ初期化する必要があり、構成クラスに配置して Spring コンテナーに注入する必要があります。
- フロント エンドまたは Excel から関連する座標データをインポートして Geometry オブジェクトを生成する
- Geometry オブジェクトを SqlServer に永続化する
この例では、Geometry オブジェクトを永続化するために 2 つの方法が推奨されています。
- Geometry オブジェクトの WKT テキストを取得し、SqlServer が提供する関数を使用して、
geometry :: STGeomFromText ()
WKT テキストをデータベースの Geometry タイプとして格納します。 - jts パッケージの Geometry オブジェクトを SqlServer JDBC パッケージの Geometry オブジェクトに変換し、Geometry オブジェクトをバイナリ形式でデータベースに永続化します。
環境:
このサンプル コードは、JTS、SpringBoot、Mybatis-Plus、mssql-jdbc 環境に基づいています。
TypeHandler
5マッピングされたカスタム オブジェクト フィールドを使用したジオメトリ データの挿入
5.1 カスタム TypeHandler
Mybatis フレームワークを使用する場合、Mybatis はカスタム型コンバーター TypeHandler を提供して、特別なオブジェクトと Sql フィールド間のマッピング関係を実現します。
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.MappedTypes;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.io.WKTReader;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
/**
* @author wangqichang
* @since 2019/8/28
*/
@Slf4j
@MappedTypes(value = {
Geometry.class})
public class GeometryTypeHandler extends BaseTypeHandler<Geometry> {
@Override
public void setNonNullParameter(PreparedStatement preparedStatement, int i, Geometry geometry, JdbcType jdbcType) throws SQLException {
/**
* 获取jts包对象的wkt文本,再转换成sqlserver的Geometry对象
* 调用ps的setBytes()方法,以二进制持久化该geometry对象
*/
com.microsoft.sqlserver.jdbc.Geometry geo = com.microsoft.sqlserver.jdbc.Geometry.STGeomFromText(geometry.toText(), geometry.getSRID());
preparedStatement.setBytes(i, geo.STAsBinary());
}
@Override
public Geometry getNullableResult(ResultSet resultSet, String s) {
try {
/**
* 从ResultSet中读取二进制转换为SqlServer的Geometry对象
* 使用jts的WKTReader将wkt文本转成jts的Geometryd对象
*/
com.microsoft.sqlserver.jdbc.Geometry geometry1 = com.microsoft.sqlserver.jdbc.Geometry.STGeomFromWKB(resultSet.getBytes(s));
String s1 = geometry1.toString();
WKTReader wktReader = SpringContextUtil.getBean(WKTReader.class);
Geometry read = wktReader.read(s1);
return read;
} catch (Exception e) {
log.error(e.getMessage());
throw new ServiceException(e.getMessage());
}
}
@Override
public Geometry getNullableResult(ResultSet resultSet, int i) throws SQLException {
return null;
}
@Override
public Geometry getNullableResult(CallableStatement callableStatement, int i) throws SQLException {
return null;
}
}
5.2 エンティティ オブジェクト
エンティティ オブジェクトは次のとおりです。
objectid
整数型、非増分 (このフィールドは Arcgis によって維持され、変更できません) は、@TableId
mybatis-plus プラグインの注釈であり、このフィールドが主キー フィールドであり、フィールド名が OBJECT であり、主キーが戦略はユーザーインプットshape
jts の Geometry オブジェクト (このオブジェクトの JSON シリアライズ結果は非常に怖いので、@JsonIgnore
装飾を使用してください)@KeySequence
これは mybatis-plus のプラグインでもあり、オブジェクトが使用する必要があるプライマリ キー シーケンス名を識別するために使用されます。ここで実装したのはIKeyGenerator
、データを挿入する前に Oracle のシーケンス名をクエリして主キーを埋めるのと似ています。
@Data
@TableName("LINE_WELL")
@KeySequence(value = "LINE_WELL",clazz = Integer.class)
public class Well extends MyGeometry implements Serializable {
@TableId(value = "OBJECTID", type = IdType.INPUT)
private Integer objectid;
@JsonIgnore
protected Geometry shape;
}
5.3 カスタム主キー生成戦略
arcgis では、空間テーブルの主キー フィールドは int であり、自己インクリメントではなく、変更できません。Arcgis を自己インクリメントに変更すると、いくつかのエラーが発生します。したがって、Java バックグラウンドで空間データを挿入するには、主キーのクエリ生成をそれ自体で完了する必要があります。
IKeyGenerator
Mybatis-Plus が提供するインターフェースです。この実装の機能は、主キー生成戦略が指定されている場合、新しいデータを追加する前に mp フレームワークがこの実装を呼び出し、結果をオブジェクトの ID に割り当てることです (Oracle のシーケンスと同様)。このクラスは必要であることに注意してください。 Spring
コンテナの真ん中に注入する
import com.baomidou.mybatisplus.core.incrementer.IKeyGenerator;
/**
* @author wangqichang
* @since 2019/8/30
*/
public class SqlServerKeyGenerator implements IKeyGenerator {
@Override
public String executeSql(String incrementerName) {
return "select max(OBJECTID)+1 from " + incrementerName;
}
}
5.4 ジオメトリ オブジェクトの永続性
オブジェクトを永続化するために mybatis-plus が提供するメソッドを呼び出すと、
String str = "POLYGON ((113.52048666400003 22.248443089000034, 113.5206744190001 22.24822462700007, 113.52082998700007 22.248343788000057, 113.52060468200011 22.248547355000028, 113.52048666400003 22.248443089000034))";
Geometry read = null;
try {
/**
* 这里使用wkt文本生成了一个jts包下的Geometry对象
*/
read = SpringContextUtil.getBean(WKTReader.class).read(str);
} catch (ParseException e) {
e.printStackTrace();
}
Well well = new Well();
well.setShape(read);
//这里是Mybatis-Plus提供的save接口,调用其内部实现直接储存对象
wellService.save(well);
System.out.println("持久化成功");
実行ログは以下の通りです.
データ挿入前にSqlServerKeyGenerator
途中のsqlを実行して主キーを取得.
挿入コードのフィールド shape は Geometry オブジェクトのバイナリです.
2019-08-30 15:54:23.541 INFO 8484 --- [nio-8905-exec-1] jdbc.sqltiming : SELECT max(OBJECTID) + 1 FROM LINE_WELL
{
executed in 4 msec}
2019-08-30 15:54:23.631 INFO 8484 --- [nio-8905-exec-1] jdbc.sqltiming : INSERT INTO LINE_WELL (OBJECTID, shape) VALUES (3, '<byte[]>')
{
executed in 17 msec}
6 手書きの xml 挿入ジオメトリ データ
SqlServer が提供する関数を使用してgeometry :: STGeomFromText( #{wktText},4326)
Geometry を WKT テキストに変換し、挿入します
<insert id="insertCorridorBySql" parameterType="com.zh.xxx.entity.xxx" useGeneratedKeys="true"
keyProperty="objectid">
INSERT INTO [LINE_CORRIDOR] (
shape
)
values (
geometry :: STGeomFromText( #{wktText},4326)
)
</insert>
Note that wktText is a temporary field that is not a table field. ここで親クラスを定義し、Geometry を含むすべての空間テーブル エンティティは wkt テキストを処理するためにこのクラスを継承します。
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.io.WKTWriter;
import java.io.Serializable;
/**
* 针对Geometry获取Wkt文本字段做处理的Geometry父类,getWktText替代getText,输出三维wkt文本
* 针对sql_server无法识别POLYGON Z 语法,对wkt文本进行替换
*/
@Data
public class MyGeometry implements Serializable {
/**
* 三维wkt输出,默认为2D不带Z
*/
@TableField(exist = false)
@JsonIgnore
private WKTWriter wktWriter = new WKTWriter(3);
/**
* sql_server 与 jts wkt不兼容问题
*/
@TableField(exist = false)
@JsonIgnore
private static final String THREE_D_PRIFIX = "POLYGON Z";
@TableField(exist = false)
@JsonIgnore
private static final String TWO_D_PRIFIX = "POLYGON";
@JsonIgnore
protected Geometry shape;
@TableField(exist = false)
@JsonIgnore
private String wktText;
public String getWktText() {
if (StrUtil.isBlank(wktText)){
if (getShape() != null) {
String wkt = wktWriter.write(shape);
if (wkt.startsWith(THREE_D_PRIFIX)) {
wktText = StrUtil.replace(wkt, THREE_D_PRIFIX, TWO_D_PRIFIX);
} else {
wktText = wkt;
}
}
}
return wktText;
}
}
7 ピットレコード
7.1 jts は、sqlserver によって認識される wkt と互換性がありません
[2019-07-01 16:40:20,637] [ERROR] [http-nio-8905-exec-5] jdbc.audit 111 7. PreparedStatement.execute() INSERT INTO [zhundergroundcableline].[dbo].[LINE_CORRIDOR] ( [Shape] ) values ( geometry :: STGeomFromText( 'POLYGON Z((113.5079365 22.24850034
0, 113.5078521 22.24845659 0, 113.5077674 22.24841271 0, 113.5076826 22.24836872 0, 113.5075978 22.24832498 0))',4326) )
com.microsoft.sqlserver.jdbc.SQLServerException: 在执行用户定义例程或聚合“geometry”期间出现 .NET Framework 错误:
System.FormatException: 24142: 在位置 8 处应为 "(",但输入中实际为 "Z"。
System.FormatException:
在 Microsoft.SqlServer.Types.WellKnownTextReader.RecognizeToken(Char token)
在 Microsoft.SqlServer.Types.SqlGeometry.GeometryFromText(OpenGisType type, SqlChars text, Int32 srid)