美文网首页开源工作生活
Java Geometry空间几何数据的处理应用

Java Geometry空间几何数据的处理应用

作者: KICHUN | 来源:发表于2019-07-03 17:32 被阅读318次

先了解几个基本概念,有助于了解本文应用场景

地理信息系统(Geographic Information System或 Geo-Information system,GIS)有时又称为“地学信息系统”。它是一种特定的十分重要的空间信息系统。它是在计算机硬、软件系统支持下,对整个或部分地球表层(包括大气层)空间中的有关地理分布数据进行采集储存管理运算分析显示描述的技术系统

  • ArcGIS平台

ArcGIS产品线为用户提供一个可伸缩的,全面的GIS平台。ArcObjects包含了许多的可编程组件,从细粒度的对象(例如单个的几何对象)到粗粒度的对象(例如与现有ArcMap文档交互的地图对象)涉及面极广,这些对象为开发者集成了全面的GIS功能。

  • Geometry数据类型

Geometry是一种空间几何数据类型,常用于描述空间几何信息,例如坐标点、线、面、三维信息等。
也就是说,GIS一般使用Geometry数据类型来存储及展示地理信息。
常见的支持Geometry的数据库有Oracle、SqlServer、Mysql、PostgreSQL
Sql-Server中支持的Geometry文档以及Sql-Server中可选择的地理数据类型如图

  • 应用场景示例

    如图,该应用主要为使用ArcGIS做地理信息系统,使用SqlServer作为空间数据库,使用Java搭建后台服务处理数据,使用JS的Vue结合ArcGIS的API做前端的渲染,成功的将地理信息高亮标识在地图上

本文关注点:使用java对Geometry数据进行处理,附带对arcgis系统一些简单使用说明 简单描述一个gis应用系统的处理流程


1. 使用ArcGIS创建地理数据库,发布GIS服务

数据库和 ArcGIS Enterprise
如何注册数据库到 ArcGIS Server 站点
发布地图服务
通过以上的文档,我们可以创建一个地理数据库,并将数据库通过arcgis进行连接,发布为一个Restful服务


访问该接口,如图


image.png

那这个接口提供了那些功能呢?
支持的操作大概如下:
Supported Operations: Query Apply Edits Add Features Update Features Delete Features Calculate Validate SQL Generate Renderer Return Updates Iteminfo Thumbnail Metadata

由ArcGIS提供的这些服务,我们可以以可视化的方式,对空间地理信息进行查询及展示
后面我们将做进一步的说明


2. 空间数据以及WKT熟知文本

好的,我们有了数据库,也有了arcgis提供的服务,现在该如何新增空间数据呢?
先通过WKT了解下空间数据Geometry大概长什么样

WKT,是一种文本标记语言,用于表示矢量几何对象、空间参照系统及空间参照系统之间的转换。它的二进制表示方式,亦即WKB(well-known binary)则胜于在传输和在数据库中存储相同的信息。该格式由开放地理空间联盟(OGC)制定。

WKT可以表示的几何对象包括:点,线,多边形,TIN(不规则三角网)及多面体。可以通过几何集合的方式来表示不同维度的几何对象。
几何物体的坐标可以是2D(x,y),3D(x,y,z),4D(x,y,z,m),加上一个属于线性参照系统的m值。
以下为几何WKT字串样例:
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

到这里我们清楚了,所谓空间数据就是一个或一组坐标,这个坐标或坐标组有类型(POINT点LINESTRING线POLYGON面),通过这些坐标,GIS系统可以完整地定位查询绘制这些坐标信息


3. 向空间数据库插入数据

那数据库如何新增这些空间数据呢?
先了解一下SQLSERVER空间数据的插入语句长啥样

--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文本


4. 使用Java创建Geometry对象

wkt文本仅仅是一个字符串而已,直接将坐标点拼接成符合WKT格式的字符串不就可以了吗?
道理是这个道理,要做好可就难了。

  • 拼接工作量巨大
  • 拼接过程容易出错
  • 拼接的结果不一定合法可用
    我们需要一套JAVA API对数据进行处理,能够方便的创建Geometry对象,进行地理信息的绘制、创建、验证等等功能

市面上常见的GeometryApi有

Esri/geometry-api-java

locationtech/jts

点击上面链接可以访问其Github地址,本文将以jts为例进行说明


以下为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);
    }

JTS中Geometry数据类型的子类


image.png

根据github中相关文档介绍,我们已经可以在项目中引入相关坐标,并创建Geometry对象,构造WKT文本了


5. 使用JAVA向空间数据库新增数据

环境说明:本案例使用Spring+Mybatis实现
根据上面测试类中Api的使用,让我们总结几个要点

  • 工厂类对象只需初始化一次,应放在配置类注入到Spring容器中
  • 由前端或Excel导入相关坐标数据,生成Geometry对象
  • 在保存数据对象生成sql时,将Geometry字段的值转化为geometry :: STGeomFromText ()的格式

6. 使用TypeHandler处理sql?

聪明的同学想到了,geometry :: STGeomFromText ()需要一个wkt文本,而Geometry对象有了,WKTWriter可以将对象转为wkt文本。
我们需要一个类型转换器,可以将实体对象中的地理信息对象转换成数据库需要的sql

Mybatis官方提供了BaseTypeHandler 它提供数据库类型和java类型属性之间的转化问题,例如将某个对象中JAVA对象Integer转成数据库的int类型

我曾经编写了如下的GeometryTypeHandler将Geometry对象插入时转成geometry :: STGeomFromText ()
当从数据库读取Geometry时将二进制数据转成Geometry对象

/**
 * @author wangqichang
 * @since 2019/4/9
 */
@Slf4j
@MappedTypes(value = {Geometry.class})
public class GeometryTypeHandler extends BaseTypeHandler<Geometry> {
    String geometryPrefix = "geometry :: STGeomFromText ( ";

    @Override
    public void setNonNullParameter(PreparedStatement preparedStatement, int i, Geometry geometry, JdbcType jdbcType) throws SQLException {
//        /**
//         *  这个是将数据库几何图形数据类型geometry 转换成java Geometry 处理器中设置参数方法
//         *  参数的格式要调用sql函数geometry :: STGeomFromText ()进行参数转换
//         *  可是PreparedStatement setString 后变成 'geometry :: STGeomFromText ()' 函数变字符串了
//         */
//
//        String geometryStr = geometry.toString();
//        StringBuilder strParam = new StringBuilder(geometryPrefix);
//        strParam.append(geometryStr).append(",").append(geometry.getSRID()).append(")");
//        log.info(strParam.toString());
//        preparedStatement.setString(i,strParam.toString());
    }

    @Override
    public Geometry getNullableResult(ResultSet resultSet, String s) throws SQLException {
//        try {
//            WKBReader wkbReader = SpringContextUtil.getBean(WKBReader.class);
//            Geometry geometry = wkbReader.read(resultSet.getBytes(s));
//            return geometry;
//        } catch (Exception e) {
//            log.error(e.getMessage());
//            throw new ServiceException(e.getMessage());
//        }
        return null;
    }

    @Override
    public Geometry getNullableResult(ResultSet resultSet, int i) throws SQLException {
        return null;
    }

    @Override
    public Geometry getNullableResult(CallableStatement callableStatement, int i) throws SQLException {
        return null;
    }
}

注意:看代码中的注释,我最后生成的sql 为'geometry :: STGeomFromText ()',带有单引号,这句sql是一个字符串,并不是sqlserver可以解析的函数。

原因:深入了解Mybatis,你会发现TypeHandler是针对PreparedStatement参数化的转化器,这个是预编译的SQL先生成?的占位符,任何实体属性值转化后都是被引号包裹,变成一个替换值,不能成为可执行的sql语句。正因如此,PreparedStatement有防止sql注入的优点


7. 手写xml插入Geometry数据

通过Mybatis文档发现,Mybatis支持的Statement有三种STATEMENT, PREPARED, CALLABLE预编译就是PREPARED,xml中会根据使用占位符#$不同使用不同Statement,如果想在sql中执行函数,只能让mybatis指定当前查询为STATEMENT类型,编写xml即可实现

    <insert id="insertCorridorBySql" parameterType="com.zh.xxx.entity.xxx" useGeneratedKeys="true"
            keyProperty="objectid">
        INSERT INTO [LINE_CORRIDOR] (
        <include refid="other_coloum"/>
        )
        values (
        #{lineName},
        #{lineId},
        #{corridorLayingtype},
        #{corridorWorkdate},
        #{corridorDepartment},
        #{corridorStartWellId},
        #{corridorEndWellId},
        geometry :: STGeomFromText( #{wktText},4326),
        #{corridorName}
        )
    </insert>

注意,wktText是一个非表字段的临时字段,我在此定义了一个父类,所有包含Geometry的空间表实体均继承此类,用于处理wkt文本

import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.annotation.TableField;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Data;
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文本进行替换
 *
 * @author wangqichang
 * @since 2019/7/1
 */
@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";

    @TableField(exist = false)
    @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;
    }
}


8. 总结

  1. 使用相关工具包,我们可以方便的构建空间数据对象,插入空间数据库
  2. 一般java后台只需要做数据插入,空间数据的查询由GIS服务API提供,所以本文并没有对空间数据字段进行读取。甚至生成的实体也不需要Geometry字段(代码中Geometry只是个临时字段标识了@TableField(exist = false)
  3. java后台还需要对空间表中不是空间字段的信息做维护
  4. 效果图参考使用场景图。当然这个效果依赖了ArcGIS的JS API,本文不对此进行展开

采坑记录:

  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)

相关文章

  • Java Geometry空间几何数据的处理应用

    先了解几个基本概念,有助于了解本文应用场景 地理信息系统GIS-百度百科 地理信息系统(Geographic In...

  • esri空间大数据解决之道之Geometry API for J

    Esri Geometry API for Java可用于在第三方数据处理解决方案中实现空间数据处理。基于MapR...

  • 辛几何

    What? 辛几何是华罗庚对symplectic geometry的翻译。 symplectic geometry...

  • 数学专业核心课程详细介绍

    具体介绍一下数学与应用数学的核心课程 1、课程名称:解析几何 Analytic Geometry 内容简介:《解析...

  • postgis几何函数汇总

    POSTGIS包含大量几何函数,方便我们处理空间数据,本文节选自官方文档,方便查找 5.3 几何构造 ST_Col...

  • JTS

    常用的Geometry数据结构 GeometryFactory GeometryFactory是geometry的...

  • GeoSpark->ST_PoinFromText

    读取csv格式的数据,将对对应文本经纬度转成geometry类型的数据进行处理 数据类型为CSV(带有表头信息) ...

  • Openlayers API-Geometry

    Geometry是用于创建几何体,几何体类型包括Point, LineString, LinearRing, Po...

  • Geometry在数据库和Java中的应用

    mysql存储字段类型 : GeometryGeometry是几何对象的基类, 也就是说Point, LineSt...

  • 数学基础资源

    高数-线性代数-矩阵 任意两空间直角坐标系的转换的数学模型和算法实现 计算几何源代码汇集 geometry++

网友评论

    本文标题:Java Geometry空间几何数据的处理应用

    本文链接:https://www.haomeiwen.com/subject/ogcncctx.html