3 Star 24 Fork 17

Lonelyleaf/postgis-java-demo

Create your Gitee Account
Explore and code with more than 13.5 million developers,Free private repositories !:)
Sign up
文件
This repository doesn't specify license. Please pay attention to the specific project description and its upstream code dependency when using it.
Clone or Download
contribute
Sync branch
Cancel
Notice: Creating folder will generate an empty file .keep, because not support in Git
Loading...
README

在java中使用postgis操作地理位置数据

最近要做gps数据的分析,开始学习postgis。首先推荐下不睡觉的怪叔叔专栏, 里面的postgis教程可以说很全面了,我就是靠这入的坑。

但在java程序中,对gis数据进行操作还有些问题,这里简单用文章记录下。

demo源码在https://gitee.com/Lonelyleaf/postgis-java-demo,下面简单说明下整个过程与遇到的坑

1.环境说明

  • jdk11
  • gradle6
  • postgres11+postgis
  • idea 2019.3

2.预备工作

首先需要安装postgres与postgis,在windows下,安装postgres时可以顺带安装postgis,记得勾选

img

安装好后,使用pgadmin来新建一个数据库,这里就叫做gis-test

img img

然后选中你的数据库,选择上方的Tools -> Query Tool打开查询编辑器。

img

输入以下sql来启用[PostGIS]

create extension postgis

数据库就准备好了!

3.准备数据

如果直接使用我的源码,那么启动项目会自动建立表结构与初始数据。这里还是说明下表结构与数据:

-- 创建gps数据表
CREATE TABLE "t_gps"
(
    "time"           timestamptz(3)         NOT NULL,
    "dev_id"         varchar(36)            NOT NULL,
    "location"       GEOGRAPHY(Point, 4326) NOT NULL,
    "gps_num"        int4,
    "gps_type"       varchar(10)            NOT NULL,
    "azimuth"        float4,
    "gnd_rate"       float4
) WITHOUT OIDS;

COMMENT ON COLUMN "t_gps"."time" IS '时间';
COMMENT ON COLUMN "t_gps"."dev_id" IS '设备ID';
COMMENT ON COLUMN "t_gps"."gps_num" IS '卫星定位数';
COMMENT ON COLUMN "t_gps"."gps_type" IS 'GPS定位信息';
COMMENT ON COLUMN "t_gps"."azimuth" IS '对地真北航向角';

这里建立一个gps数据表,其中location字段使用的GEOGRAPHY来保存坐标信息。 其中Point表示是点信息,postgis还支持LinestringPolygon等。 后面的4326表示了[SRID],表明了使用的哪种坐标系,这里是使用的WGS84,既gps的 标准坐标。

然后在这里 下载样本数据的sql,放入数据库中执行。

执行成功后选中数据库,然后Schemas->Tables在t_gps表右键View/Edit Data->All Rows就能看到刚才导入的数据了,

img

然后可以看到下面的Data Output选项卡中,我们的location字段右边有一个👁一样的图标,点击后,可以简单的在地图上 预览到刚才我们导入的数据

img img

要专门分析数据,可以用qgisarcgis这些专业的gis软件,这里我们已经初步达成目的,下面说明下java后端程序中,怎样 读写postgis数据。

最后给location建立索引,注意要使用[gist索引]来创建。

create index idx_gpt_location on t_gps using gist("location");

postgis的文档上对[gist索引]有介绍,时专门针对空间数据的一种索引,在

4.java后端工程

具体项目请参考我的项目源码, 基本是按照spring boot的curd工程来搭建。由于用到很多其它技术具体搭建过程这里不细说, 下面简单说明下读写数据的过程和一些痛点。

4.1 java表实体

首先项目使用的是mybatis-plus来做crud,下面是t_gps表的java实体:

@Data
@TableName("t_gps")
public class GpsEntity {
    @ApiModelProperty("时间")
    private Date time;
    @ApiModelProperty("设备id")
    private String devId;
    @ApiModelProperty("位置")
    private org.postgis.Point location;
    @ApiModelProperty("卫星定位数")
    private int gpsNum;
    @ApiModelProperty("GPS定位信息")
    private String gpsType;
    @ApiModelProperty("对地真北航向角")
    private double azimuth;
    @ApiModelProperty("地面速率")
    private double gndRate;
}

4.2 postgis的geometry、geography类型问题

注意org.postgis.Point在[postgis-jdbc]中,并且为了让jdbc能正确读取数据,需要 将[postgis-jdbc]中的数据进行注册。[postgis-jdbc]提供了自动注册与手动注册。

如果使用自动注册,可以用org.postgis.DriverWrapper作为driver,然后jdbc的url使用jdbc:postgresql_postGIS 就可以自动注册org.postgis.Geometry。但是,源码中支持的数据库类型是geometry而不支持geography,这两者的 差别可以参考不睡觉的怪叔叔德哥的文章

PostGIS教程十三:地理 PostGIS距离计算建议 - 投影与球坐标系, geometry与geography类型

为了让geography也能自动注册,项目中自定义了com.github.lonelyleaf.gis.db.DriverWrapper类, 转换出来还是org.postgis.Geometry,在java代码层使用和geometry并没做区分。

pgconn.addDataType("geography", org.postgis.PGgeometry.class);
pgconn.addDataType("public.geography", org.postgis.PGgeometry.class);
pgconn.addDataType("\"public\".\"geography\"", org.postgis.PGgeometry.class);

如果你没有注册类型,那么你拿到的类型会是org.postgresql.util.PGobject。但其实通过 org.postgis.PGgeometry#geomFromString()还是很可以获取PGobject#value来手动转换的。

4.3 mybatis中需要定义TypeHandler

明显mybatis没有原生支持org.postgis.Geometry与其各个子类。有个 mybatis-typehandlers-postgis 做了一些工作,但实在太简单我选择直接copy其中核心代码

public abstract class AbstractGeometryTypeHandler<T extends Geometry> extends BaseTypeHandler<T> {

    public void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException {
        PGgeometry geometry = new PGgeometry();
        geometry.setGeometry(parameter);
        ps.setObject(i, geometry);
    }

    public T getNullableResult(ResultSet rs, String columnName) throws SQLException {
        PGgeometry pGgeometry = (PGgeometry) rs.getObject(columnName);
        if (pGgeometry == null) {
            return null;
        }
        return (T) pGgeometry.getGeometry();
    }

    public T getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        PGgeometry pGgeometry = (PGgeometry) rs.getObject(columnIndex);
        if (pGgeometry == null) {
            return null;
        }
        return (T) pGgeometry.getGeometry();
    }

    public T getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        PGgeometry pGgeometry = (PGgeometry) cs.getObject(columnIndex);
        if (pGgeometry == null) {
            return null;
        }
        return (T) pGgeometry.getGeometry();
    }

}

@MappedTypes(Point.class)
public class PointTypeHandler extends AbstractGeometryTypeHandler<Point> {
}

注意如果你没有在jdbc connection中注册类型,那rs.getObject拿到的会是org.postgresql.util.PGobject

4.4 org.postgis.Geometry的json序列化

由于没有现成的org.postgis.Geometry转为GeoJson的库,所以项目中使用了自定义SimplePoint类来 转换一下然后序列化。

@Data
@AllArgsConstructor
@NoArgsConstructor
public class SimplePoint {

    private double x;
    private double y;

    public SimplePoint(org.postgis.Point point) {
        this.x = point.x;
        this.y = point.y;
    }
}

其实[postgis-jdbc]中有org.postgis.jts包,对[JTS]有支持,[JTS]是有jackson库转GeoJson的。 如果需要在java层做些地理位置的运算,使用org.postgis.jts包应该更好。

4.5 坐标的保存与[SRID]

SRID的选择其实很复杂,详细解释可以参考下不睡觉的怪叔叔的文章https://blog.csdn.net/qq_35732147/article/details/86301242。 这里摘抄一段

地球不是平的,也没有简单的方法把它放在一张平面纸地图上(或电脑屏幕上),所以人们想出了各种巧妙的解决方案(投影)。

每种投影方案都有优点和缺点,一些投影保留面积特征;一些投影保留角度特征,如墨卡托投影(Mercator);
一些投影试图找到一个很好的中间混合状态,在几个参数上只有很小的失真。所有投影的共同之处在于,
它们将(地球)转换为平面笛卡尔坐标系,选择哪种投影取决于你将如何使用数据(需要哪些数据特征,面积?角度?或者其他)。

[SRID]其实就决定了你的坐标使用的哪种投影,由于我的数据都是标准的gps坐标(经纬度,没有偏移), 所以在转换与建表时,都使用了srid=4326。具体数据库应该使用哪种一定要根据业务来,不然使用postgis进行计算与使用 各种gis软件进行分析时一定会出问题。

在转换到org.postgis.Point时,项目中写死了srid的值,这不是必须,但为了保证正确最好这样做:

default org.postgis.Point toGisPoint(SimplePoint point) {
    Point gisPoint = new Point();
    gisPoint.x = point.getX();
    gisPoint.y = point.getY();
    gisPoint.dimension = 2;
    //WGS84坐标系,也就是GPS使用的坐标
    gisPoint.srid = 4326;
    return gisPoint;
}

5. 总结

java与postgis交互其实不难,这里是用实体<->表对于的方法在建模。其实实在搞不懂,最次还可以全部靠 xml里手写sql搞定不是🐒

文章只介绍了数据的交互,但postgis的各种强大的空间分析的函数并没有介绍,等业务实践再积累些也许可以 再写一下。

如果对分析有兴趣,再次推荐下大佬不睡觉的怪叔叔专栏

[gist索引]:https://postgis.net/workshops/postgis-intro/indexing.html [PostGIS]: https://postgis.net/ [SRID]: https://en.wikipedia.org/wiki/Spatial_reference_system [postgis-jdbc]: https://github.com/postgis/postgis-java [JTS]: https://github.com/locationtech/jts [SRID]: https://en.wikipedia.org/wiki/Spatial_reference_system

马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
Java
1
https://gitee.com/Lonelyleaf/postgis-java-demo.git
git@gitee.com:Lonelyleaf/postgis-java-demo.git
Lonelyleaf
postgis-java-demo
postgis-java-demo
master

Search