The Application of Java Geometry Spatial Geometry Data Processing

1 Processing application of Java Geometry spatial geometric data

WKT is a text markup language used to represent vector geometric objects, spatial reference systems, and conversion between spatial reference systems. Its binary representation, WKB (well-known binary), is better than storing the same information in transmission and in the database. The format was developed by the Open Geospatial Consortium (OGC).

Geometric objects that can be represented by WKT include: points, lines, polygons, TIN ( triangular irregular network ) and polyhedrons. Geometric objects of different dimensions can be represented by means of geometric collections.
The coordinates of geometric objects can be 2D(x,y), 3D(x,y,z), 4D(x,y,z,m), plus an m value belonging to the linear reference system.
The following is a sample geometry WKT string:

  • POINT(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))

  • MULTIPOINT(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))

  • POINT ZM (1 1 5 60)

  • POINT M (1 1 80)

  • POINT EMPTY

  • MULTIPOLYGON EMPTY

2 Insert data into the spatial database

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

That is to say, by converting coordinates into WKT text, we can insert spatial data. The next thing we have to consider is how to generate WKT text.

3 Using Java to create Geometry objects

3.1 Common Geometry Java API

The wkt text is just a string, wouldn't it be enough to directly splice the coordinate points into a string conforming to the WKT format?
The truth is this truth, but it is difficult to do it well.

  • Splicing workload is huge
  • The splicing process is error-prone
  • The splicing results may not be legal and usable.
    We need a set of JAVA API to process the data, which can easily create Geometry objects, and perform functions such as drawing, creating, and verifying geographic information.

Common GeometryApi on the market are

Esri is the official javaSDK provided by Arcgis. Unfortunately, it does not have many functions and cannot even provide basic spatial computing functions.
jts has more complete functions and more information

3.2 Some API usage methods of JTS

    @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 Subclass of Geometry data type in JTS

img

4 Use JAVA to add data to the spatial database

According to the use of Api in the above test class, let us summarize a few points

  • The factory class object only needs to be initialized once, and should be placed in the configuration class and injected into the Spring container
  • Import relevant coordinate data from the front end or Excel to generate a Geometry object
  • Persist Geometry object to SqlServer

In this example, two methods are recommended to persist the Geometry object:

  1. Get the WKT text of the Geometry object, and then use the function provided by SqlServer geometry :: STGeomFromText ()to store the WKT text as the Geometry type of the database
  2. Convert the Geometry object in the jts package to the Geometry object in the SqlServer JDBC package, and persist the Geometry object to the database in binary form

Environment:
This example code is based on JTS, SpringBoot, Mybatis-Plus, mssql-jdbc environment

5 Inserting Geometry data using TypeHandlermapped custom object fields

5.1 Custom TypeHandler

When we use the Mybatis framework, Mybatis provides a custom type converter TypeHandler to realize the mapping relationship between special objects and Sql fields

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 Entity objects

The entity objects are as follows:

  • objectidInteger type, non-autoincrement (this field is maintained by Arcgis and cannot be modified) @TableIdis the annotation of the mybatis-plus plug-in, telling the plug-in that this field is the primary key field, the field name is OBJECT, and the primary key strategy is user input
  • shapeGeometry object for jts (the JSON serialization result of this object is very scary, so use @JsonIgnoredecoration)
  • @KeySequenceIt is also a plug-in of mybatis-plus, which is used to identify the primary key sequence name that the object needs to use. Here I implemented one IKeyGenerator, which is similar to querying Oracle's sequence name to fill the primary key before inserting data.
@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 Custom primary key generation strategy

In arcgis, the primary key field in the spatial table is int, and it is not self-incrementing and cannot be modified. Arcgis will have some errors when it is modified to self-increment. Therefore, inserting spatial data in the java background needs to complete the query generation of the primary key by itself.
IKeyGeneratorIt is the interface provided by Mybatis-Plus. The function of this implementation is that when the primary key generation strategy is specified, the mp framework will call this implementation before adding new data, and assign the result to the ID of the object (similar to Oracle's sequence). Note that this class needs to be injected into the Spring
container middle

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 Geometry Object Persistence

When we call the method provided by mybatis-plus to persist the object

 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("持久化成功");

The execution log is as follows:
Before the data is inserted, SqlServerKeyGeneratorthe sql in the middle is executed to obtain the primary key.
The field shape in the insertion code is the binary of the Geometry object.

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 handwritten xml insert Geometry data

Use the function provided by SqlServer geometry :: STGeomFromText( #{wktText},4326)to convert Geometry into WKT text and insert it

    <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. I define a parent class here. All spatial table entities that contain Geometry inherit this class for processing wkt text.

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 Pit Records

7.1 jts is not compatible with wkt recognized by sqlserver

[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)

Guess you like

Origin blog.csdn.net/An1090239782/article/details/123509504