1 Java Geometry 공간 기하학 데이터 처리 응용
WKT 는 벡터 기하학적 개체, 공간 참조 시스템 및 공간 참조 시스템 간의 변환을 나타내는 데 사용되는 텍스트 마크업 언어입니다. 이진 표현인 WKB(well-known binary)는 동일한 정보를 전송 및 데이터베이스에 저장하는 것보다 낫습니다. 이 형식은 OGC(Open Geospatial Consortium)에서 개발했습니다.
WKT로 표현할 수 있는 기하학적 개체에는 점, 선, 다각형, TIN( 삼각형 불규칙 네트워크 ) 및 다면체가 포함됩니다. 차원이 다른 기하학적 개체는 기하학적 컬렉션을 통해 나타낼 수 있습니다.
기하학적 개체의 좌표는 2D(x,y), 3D(x,y,z), 4D(x,y,z,m)에 선형 참조 시스템에 속하는 m 값이 될 수 있습니다.
다음은 샘플 기하학 WKT 문자열입니다.
-
포인트(6 10)
-
라인스트링(3 4,10 50,20 25)
-
폴리곤((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))
-
다중 다각형(((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 삼)))
-
GeometryCOLLECTION(POINT(4 6),LINESTRING(4 6,7 10))
-
포인트 ZM (1 1 5 60)
-
포인트엠 (1180)
-
비어있는 포인트
-
다중 다각형 비어 있음
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 세트가 필요합니다.
시중에 나와 있는 Common 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 사용에 따라 몇 가지 사항을 요약해 보겠습니다.
- 팩토리 클래스 객체는 한 번만 초기화하면 되며 구성 클래스에 배치하고 Spring 컨테이너에 주입해야 합니다.
- 프런트 엔드 또는 Excel에서 관련 좌표 데이터를 가져와 Geometry 개체를 생성합니다.
- SqlServer에 지오메트리 개체 유지
이 예에서는 Geometry 개체를 유지하기 위해 두 가지 방법이 권장됩니다.
- 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는 특수 개체와 Sql 필드 간의 매핑 관계를 실현하기 위해 사용자 정의 유형 변환기 TypeHandler를 제공합니다.
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
은 플러그인에 이 필드가 기본 키 필드이고 필드 이름이 OBJECT임을 알려주는 mybatis-plus 플러그인의 주석입니다. 기본 키 전략은 사용자 입력입니다.shape
jts용 기하학 객체(이 객체의 JSON 직렬화 결과는 매우 무섭기 때문에@JsonIgnore
데코레이션 사용)@KeySequence
개체가 사용해야 하는 기본 키 시퀀스 이름을 식별하는 데 사용되는 mybatis-plus의 플러그인이기도 합니다.IKeyGenerator
여기에서는 데이터를 삽입하기 전에 기본 키를 채우기 위해 Oracle의 시퀀스 이름을 쿼리하는 것과 유사한 one 을 구현했습니다 .
@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에 할당한다는 것입니다(오라클의 시퀀스와 유사). 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을 실행하여 기본 키를 획득하고
삽입 코드의 필드 모양은 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>
wktText는 테이블 필드가 아닌 임시 필드입니다. 여기서 부모 클래스를 정의하고 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)