坐标转换-使用geotools读取和转换地理空间表的坐标系(sqlserver、postgresql)

前言:

业务上通过GIS软件将空间数据导入到数据库时,因为不同的数据来源和软件设置,可能导入到数据库的空间表坐标系是各种各样的。

如果要把数据库空间表发布到geoserver并且统一坐标系,只是在geoserver单纯的设置坐标系只是改了定义并没有实际执行坐标转换,所以需要在数据库层面统一好坐标系,再发布到geoserver。

1,开发前准备

1.1,数据准备

要准备测试数据,可以参考 地理空间表的导入

我这里使用arcgis pro导入sqlserver,如果导入postgresql需要企业数据库才行,也就是需要离线证书,比较麻烦。

我先导入一个4524的投影坐标,测试转换为4490

1.2,环境准备

坐标转换需要先读取数据库的空间表原坐标系,在根据原坐标系转换为目标坐标系。

使用的转换工具是geotool。

pom引入必要的依赖,geotools版本是24.3

<dependency>
    <groupId>org.geotools</groupId>
    <artifactId>gt-main</artifactId>
    <version>${geotools.version}</version>
</dependency>
<dependency>
     <groupId>org.geotools</groupId>
     <artifactId>gt-jdbc</artifactId>
     <version>${geotools.version}</version>
 </dependency>
 <dependency>
     <groupId>org.geotools.jdbc</groupId>
     <artifactId>gt-jdbc-sqlserver</artifactId>
     <version>${geotools.version}</version>
 </dependency>
 <dependency>
     <groupId>org.geotools.jdbc</groupId>
     <artifactId>gt-jdbc-postgis</artifactId>
     <version>${geotools.version}</version>
 </dependency>

2,读取空间表原坐标系

要使用geotool读取空间表的坐标系,需要先使用geotool提供的方法创建DataStore,官网有一个示例代码
https://docs.geotools.org/latest/userguide/library/jdbc/sqlserver.html

java.util.Map params = new java.util.HashMap();
params.put( "dbtype", "sqlserver");   //(巨坑)
params.put( "host", "localhost");
params.put( "port", 4866);
params.put( "user", "geotools");
params.put( "passwd", "geotools");
DataStore dataStore=DataStoreFinder.getDataStore(params);

这是一个坑,官方说明是版本14之后支持Microsoft JDBC driver,dbtype应该就不需要使用jtds前缀了,实际上不加必报错

先写一个测试方法,传入数据库连接信息,表名,数据库类型,返回原表坐标系

import org.geotools.data.DataStore;
import org.geotools.data.DataStoreFinder;
import org.geotools.data.simple.SimpleFeatureSource;
import org.geotools.jdbc.JDBCDataStoreFactory;
import org.opengis.feature.simple.SimpleFeatureType;
import org.opengis.referencing.crs.CoordinateReferenceSystem;

public static int getEpsg(DatabaseConfig databaseConfig, String tableName) {
        DataStore dataStore = null;
        try {
            Map<String, Object> params = new HashMap<>();
//        params.put(JDBCDataStoreFactory.SCHEMA.key, "dbo");
           if (DatabaseType.SQLSERVER.equals(databaseConfig.getDatabaseType())) {
                params.put(JDBCDataStoreFactory.DBTYPE.key, "jtds-sqlserver");
            } else {
                params.put(JDBCDataStoreFactory.DBTYPE.key, "jtds-postgis");
            }
            params.put(JDBCDataStoreFactory.HOST.key, databaseConfig.getHost());
            params.put(JDBCDataStoreFactory.PORT.key, databaseConfig.getPort());
            params.put(JDBCDataStoreFactory.DATABASE.key, databaseConfig.getDatabaseName());
            params.put(JDBCDataStoreFactory.USER.key, databaseConfig.getUsername());
            params.put(JDBCDataStoreFactory.PASSWD.key, databaseConfig.getPassword());
            dataStore = DataStoreFinder.getDataStore(params);
            if (dataStore == null) {
                System.out.println("Failed to connect to the database.");
                return -1;
            }
            // Get the feature source for the "aa" table
            SimpleFeatureSource featureSource = dataStore.getFeatureSource(tableName);
            // Get the feature type and its CRS
            SimpleFeatureType featureType = featureSource.getSchema();
            CoordinateReferenceSystem crs = featureType.getCoordinateReferenceSystem();
            // Print the CRS details
            if (crs != null) {
                System.out.println("Spatial Reference System: " + crs.getName());
                System.out.println("EPSG Code: " + crs.getName().getCode());
                System.out.println("crs : " + crs.toString());
                //抽取原表坐标系
                int result = extractEPSG(crs.toString());
                System.out.println("Result: " + result);
                return result;
            }
            // Close the data store
            dataStore.dispose();
            return 0;
        } catch (IOException e) {
            log.error("查询空间表坐标系异常:{}", e.toString());
            return -1;
        } finally {
            if (dataStore != null) {
                dataStore.dispose();
            }
        }
    }

然后看一下解析出来坐标信息

Spatial Reference System: EPSG:CGCS2000 / 3-degree Gauss-Kruger zone 36
EPSG Code: CGCS2000 / 3-degree Gauss-Kruger zone 36
crs : PROJCS["CGCS2000 / 3-degree Gauss-Kruger zone 36", 
  GEOGCS["China Geodetic Coordinate System 2000", 
    DATUM["China 2000", 
      SPHEROID["CGCS2000", 6378137.0, 298.257222101, AUTHORITY["EPSG","1024"]], 
      AUTHORITY["EPSG","1043"]], 
    PRIMEM["Greenwich", 0.0, AUTHORITY["EPSG","8901"]], 
    UNIT["degree", 0.017453292519943295], 
    AXIS["Geodetic latitude", NORTH], 
    AXIS["Geodetic longitude", EAST], 
    AUTHORITY["EPSG","4490"]], 
  PROJECTION["Transverse_Mercator", AUTHORITY["EPSG","9807"]], 
  PARAMETER["central_meridian", 108.0], 
  PARAMETER["latitude_of_origin", 0.0], 
  PARAMETER["scale_factor", 1.0], 
  PARAMETER["false_easting", 36500000.0], 
  PARAMETER["false_northing", 0.0], 
  UNIT["m", 1.0], 
  AXIS["Northing", NORTH], 
  AXIS["Easting", EAST], 
  AUTHORITY["EPSG","4524"]]

我想要的是之前我们在arcgis pro中看到的投影坐标,位于crs信息的最后一个EPSG内,针对crs信息写一个方法解析出epsg

    public static int extractEPSG(String input) {
        Pattern pattern = Pattern.compile("AUTHORITY\\[\"EPSG\",\"(\\d+)\"\\]");
        Matcher matcher = pattern.matcher(input);

        int lastEPSG = 0;
        while (matcher.find()) {
            lastEPSG = Integer.parseInt(matcher.group(1));
        }
        return lastEPSG;
    }

3,执行坐标转换

我这里目标坐标系写死,因为系统需要插入到sqlserver中的都要统一坐标系,所以直接在原表更新了。

如果要保留原表信息可以复制表在副本表更新坐标。

sqlserver与postgresql中空间函数有些差异,需要区分处理。

/**
     * 地理空间表坐标转换
     *
     * @param sourceEpsg     原表坐标系
     * @param config         数据库连接信息
     * @param tableName      表名  dbo.ROAD
     * @param geometryColumn 空间字段
     */
    public static void epsgTo4490(int sourceEpsg, DatabaseConfig config, String tableName, String geometryColumn) {
        String sourceEPSG = "EPSG:" + sourceEpsg;
        String targetEPSG = "EPSG:4490";
        ResultSet resultSet = null;
        try (Connection connection = DatabaseConnection.getConnection(config)) {
            //拼接sql
            String sql;
            if (config.getDatabaseType().SQLSERVER.equals(config.getDatabaseType())) {
                sql = "SELECT " + geometryColumn + ".STAsText() as Shape,OBJECTID FROM " + tableName;
            } else {
                //ST_AsText(columns)
                sql = "SELECT ST_AsText(" + geometryColumn + ") as Shape,OBJECTID FROM " + tableName;
            }

            // 使用连接执行 SQL 查询操作
            PreparedStatement statement = connection.prepareStatement(sql);
            resultSet = statement.executeQuery();

            // Create MathTransform
            CRSFactory crsFactory = new CRSFactory();
            org.osgeo.proj4j.CoordinateReferenceSystem sourceCRS = crsFactory.createFromName(sourceEPSG);
            org.osgeo.proj4j.CoordinateReferenceSystem targetCRS = crsFactory.createFromName(targetEPSG);
            CoordinateTransformFactory transformFactory = new CoordinateTransformFactory();
            CoordinateTransform transform = transformFactory.createTransform(sourceCRS, targetCRS);

            // Process each row of the result set
            while (resultSet.next()) {
                String shape = resultSet.getString("Shape");
                int objectId = resultSet.getInt("OBJECTID");

                // Convert the string representation of the geometry to a JTS Geometry object
                WKTReader reader = new WKTReader();
                Geometry geometry = reader.read(shape);

                // Perform the coordinate transformation for each coordinate in the geometry
                for (int i = 0; i < geometry.getCoordinates().length; i++) {
                    Coordinate srcCoord = geometry.getCoordinates()[i];
                    ProjCoordinate targetCoord = new ProjCoordinate(srcCoord.getX(), srcCoord.getY());
                    transform.transform(targetCoord, targetCoord); // 将源坐标转换为目标坐标,并保存在 targetCoord 中
                    srcCoord.setX(targetCoord.x);
                    srcCoord.setY(targetCoord.y);
                }

                // Convert the transformed geometry back to a string
                WKTWriter writer = new WKTWriter();
                String transformedShape = writer.write(geometry);

                // Update the original table with the transformed geometry using the primary key
                String updateSQL;
                if (DatabaseType.SQLSERVER.equals(config.getDatabaseType())) {
                    updateSQL = "UPDATE " + tableName + " SET " + geometryColumn + " = ? WHERE OBJECTID = ?";
                } else {
                    //UPDATE "public"."ROAD" SET Shape = ST_SetSRID(ST_GeomFromText("Shape"), 4490);
                    updateSQL = "UPDATE " + tableName + " SET " + geometryColumn + " = ST_SetSRID(?,4490) WHERE OBJECTID = ?";
                }
                statement = connection.prepareStatement(updateSQL);
                statement.setString(1, transformedShape);
                statement.setInt(2, objectId);
                statement.executeUpdate();
                statement.clearParameters();
            }
            if (DatabaseType.SQLSERVER.equals(config.getDatabaseType())) {
                //修复多边形错误   UPDATE dbo.ROAD SET Shape = Shape.MakeValid()
                String updateSQL = "UPDATE " + tableName + " SET " + geometryColumn + " = " + geometryColumn + ".MakeValid()";
                statement = connection.prepareStatement(updateSQL);
                statement.executeUpdate();
                //指定坐标系 UPDATE dbo.ROAD SET Shape.STSrid=4490
                updateSQL = "UPDATE " + tableName + " SET " + geometryColumn + ".STSrid=4490";
                statement = connection.prepareStatement(updateSQL);
                statement.executeUpdate();
            }
            // Close the resources
            statement.close();
            resultSet.close();
        } catch (SQLException e) {
            log.error("坐标转换中sql执行异常:{}", e.getMessage());
        } catch (ParseException e) {
            log.error("坐标转换中异常:{}", e.getMessage());
        }
    }

上述代码只是sqlservcer亲测多种坐标系转换正常,且转换后的表发布到geoserver和arcgis都能正常预览且聚焦位置正确,postgresql还有待测试

4,单元测试

    public static void main(String[] args) throws SQLException {
        String tableName = "ROAD";
        //测试sqlserver
        DatabaseConfig databaseConfig = new DatabaseConfig(DatabaseType.SQLSERVER, "127.0.0.1", 1433, "测试中文数据库", "sa", "xxxx");
        //测试postgresql
        //DatabaseConfig databaseConfig = new DatabaseConfig(DatabaseType.POSTGRESQL, "127.0.0.1", 5432, "postgis20", "postgres", "xxxxxxx");
        int sourceEpsg = TableEpsgUtil.getEpsg(databaseConfig, tableName);
        System.out.println("原表坐标:" + sourceEpsg);
        //如果获取到原表坐标并且不是4490,则执行转换
        if (sourceEpsg > 0 && sourceEpsg != 4490) {
            epsgTo4490(sourceEpsg, databaseConfig, tableName, "Shape");
            System.out.println("坐标转换完成");
        }
    }
相关推荐
FREE_QIU7 小时前
【SQL server】存储过程模板
sql·sqlserver
lsw1990lsw15 小时前
ubuntu在线安装PostgreSQL(pgsql)
linux·ubuntu·postgresql
leegong231111 天前
PostgreSQL认证指南
数据库·postgresql
AI航海家(Ethan)2 天前
Django中实现简单易用的分页工具
数据库·后端·python·postgresql·django
handsomestWei3 天前
postgresql timescaladb时序数据库使用入门
数据库·postgresql·时序数据库·timescaladb
离陌殇殃4 天前
数据库与表的基本操作
数据库·oracle·sqlserver
AI航海家(Ethan)4 天前
如何使用Django中间件
后端·python·postgresql·中间件·django
归宿乐瑶4 天前
解决 MyBatis Plus 在 PostgreSQL 中 BigDecimal 精度丢失的问题
java·数据库·spring boot·postgresql·mybatis-plus
程序员学习随笔4 天前
PostgreSQL技术内幕25:时序数据库插件TimescaleDB
数据库·postgresql·时序数据库
cwr8884 天前
RedHat8安装postgresql15和 postgis3.4.4记录及遇到的问题总结
postgresql·postgis