# 解析 shp 文件

导入依赖

<!-- shp 文件解析 -->
<dependency>
   <groupId>org.gdal</groupId>
   <artifactId>gdal</artifactId>
   <version>3.5.0</version>
</dependency>
<dependency>
   <groupId>net.postgis</groupId>
   <artifactId>postgis-jdbc</artifactId>
   <version>2.5.0</version>
</dependency>
<!-- 解析 shp 文件 -->
<dependency>
   <groupId>org.geotools</groupId>
   <artifactId>gt-shapefile</artifactId>
   <version>25.0</version>
</dependency>
<dependency>
   <groupId>org.geotools</groupId>
   <artifactId>gt-epsg-wkt</artifactId>
   <version>25.0</version>
</dependency>
<dependency>
   <groupId>org.geotools</groupId>
   <artifactId>gt-geojson</artifactId>
   <version>25.0</version>
</dependency>

# controller

/**
  * 上传 zip 解压 shp 文件信息存入数据库
  * @author 窦凯欣
  */
@ApiOperation(value = "上传zip解压shp文件信息存入数据库")
@Anonymous
@PostMapping("/mineLayersController/uploadShp")
public void uploadShp(@NotNull(message = "没有上传相关文件") @RequestBody MultipartFile file) throws IOException
{
   ksTcService.uploadShp(file);
}

# serviceImpl

/**
  * 上传 zip 解压 shp 文件信息存入数据库
  * @param file
  * @throws IOException
  * @author 窦凯欣
  */
@Override
public Boolean uploadShp(MultipartFile file) throws IOException {
   // 创建临时目录
   Path tempDir = Files.createTempDirectory("");
   // 创建临时 ZIP 文件
   File zipFile = Files.createTempFile(tempDir, null, ".zip").toFile();
   file.transferTo(zipFile);
   // 判断是否为 zip 文件
   if (!ZipUtil.isZipFile(file))
   {
      throw new RuntimeException("请上传zip文件");
   }
   // 解压 ZIP 文件
   File unzipDir = Files.createTempDirectory(tempDir, "unzip").toFile();
   cn.hutool.core.util.ZipUtil.unzip(zipFile, unzipDir);
   // 查找解压后的 SHP 文件
   File shpFile = Files.list(unzipDir.toPath())
      .filter(path -> path.toString().endsWith(".shp"))
      .findFirst()
      .orElseThrow(() -> new IOException("Shapefile not found"))
      .toFile();
   // 读取 SHP 文件
   Map<String, Object> map = new HashMap<>();
   try {
      map.put("url", shpFile.toURI().toURL());
      // 处理编码问题
      map.put("charset", Charset.forName(getShapeFileCharsetName(shpFile)));
   } catch (Exception e) {
      throw new IOException("Invalid shapefile URL", e);
   }
   // 创建工厂对象
   ShapefileDataStoreFactory dataStoreFactory = new ShapefileDataStoreFactory();
   ShapefileDataStore dataStore = (ShapefileDataStore) dataStoreFactory.createDataStore(map);
   // 读取要素
   try (SimpleFeatureIterator iterator = dataStore.getFeatureSource().getFeatures().features()) {
      while (iterator.hasNext()) {
         SimpleFeature feature = iterator.next();
         Iterator<Property> it = feature.getProperties().iterator();
         Map<String, String> mapr = new HashMap<>();
         KsTc ksTc = new KsTc();
         while(it.hasNext())
         {
            Property next = it.next();
            // 将解析 shp 文件的数据以 Key_Value 形式存储到 map 集合中
            mapr.put(next.getName().toString(), next.getValue().toString());
         }
         // 投影坐标转地理坐标的十进制度
         String geo = CoordinateUtil.coordinateTransform(feature, 4490).toString();
         // 添加 ks_tc 数据
         setData(mapr, ksTc, geo);
      }
   } finally {
      // 释放资源
      dataStore.dispose();
   }
   return true;
}
/**
  * 添加 ks_tc 数据
  * @param mapr
  * @param ksTc
  * @author 窦凯欣
  */
@SneakyThrows
private void setData(Map<String, String> mapr, KsTc ksTc, String geo)
{
   // 将 map 数据转换为 JSON 字符串
   String jsonString = JSON.toJSONString(mapr);
   // 将 json 格式的字符传转换为对象
   KsTcParam ksTcParam = JSON.parseObject(jsonString, KsTcParam.class);
   // 设置对象属性数据
   ksTc.setXkzh(ksTcParam.getXKZ());
   String monthAndYaer = ksTcParam.getYXQZ();
   String[] split = monthAndYaer.split("/");
   ksTc.setMonth(Integer.parseInt(split[1]));
   ksTc.setYear(Integer.parseInt(split[0]));
   ksTc.setLon(ksTcParam.getZBX());
   ksTc.setGeom(geo);
   ksTc.setLat(ksTcParam.getZBY());
   ksTc.setWtlx(ksTcParam.getWTLX());
   ksTc.setZdfs(ksTcParam.getZDFS());
   ksTc.setKczt(ksTcParam.getKCZT());
   ksTc.setZdmj(ksTcParam.getZDMJ());
   ksTc.setDt(ksTcParam.getDT());
   // 添加数据
   ksTcxMapper.addData(ksTc);
}
/**
  * 设置字符编码
  * @param file
  * @throws Exception
  * @author 窦凯欣
  */
private static String getShapeFileCharsetName(File file) throws Exception {
   if (file.exists() && !file.isFile()) {
      return "GBK";
   }
   String encode = "GBK";
   BufferedReader reader = null;
   try {
      reader = new BufferedReader(new FileReader(file));
      String tempString = null;
      // 一次读入一行,直到读入 null 为文件结束
      while ((tempString = reader.readLine()) != null) {
         // 显示行号
         if ("UTF-8".equals(tempString.toUpperCase())) {
            encode = "UTF-8";
            break;
         }
         break;
      }
      reader.close();
   } catch (IOException e) {
      e.printStackTrace();
   } finally {
      if (reader != null) {
         try {
            reader.close();
         } catch (IOException e1) {
         }
      }
   }
   return encode;
}

# 工具类

package com.ruoyi.utils;
import cn.hutool.core.util.StrUtil;
import com.fhs.common.utils.StringUtil;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.StringUtils;
import org.apache.commons.collections4.map.LinkedMap;
import org.geotools.geometry.jts.JTS;
import org.geotools.geometry.jts.JTSFactoryFinder;
import org.geotools.geometry.jts.WKTReader2;
import org.geotools.referencing.CRS;
import org.locationtech.jts.geom.*;
import org.locationtech.jts.io.ParseException;
import org.locationtech.jts.io.WKTReader;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.referencing.FactoryException;
import org.opengis.referencing.crs.CRSAuthorityFactory;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.operation.MathTransform;
import org.opengis.referencing.operation.TransformException;
import java.util.*;
import java.util.stream.Collectors;
/**
 * 坐标工具
 *
 * @liuliya
 */
public class CoordinateUtil {
    // WGS84 标准参考椭球中的地球长半径 (单位:米)
    private static final double EARTH_RADIUS_WGS84 = 6378137.0;
    // 十进制经度
    public static final String LNG_PATTERN = "^[\\-\\+]?(0(\\.\\d{1,14})?|([1-9](\\d)?)(\\.\\d{1,14})?|1[0-7]\\d{1}(\\.\\d{1,14})?|180(\\.0{1,14})?)$";
    // 纬度
    public static final String LAT_PATTERN = "^[\\-\\+]?((0|([1-8]\\d?))(\\.\\d{1,14})?|90(\\.0{1,14})?)$";
    /*
     * 度分秒正则
     * 每节的数字不能以 0 开头(比如不能写 08 度,而要写 8 度);
     * 秒的数字可以是小数,小数点后最多有两位数字;
     * 分隔三个节的标志符可以是空格、中横线、逗号、分号、°′" 或者度分秒;
     * 取值范围,经度为 0 度 0 分 0 秒 至 180 度 0 分 0 秒;纬度为 0 度 0 分 0 秒 至 90 度 0 分 0 秒。
     * */
    // 经度
    public static final String LNG_PATTERN_D = "^((\\d|[1-9]\\d|1[0-7]\\d)[°](\\d|[0-5]\\d)[′](\\d|[0-5]\\d)(\\.\\d{1,6})?[\\″]$)|(180[°]0[′]0[\\″]$)";
    // 纬度
    public static final String LAT_PATTERN_D = "^((\\d|[1-8]\\d)[°](\\d|[0-5]\\d)[′](\\d|[0-5]\\d)(\\.\\d{1,6})?[\\″]$)|(90[°]0[′]0[\\″]$)";
    /**
     * 计算两个坐标的距离 (粗略计算,单位:米)
     * 计算公式参照 google map 的距离计算
     *
     * @param lat1 坐标 1 纬度
     * @param lng1 坐标 1 经度
     * @param lat2 坐标 2 纬度
     * @param lng2 坐标 2 经度
     * @return
     */
    public static double distance(double lat1, double lng1, double lat2, double lng2) {
        double radLat1 = Math.toRadians(lat1);
        double radLat2 = Math.toRadians(lat2);
        double a = radLat1 - radLat2;
        double b = Math.toRadians(lng1) - Math.toRadians(lng2);
        double s = 2 * Math.asin(Math.sqrt(Math.pow(Math.sin(a / 2), 2) +
                Math.cos(radLat1) * Math.cos(radLat2) * Math.pow(Math.sin(b / 2), 2)));
        return Math.round(s * EARTH_RADIUS_WGS84);
    }
    /**
     * 度分秒转十进制度
     *
     * @param jwd
     * @return
     */
    public static String Dms2D(String jwd) {
        if (StrUtil.isNotEmpty(jwd) && (jwd.contains("°"))) {// 如果不为空并且存在度单位
            // 计算前进行数据处理
            jwd = jwd.replace("E", "").replace("N", "").replace(":", "").replace(":", "");
            double d = 0, m = 0, s = 0;
            d = Double.parseDouble(jwd.split("°")[0]);
            // 不同单位的分,可扩展
            if (jwd.contains("′")) {// 正常的′
                m = Double.parseDouble(jwd.split("°")[1].split("′")[0]);
            } else if (jwd.contains("'")) {// 特殊的'
                m = Double.parseDouble(jwd.split("°")[1].split("'")[0]);
            }
            // 不同单位的秒,可扩展
            if (jwd.contains("″")) {// 正常的″
                // 有时候没有分 如:112°10.25″
                s = jwd.contains("′") ? Double.parseDouble(jwd.split("′")[1].split("″")[0]) : Double.parseDouble(jwd.split("°")[1].split("″")[0]);
            } else if (jwd.contains("''")) {// 特殊的 ''
                // 有时候没有分 如:112°10.25''
                s = jwd.contains("'") ? Double.parseDouble(jwd.split("'")[1].split("''")[0]) : Double.parseDouble(jwd.split("°")[1].split("''")[0]);
            }
            jwd = String.valueOf(d + m / 60 + s / 60 / 60);// 计算并转换为 string
        }
        return jwd;
    }
    /**
     * @param sourceGeometry
     * @param targetSrid
     * @author 窦凯欣
     */
    public static Geometry coordinateTransform(Geometry sourceGeometry,int targetSrid){
        if (sourceGeometry == null || sourceGeometry.getSRID() == 0 || targetSrid == 0){
            return null;
        }
        try {
            CRSAuthorityFactory factory = CRS.getAuthorityFactory(true);
            CoordinateReferenceSystem source = factory.createCoordinateReferenceSystem("EPSG:" + sourceGeometry.getSRID());
            CoordinateReferenceSystem target = factory.createCoordinateReferenceSystem("EPSG:" + targetSrid);
            MathTransform transform = CRS.findMathTransform(source, target,true);
            Geometry res = JTS.transform(sourceGeometry, transform);
            if (res != null){
                res.setSRID(targetSrid);
            }
            return res;
        }catch (FactoryException | TransformException e){
            e.printStackTrace();
        }
        return null;
    }
    /**
     * @param wkt
     * @param srid
     * @author 窦凯欣
     */
    public static Geometry createGeometry(String wkt,int srid){
        if (StringUtil.isEmpty(wkt)){
            return null;
        }
        try {
            WKTReader reader = new WKTReader();
            Geometry geometry = reader.read(wkt);
            geometry.setSRID(srid);
            return geometry;
        } catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }
    /**
     * 坐标转换
     *
     * @param y
     * @param x
     * @param dh
     * @return
     * @throws Exception
     */
    public static double[] transTo4490fromProjectionByEpsg(double x, double y, String dh) throws Exception {
        String epsgSource = "";
        switch (dh) {
            //6 度分带
            case "13":
                epsgSource = "EPSG:4491";
                break;
            case "14":
                epsgSource = "EPSG:4492";
                break;
            case "15":
                epsgSource = "EPSG:4493";
                break;
            case "16":
                epsgSource = "EPSG:4494";
                break;
            case "17":
                epsgSource = "EPSG:4495";
                break;
            case "18":
                epsgSource = "EPSG:4496";
                break;
            case "19":
                epsgSource = "EPSG:4497";
                break;
            case "20":
                epsgSource = "EPSG:4498";
                break;
            case "21":
                epsgSource = "EPSG:4499";
                break;
            case "22":
                epsgSource = "EPSG:4500";
                break;
            case "23":
                epsgSource = "EPSG:4501";
                break;
            //3 度分带
            case "25":
                epsgSource = "EPSG:4513";
                break;
            case "26": //
                epsgSource = "EPSG:4514";
                break;
            case "27": //
                epsgSource = "EPSG:4515";
                break;
            case "28":  //
                epsgSource = "EPSG:4516";
                break;
            case "29":  //
                epsgSource = "EPSG:4517";
                break;
            case "30":  //
                epsgSource = "EPSG:4518";
                break;
            case "31":  //
                epsgSource = "EPSG:4519";
                break;
            case "32":  //
                epsgSource = "EPSG:4520";
                break;
            case "33":  //
                epsgSource = "EPSG:4521";
                break;
            case "34":   //
                epsgSource = "EPSG:4522";
                break;
            case "35":   //
                epsgSource = "EPSG:4523";
                break;
            case "36":  //
                epsgSource = "EPSG:4524";
                break;
            case "37":  //
                epsgSource = "EPSG:4525";
                break;
            case "38":   //'EPSG:':
                epsgSource = "EPSG:4526";
                break;
            case "39"://'EPSG:':
                epsgSource = "EPSG:4527";
                break;
            case "40"://'EPSG:':
                epsgSource = "EPSG:4528";
                break;
            case "41"://'EPSG:':
                epsgSource = "EPSG:4529";
                break;
            case "42"://'EPSG:':
                epsgSource = "EPSG:4530";
                break;
            case "43"://'EPSG:':
                epsgSource = "EPSG:4531";
                break;
            case "44"://'EPSG:':
                epsgSource = "EPSG:4532";
                break;
            case "45"://'EPSG:':
                epsgSource = "EPSG:4533";
                break;
        }
        return transFromByEpsg(x, y, epsgSource, "EPSG:4490");
    }
    /**
     * 坐标投影转换
     *
     * @param x
     * @param y
     * @param epsgSource
     * @param epsgTarget
     * @return
     * @throws Exception
     */
    public static double[] transFromByEpsg(double x, double y, String epsgSource, String epsgTarget) throws Exception {
        Coordinate sourceCoord = new Coordinate(y,x);
        Coordinate targetCoord = new Coordinate(0, 0);
        try {
            CoordinateReferenceSystem crsSource = CRS.decode(epsgSource);
            CoordinateReferenceSystem crsTarget = CRS.decode(epsgTarget);
            MathTransform transform = CRS.findMathTransform(crsSource, crsTarget);
            JTS.transform(sourceCoord, targetCoord, transform);
            double[] targetCoords = {targetCoord.x,targetCoord.y};
            return targetCoords;
        } catch (TransformException te) {
            throw new Exception("坐标系转换错误:TransformException:" + te.getMessage());
        } catch (FactoryException fe) {
            throw new Exception("坐标系转换错误:FactoryException:" + fe.getMessage());
        } catch (Exception e) {
            throw new Exception("坐标系转换错误:Exception:" + e.getMessage());
        }
    }
    /**
     * 创建点坐标
     * @param x 经度
     * @param y 纬度
     * @return
     */
    public static Point createPoint(Double x, Double y){
        // 创建几何工厂
        GeometryFactory geometryFactory = JTSFactoryFinder.getGeometryFactory();
        // 创建一个几何坐标
        Coordinate coordinate = new Coordinate(x, y);
        return geometryFactory.createPoint(coordinate);
    }
    /**
     * 将要素的坐标系转为相对应的坐标系
     * @param feature 要素类
     * @param targetSrid 坐标系 id
     * @return
     */
    public static Geometry coordinateTransform(SimpleFeature feature, int targetSrid){
        if (StringUtils.isNull(feature)){
            return null;
        }
        try {
            CRSAuthorityFactory factory = CRS.getAuthorityFactory(true);
            CoordinateReferenceSystem target = factory.createCoordinateReferenceSystem("EPSG:" + targetSrid);
            CoordinateReferenceSystem coordinateReferenceSystem = feature.getDefaultGeometryProperty().getDescriptor().getCoordinateReferenceSystem();
            if (coordinateReferenceSystem == null){
                Geometry res = (Geometry)feature.getDefaultGeometry();
                res.setSRID(targetSrid);
                return res;
            }
            MathTransform transform = CRS.findMathTransform(coordinateReferenceSystem, target,true);
            Geometry res = JTS.transform((Geometry)feature.getDefaultGeometry(), transform);
            if (res != null){
                res.setSRID(targetSrid);
            }
            return res;
        }catch (FactoryException | TransformException e){
            e.printStackTrace();
        }
        return null;
    }
    /**
     * 面坐标处理解析成 WKT 格式
     * 第一种方式
     * @param coordinatesStr 一串坐标
     * @return
     *
     * 4348121.92,38610084.30
     * 4348076.60,38611233.20
     * 4347543.64,38611157.40
     * 4347515.09,38611985.26
     * 4346651.97,38612040.25
     * 4346493.69,38611280.02
     * 4346043.13,38610778.17
     * 4345917.48,38609708.95
     * 4346602.51,38609067.42
     */
    public static String CoordinateDisposeWKT1(String coordinatesStr){
        coordinatesStr = coordinatesStr.trim().replaceAll("\\s+","\n");
        // 分割文本获取所有的点坐标
        String[] pointCollection = coordinatesStr.split("[\t\n\r]");
        // 一个面最少三个点
        if (pointCollection.length < 3) {
            return null;
        }
        // 判断当前坐标串用那种方式解析 1. 十进制度、2. 度分秒、3. 投影
        Integer analysisType = polygonAnalysis_type(pointCollection[0].trim().split(","));
        // 存储创建好的点坐标几何
        Coordinate[] coordinates = new Coordinate[pointCollection.length];
        // 循环每个坐标,验真坐标的正确性
        for (int i = 0; i < pointCollection.length; i++) {
            // 根据 "," 分割 x y
            String[] pointStr = pointCollection[i].trim().split(",");
            if (pointStr.length != 2) {
                return null;
            }
            Point point = polygonAnalysis_point(pointStr, analysisType);
            // 其中有一个点存在错误该图形不正确
            if (StringUtils.isNull(point)) {
                return null;
            }
            // 创建点几何
            coordinates[i] = point.getCoordinate();
        }
        Polygon polygon = GeometryUtil.createPolygon(coordinates);
        // 创建多面类型
        MultiPolygon multiPolygon = GeometryUtil.createMultiPolygon(new Polygon[]{polygon});
        // 检查图形的有效性
        if (!multiPolygon.isValid()) {
            return null;
        }
        return multiPolygon.toString();
    }
    /**
     * 面坐标处理解析成 WKT 格式
     * 第二种方式
     * @param coordinatesStr 一串坐标
     * @return
     *
     * 1,4203487.48,38515918.33, ,
     * 2,4203491.25,38516654.34, ,
     * 3,4203431.04,38516773.13, ,
     * 4,4203336.43,38516829.18, ,
     * 5,4203273.42,38516862.76, ,
     * 6,4203217.14,38516856.54, ,
     * 7,4203189.85,38516751.12, ,
     * 8,4203189.85,38516663.19, ,
     * 9,4203093.83,38516717.14, ,
     * 10,4203050.36,38516838.10, ,
     * 11,4202394.67,38516765.61, ,
     * 12,4202312.69,38516211.71, ,
     * *,632.5,350, ,1
     */
    public static String CoordinateDisposeWKT2(String coordinatesStr){
        String[] coordinatesArry = coordinatesStr.split(",");
        // 判断当前坐标串用那种方式解析 1. 十进制度、2. 度分秒、3. 投影
        Integer analysisType = polygonAnalysis_type(new String[]{coordinatesArry[1],coordinatesArry[2]});
        // 坐标面集合
        Map<List<Coordinate>,Integer> CoordinateList = new LinkedMap<>();
        // 坐标点集合
        List<Coordinate> pointList = new ArrayList<>();
        // 坐标解析
        for (int i = 3; i < coordinatesArry.length; i+=4) {
            String sign = coordinatesArry[i-3];
            sign = sign.trim();
            // 结束符
            if ("*".equals(sign)){
                String[] end = coordinatesArry[i+1].split(" ");
                CoordinateList.put(pointList,Integer.parseInt(end[0]));
                pointList = new ArrayList<>();
                continue;
            }
            String x = coordinatesArry[i-2].trim();
            String y = coordinatesArry[i-1].trim();
            Point point = polygonAnalysis_point(new String[]{x,y}, analysisType);
            pointList.add(point.getCoordinate());
        }
        // 坐标转为面
        List<List<Polygon>> multiPolygonList = new ArrayList<>();
        List<Polygon> polygonList = new ArrayList<>();
        for (List<Coordinate> k : CoordinateList.keySet()) {
            // 获取结束值
            Integer v = CoordinateList.get(k);
            Coordinate[] coordinates = new Coordinate[k.size()];
            Polygon polygon = GeometryUtil.createPolygon(k.toArray(coordinates));
            if (v == 1){
                multiPolygonList.add(polygonList);
                polygonList = new ArrayList<>();
            }
            polygonList.add(polygon);
        }
        multiPolygonList.remove(0);
        multiPolygonList.add(polygonList);
        // 处理多面或面带洞
        polygonList = new ArrayList<>();
        for (List<Polygon> list : multiPolygonList) {
            if (list.size() <= 1){
                polygonList.addAll(list);
                continue;
            }
            LinearRing[] linearRings = new LinearRing[list.size()-1];
            for (int i = 1; i < list.size(); i++) {
                linearRings[i-1] = list.get(i).getExteriorRing();
            }
            Polygon polygonWithHoles = GeometryUtil.createPolygonWithHoles(list.get(0).getExteriorRing(), linearRings);
            polygonList.add(polygonWithHoles);
        }
        // 创建多面
        Polygon[] polygons = new Polygon[polygonList.size()];
        MultiPolygon multiPolygon = GeometryUtil.createMultiPolygon(polygonList.toArray(polygons));
        return multiPolygon.toString();
    }
    /**
     * 面坐标处理解析成 WKT 格式
     * 第三种方式
     * @param coordinatesStr 一串坐标
     * @return
     *
     * 2,5,1,4421028.05,39571109.36,2,4421062.05,39571064.36,3,4421110.05,39571178.36,4,4421088.05,39571236.36,5,4421024.05,39571217.36,103,47,,1,3,7,4420967.05,39571412.36,8,4420819.05,39571421.36,6,4420876.05,39571372.36,103,47,,1,
     */
    public static String CoordinateDisposeWKT3(String coordinatesStr){
        String[] coordinatesArry = coordinatesStr.split(",");
        // 判断当前坐标串用那种方式解析 1. 十进制度、2. 度分秒、3. 投影
        Integer analysisType = polygonAnalysis_type(new String[]{coordinatesArry[3],coordinatesArry[4]});
        // 面的个数
        Integer polygonNum = Integer.parseInt(coordinatesArry[0]);
        // 第一个面的点个数
        Integer pointNum = Integer.parseInt(coordinatesArry[1]);
        // 第一个 X 点
        Integer x = 3;
        // 第一个 Y 点
        Integer y = 4;
        // 存放多个面
        List<Polygon> multiPolygonList = new ArrayList<>();
        // 存放为洞的面
        List<Polygon> holePolygon = new ArrayList<>();
        for (int i = 0; i < polygonNum; i++) {
            Coordinate[] coordinates = new Coordinate[pointNum];
            for (int j = 0; j < pointNum; j++,x+=3,y+=3) {
                //x 点
                String lon = coordinatesArry[x];
                //y 点
                String lat = coordinatesArry[y];
                // 转为点
                Point point = polygonAnalysis_point(new String[]{lon,lat}, analysisType);
                coordinates[j] = point.getCoordinate();
            }
            // 创建面
            Polygon polygon = GeometryUtil.createPolygon(coordinates);
            // 判断是多面或带洞的面
            Integer type = Integer.parseInt(coordinatesArry[y+1]);
            if (type == -1){
                holePolygon.add(polygon);
            }else {
                multiPolygonList.add(polygon);
            }
            if (i < polygonNum-1){
                // 第二个或更多面的点数
                pointNum = Integer.parseInt(coordinatesArry[y+2]);
                x+=5;
                y+=5;
            }
        }
        MultiPolygon multiPolygon = GeometryUtil.createMultiPolygon(multiPolygonList.toArray(new Polygon[multiPolygonList.size()]));
        // 存在带洞的情况
        if (holePolygon.size() != 0){
            List<LinearRing> linearRingList = holePolygon.stream().map(hole -> hole.getExteriorRing()).collect(Collectors.toList());
            // 创建带洞的面
            Coordinate[] coordinates = multiPolygon.getCoordinates();
            GeometryFactory geometryFactory = new GeometryFactory();
            LinearRing ring = geometryFactory.createLinearRing(coordinates);
            Polygon polygonWithHoles = GeometryUtil.createPolygonWithHoles(ring, linearRingList.toArray(new LinearRing[holePolygon.size()]));
            multiPolygon = GeometryUtil.createMultiPolygon(new Polygon[]{polygonWithHoles});
        }
        // 检查图形的有效性
        if (!multiPolygon.isValid()) {
            return null;
        }
        return multiPolygon.toString();
    }
    /**
     * 面坐标处理解析成 WKT 格式
     * 第四种方式
     * @param coordinatesStr 一串坐标
     * @return
     *
     * 2,4,114.0643000,41.0300000,114.0820000,41.0300000,114.0820000,41.0200000,114.0643000,41.0200000,0,0,0,4,114.0721000,41.0219000,114.0725000,41.0214000,114.0707000,41.0203000,114.0703000,41.0207000,-1,0,0,
     */
    public static String CoordinateDisposeWKT4(String coordinatesStr){
        String[] coordinatesArry = coordinatesStr.split(",");
        // 判断当前坐标串用那种方式解析 1. 十进制度、2. 度分秒、3. 投影
        Integer analysisType = polygonAnalysis_type(new String[]{coordinatesArry[2],coordinatesArry[3]});
        // 面的个数
        Integer polygonNum = Integer.parseInt(coordinatesArry[0]);
        // 第一个面的点个数
        Integer pointNum = Integer.parseInt(coordinatesArry[1]);
        // 第一个 X 点
        Integer x = 2;
        // 第一个 Y 点
        Integer y = 3;
        // 存放多个面
        List<Polygon> multiPolygonList = new ArrayList<>();
        // 存放为洞的面
        List<Polygon> holePolygon = new ArrayList<>();
        for (int i = 0; i < polygonNum; i++) {
            Coordinate[] coordinates = new Coordinate[pointNum];
            for (int j = 0; j < pointNum; j++,x+=2,y+=2) {
                //x 点
                String lon = coordinatesArry[x];
                //y 点
                String lat = coordinatesArry[y];
                // 转为点
                Point point = polygonAnalysis_point(new String[]{lon,lat}, analysisType);
                coordinates[j] = point.getCoordinate();
            }
            // 创建面
            Polygon polygon = GeometryUtil.createPolygon(coordinates);
            // 判断是多面或带洞的面
            Integer type = Integer.parseInt(coordinatesArry[x]);
            if (type == -1){
                holePolygon.add(polygon);
            }else {
                multiPolygonList.add(polygon);
            }
            if (i < polygonNum-1){
                // 第二个或更多面的点数
                pointNum = Integer.parseInt(coordinatesArry[y+2]);
                x+=4;
                y+=4;
            }
        }
        MultiPolygon multiPolygon = GeometryUtil.createMultiPolygon(multiPolygonList.toArray(new Polygon[multiPolygonList.size()]));
        // 存在带洞的情况
        if (holePolygon.size() != 0){
            List<LinearRing> linearRingList = holePolygon.stream().map(hole -> hole.getExteriorRing()).collect(Collectors.toList());
            // 创建带洞的面
            Coordinate[] coordinates = multiPolygon.getCoordinates();
            GeometryFactory geometryFactory = new GeometryFactory();
            LinearRing ring = geometryFactory.createLinearRing(coordinates);
            Polygon polygonWithHoles = GeometryUtil.createPolygonWithHoles(ring, linearRingList.toArray(new LinearRing[holePolygon.size()]));
            multiPolygon = GeometryUtil.createMultiPolygon(new Polygon[]{polygonWithHoles});
        }
        // 检查图形的有效性
        if (!multiPolygon.isValid()) {
            return null;
        }
        return multiPolygon.toString();
    }
    /**
     * 根据解析类型创建点 (面解析方法中使用)
     *
     * @param pointStr     坐标点 x y 数组
     * @param analysisType 解析类型 1. 十进制度、2. 度分秒、3. 投影
     * @return
     */
    public static Point polygonAnalysis_point(String[] pointStr, Integer analysisType) {
        // 根据得到的类型解析每个点
        switch (analysisType) {
            case 1:
                return pointDecimalism(pointStr[0], pointStr[1]);
            case 2:
                return pointDFM(pointStr[0], pointStr[1]);
            case 3:
                return pointEPSG4490(pointStr[0], pointStr[1]);
        }
        return null;
    }
    /**
     * 获取坐标解析类型 1. 十进制度、2. 度分秒、3. 投影 0. 错误类型
     *
     * @param pointIndex1 坐标 x y 数组
     * @return
     */
    public static Integer polygonAnalysis_type(String[] pointIndex1) {
        if (pointIndex1.length != 2) {
            return 0;
        }
        Point pointDecimalism = pointDecimalism(pointIndex1[0], pointIndex1[1]);
        Point pointDFM = pointDFM(pointIndex1[0], pointIndex1[1]);
        Point pointEPSG4490 = pointEPSG4490(pointIndex1[0], pointIndex1[1]);
        if (StringUtils.isNotNull(pointDecimalism)) {
            return 1;
        }
        if (StringUtils.isNotNull(pointDFM)) {
            return 2;
        }
        if (StringUtils.isNotNull(pointEPSG4490)) {
            return 3;
        }
        return 0;
    }
    /**
     * 检查输入的十进制度点坐标
     *
     * @param x 经度
     * @param y 纬度
     */
    public static Point pointDecimalism(String x, String y) {
        if (!(BasicUtil.isDouble(x, CoordinateUtil.LNG_PATTERN) && BasicUtil.isDouble(y, CoordinateUtil.LAT_PATTERN))) {
            return null;
        }
        return CoordinateUtil.createPoint(Double.parseDouble(x), Double.parseDouble(y));
    }
    /**
     * 检查输入的度分秒点坐标
     *
     * @param x 经度
     * @param y 纬度
     */
    public static Point pointDFM(String x, String y) {
        if (!(BasicUtil.isPattern(x, CoordinateUtil.LNG_PATTERN_D) && BasicUtil.isPattern(y, CoordinateUtil.LAT_PATTERN_D))){
            return null;
        }
        // 转为十进制度
        double lon = Double.parseDouble(CoordinateUtil.Dms2D(x));
        double lat = Double.parseDouble(CoordinateUtil.Dms2D(y));
        return CoordinateUtil.createPoint(lon, lat);
    }
    /**
     * 检查输入的 2000 大地点坐标
     *
     * @param x 经度
     * @param y 纬度
     */
    public static Point pointEPSG4490(String x, String y) {
        try {
            double[] doubles = CoordinateUtil
                    .transTo4490fromProjectionByEpsg(Double.parseDouble(x), Double.parseDouble(y), y.substring(0, 2));
            return CoordinateUtil.createPoint(doubles[0], doubles[1]);
        } catch (Exception e) {
            return null;
        }
    }
    /**
     * 读取 WKT 格式数据
     * @param WKT
     * @return
     */
    public static Geometry readWKT(String WKT){
        // 读取坐标
        WKTReader2 wktReader = new WKTReader2();
        try {
            return wktReader.read(WKT);
        } catch (ParseException e) {
            e.printStackTrace();
        }
        return null;
    }
}

# 查询 geometry 数据进行数据转换

package com.ruoyi.xuexiao;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.io.WKBReader;
import org.locationtech.jts.io.WKBWriter;
import org.locationtech.jts.io.WKTWriter;
public class WKBToWKTConverter {
    public static void main(String[] args) {
        // WKB hex string
        String wkbHex = "01060000000200000001030000000100000039000000745A181CEEE65C408D3DDA56F17244400E9FE57FEEE65C40EDBA566AEE7244400B9FE57FEEE65C40561F8713EC724440C5FCFE4DEEE65C40FC259EEEE972444095AB0B35EEE65C404D8ACE97E77244408492BFCAEEE65C406CBC666CE67244402CD78C2EEFE65C406B656479E47244406E797360EFE65C40ED522FEAE27244406AC02C6DEFE65C404FE69339E17244406598CEA4F0E65C4045B75F93E072444076D07553F1E65C40AD1B903CDE724440A721696CF1E65C40D50F7288DA7244403A6636D0F1E65C40C5616DA2D6724440A521696CF1E65C407B81D0E7D3724440ECCE15FCEFE65C4064CE02B5D372444075B11A0FF0E65C408D9A1C52D3724440596027F6EFE65C405B11828AD2724440BFC4579FEDE65C408D431A5FD1724440B886A116EBE65C409D5C66C9D07244405D8DB8F1E8E65C40F5FE4CFBD0724440EE7A8362E7E65C40BCCCB426D27244408DA0F581E6E65C40E53C0384D3724440ABB941ECE5E65C403CDFE9B5D37244405CF8FF75E4E65C404CF83520D37244401637BEFFE2E65C40632ACEF4D1724440F08EC8F3E0E65C4036A1332DD17244406044ECB5DEE65C406ED3CB01D07244406C06362DDCE65C4076EC176CCF7244404C96E7CFDAE65C40D58EFE9DCF7244402E073E2ED8E65C408E431A5FD17244401478948CD5E65C403EDFE9B5D372444064DCC435D3E65C408D2FD5CDD772444084B75A17D0E65C40C64D2811DD724440ED728DB3CFE65C4036471136DF724440CD219A9ACFE65C4025DCC9B1E3724440341574E5CFE65C40E5CE9BFBE7724440AF084E30D0E65C402FAF38B6EA724440C440F5DED0E65C404E768906EE724440BE5F5023D2E65C40FD11595DF07244400440EDDDD4E65C40CEDFC088F1724440E56457FCD7E65C400D2BA5C7EF7244405FCE8E7EDBE65C40465D3D9CEE724440034B0391DCE65C40DEA10A00EF724440B2C777A3DDE65C40FC11595DF0724440AAAE2B39DEE65C4014C05D43F47244400289B919DFE65C40F48DC56EF5724440BF3DD5DAE0E65C4023176036F6724440FD1D7295E3E65C409D42E12FF7724440DA42DCB3E6E65C407C10495BF87244402A568613E9E65C40B5D393AFF972444043746C87E9E65C40D5093280FA72444084FD064FEAE65C406B4EFFE3FA72444060CB6E7AEBE65C406D4EFFE3FA724440783BBDD7ECE65C40D5B22F8DF872444017808A3BEDE65C405CA0FAFDF6724440FB663ED1EDE65C40BD1D7711F4724440745A181CEEE65C408D3DDA56F17244400103000000010000002F0000001FDB0F8515E75C40CB2C4404D372444015840D9213E75C40DB45906ED27244406C07997F12E75C401BE876A0D27244407F84919211E75C402207D2E4D372444027E2AA6011E75C4064C8135BD57244406B4CEAE310E75C409C700967D7724440F758109910E75C4013BBE5A4D9724440F558109910E75C40AB1E0E4DDB724440CEA370D710E75C403A69EA8ADD7244401D46570911E75C40C4CC1233DF7244402D7EFEB711E75C4083498745E072444000B0128C12E75C40624F961FE2724440E9321A7913E75C402A1DFE4AE37244407DFAEEC914E75C4064C5F356E572444006C2C31A16E75C409B6DE962E77244408838A55217E75C407473F83CE972444065BBAC3F18E75C409BE3469AEA7244400D9CCDFA18E75C409B02A2DEEB724440AE7CEEB519E75C40CB8B3CA6EC7244405E5D0F711AE75C407B08B1B8ED724440CCEC3C131BE75C40945FB3ABEF724440232B779C1BE75C403B332AB1F27244400676D7DA1BE75C4064C2D352F57244400B122B321CE75C40095EA3A9F7724440C60B98571CE75C40BAF97200FA724440AF56F8951CE75C40415D9BA8FB72444027E625381DE75C4061CDE905FD724440656FC0FF1DE75C40BA6FD037FD724440DC629A4A1EE75C40212B03D4FC72444093F85AC71EE75C40BA5075F3FB72444037A1D4D31EE75C409B8924A3F87244404B5674951EE75C40EAED544CF6724440BC11A7311EE75C40EB77F714F372444015CDD9CD1DE75C40198B34A5F07244400D3186761DE75C407C08B1B8ED724440C88E9F441DE75C40220FC893EB724440E4DF925D1DE75C40D92E2BD9E87244400B3186761DE75C40833542B4E672444075880C6A1DE75C40291DFE4AE372444027E625381DE75C401B6FF964DF724440DEA7EBAE1CE75C405A9B825FDC724440577C6AB51BE75C40C3FFB208DA724440EC05897D1AE75C40CBF9A32ED8724440402568C219E75C40042C3C03D772444045060D7E18E75C4062C8135BD5724440D7F3D7EE16E75C402207D2E4D37244401FDB0F8515E75C40CB2C4404D3724440";
        try {
            // Convert WKB hex string to byte array
            byte[] wkbBytes = hexStringToByteArray(wkbHex);
            // Create a WKBReader instance
            WKBReader wkbReader = new WKBReader();
            // Read the geometry from the WKB byte array
            Geometry geometry = wkbReader.read(wkbBytes);
            // Convert the geometry to WKT
            WKTWriter wktWriter = new WKTWriter();
            String wkt = wktWriter.write(geometry);
            // Print the WKT
            System.out.println(wkt);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    // Helper method to convert hex string to byte array
    public static byte[] hexStringToByteArray(String s) {
        int len = s.length();
        byte[] data = new byte[len / 2];
        for (int i = 0; i < len; i += 2) {
            data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
                                + Character.digit(s.charAt(i+1), 16));
        }
        return data;
    }
}

打印结果:

MULTIPOLYGON (((115.60828306558852 40.89799008995315, 115.60830686019855 40.89790086016533, 115.60830686019851 40.897829476335275, 115.6082949628936 40.897764041157615, 115.60828901424095 40.89769265732739, 115.60832470615611 40.89765696541221, 115.60834850076634 40.89759747888714, 115.6083603980712 40.89754988966693, 115.60836343164706 40.89749831887877, 115.60843773055383 40.89747850583675, 115.60847937112143 40.89740712200669, 115.60848531977409 40.897294097608814, 115.60850911438402 40.89717512455858, 115.60848531977406 40.89709184342333, 115.60839750413624 40.89708578717253, 115.60840203863866 40.89707399746576, 115.6083960899863 40.897050202855745, 115.60825332232615 40.8970145109407, 115.60809865736053 40.896996664983114, 115.6079677870052 40.8970026136357, 115.60787260856497 40.897038305550694, 115.60781907069232 40.897079946118346, 115.60778337877734 40.897085894770925, 115.60769414898954 40.89706804881334, 115.60760491920186 40.8970323568981, 115.60747999749924 40.89700856228812, 115.60734317849119 40.89697287037312, 115.60718851352584 40.896955024415476, 115.60710523239067 40.89696097306811, 115.60694461877276 40.89701451094071, 115.6067840051549 40.89708589477094, 115.60664123749444 40.89721081647358, 115.60645088061398 40.89737143009147, 115.60642708600399 40.897436865269285, 115.60642113735157 40.897573684276985, 115.60643898330892 40.89770455463239, 115.60645682926655 40.89778783576764, 115.60649846983421 40.89788896286028, 115.60657580231688 40.8979603466905, 115.60674236458732 40.89799603860557, 115.6069327214678 40.89794250073292, 115.6071468729583 40.89790680881792, 115.60721230813583 40.89791870612292, 115.60727774331352 40.897960346690496, 115.6073134352288 40.89807931974079, 115.60736697310134 40.89811501165596, 115.60747404884658 40.898138806265955, 115.6076406111169 40.89816854952844, 115.60783096799733 40.89820424144361, 115.60797584646994 40.8982448074161, 115.60800347892022 40.89826967662126, 115.60805106814047 40.89828157392625, 115.60812245197076 40.89828157392626, 115.60820573310582 40.8982101900962, 115.60822952771592 40.89816260087602, 115.60826521963092 40.89807337108821, 115.60828306558852 40.89799008995315)), ((115.61068846271927 40.89706471756798, 115.610569489669 40.89704687161039, 115.6105040544914 40.89705282026281, 115.61044754229259 40.89709148650424, 115.61043564498742 40.89713610139805, 115.61040590172495 40.897198562249486, 115.61038805576742 40.89726697175333, 115.61038805576739 40.89731753529971, 115.61040292739855 40.89738594480373, 115.6104148247036 40.89743650835001, 115.61045646527118 40.89746922593897, 115.61050702881766 40.897525738137816, 115.61056354101642 40.89756143005282, 115.61064384782544 40.89762389090427, 115.61072415463431 40.8976863517557, 115.61079851279112 40.897742863954505, 115.6108550249897 40.89778450452214, 115.61089963988361 40.89782317076352, 115.61094425477742 40.897846965373525, 115.61098886967144 40.897879682962376, 115.61102753591257 40.897939169487614, 115.6110602535014 40.89803137360146, 115.61107512513271 40.898111680410494, 115.61109594541647 40.89818306424065, 115.61110486839524 40.89825444807089, 115.61111974002664 40.89830501161715, 115.6111584062679 40.898346652184735, 115.6112059954881 40.89835260083733, 115.61122384144568 40.89834070353232, 115.61125358470808 40.89831393459595, 115.61125655903457 40.898212807503306, 115.61124168740314 40.89814142367307, 115.61121789279326 40.89804327090663, 115.61119409818305 40.89796891275018, 115.61117327789925 40.89787968296238, 115.61116138059435 40.89781424778472, 115.61116732924671 40.89773096664948, 115.61117327789923 40.89766553147185, 115.61117030357293 40.89756143005281, 115.6111584062679 40.89744245700259, 115.61112568867927 40.89735025288856, 115.6110662021541 40.8972788690585, 115.61099184399762 40.89722235685948, 115.61094722910366 40.897186664944485, 115.61086989662097 40.89713610139803, 115.61077471818076 40.89709148650424, 115.61068846271927 40.89706471756798)))