Coordinate Transformations - Read and convert coordinate systems of geospatial tables using GeoTools (SQLServer, PostgreSQL)

Foreword:

When importing spatial data into the database through GIS software in business, due to different data sources and software settings, the spatial table coordinate systems that may be imported into the database are various.
If you want to publish the database space table to GeoServer and unify the coordinate system, you just simply set the coordinate system on GeoServer, just change the definition and do not actually perform coordinate conversion, so you need to unify the coordinate system at the database level, and then publish it to GeoServer.
insert image description here

1, Preparation before development

1.1, data preparation

To prepare test data, you can refer to the import of geospatial tables .
I use arcgis pro to import sqlserver here. If you import postgresql, you need an enterprise database, that is, you need an offline certificate, which is troublesome.
I first import a projected coordinate of 4524, and the test is converted to 4490
insert image description here

1.2, environment preparation

Coordinate conversion needs to read the original coordinate system of the spatial table of the database first, and then convert to the target coordinate system according to the original coordinate system.
The conversion tool used is geotool.
Pom introduces the necessary dependencies, the geotools version is 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, read the original coordinate system of the space table

To use geotool to read the coordinate system of the spatial table, you need to use the method provided by geotool to create a DataStore. The official website has a sample code
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);

This is a trap. The official statement is that Microsoft JDBC driver is supported after version 14. The dbtype should not need to use the jtds prefix. In fact, no error will be reported if it is not added.

Write a test method first, pass in the database connection information, table name, database type, and return the original table coordinate system

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

Then look at the parsed coordinate information

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"]]

What I want is the projected coordinates we saw in arcgis pro before, which is located in the last EPSG of the crs information. Write a method to parse out the epsg for the crs information

    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, perform coordinate transformation

I wrote the target coordinate system hard here, because the system needs to insert into the sqlserver to have a unified coordinate system, so it is directly updated in the original table.
If you want to keep the original table information, you can copy the table and update the coordinates in the copy table.
There are some differences between the spatial functions in sqlserver and postgresql, which need to be handled separately.

/**
     * 地理空间表坐标转换
     *
     * @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());
        }
    }

The above code is only sqlservcer pro-test that the conversion of various coordinate systems is normal, and the converted table can be previewed normally and the focus position is correct when the converted table is published to geoserver and arcgis. Postgresql has yet to be tested

4, unit test

    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("坐标转换完成");
        }
    }

Guess you like

Origin blog.csdn.net/u012796085/article/details/132128410