GDAL优化
This commit is contained in:
		| @ -2,7 +2,8 @@ package com.yj.earth.common.util; | ||||
|  | ||||
| import org.gdal.gdal.gdal; | ||||
| import org.gdal.ogr.*; | ||||
| import org.gdal.osr.SpatialReference; | ||||
| import com.fasterxml.jackson.databind.ObjectMapper; | ||||
| import com.fasterxml.jackson.core.JsonProcessingException; | ||||
|  | ||||
| import java.util.ArrayList; | ||||
| import java.util.HashMap; | ||||
| @ -11,88 +12,236 @@ import java.util.Map; | ||||
|  | ||||
| public class GdalUtil { | ||||
|  | ||||
|     // 静态初始化 | ||||
|     // 静态初始化ObjectMapper,避免重复创建 | ||||
|     private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); | ||||
|  | ||||
|     // 静态初始化GDAL | ||||
|     static { | ||||
|         // 可以考虑设置缓存目录来提升性能 | ||||
|         gdal.SetConfigOption("GDAL_CACHEMAX", "1024"); // 设置1GB缓存 | ||||
|         gdal.SetConfigOption("PROJ_LIB", "./lib"); | ||||
|         gdal.AllRegister(); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 读取矢量文件并转换为标准GeoJSON格式 | ||||
|      * 读取矢量文件、将每个图层转换为符合指定格式的 GeoJSON | ||||
|      */ | ||||
|     public static String readVectorToGeoJson(String filePath) { | ||||
|         // 打开矢量文件 | ||||
|         DataSource dataSource = ogr.Open(filePath); | ||||
|         if (dataSource == null) { | ||||
|             throw new RuntimeException("无法打开矢量文件: " + filePath); | ||||
|         } | ||||
|  | ||||
|         // 创建GeoJSON根对象 - FeatureCollection | ||||
|         Map<String, Object> geoJson = new HashMap<>(); | ||||
|         geoJson.put("type", "FeatureCollection"); | ||||
|  | ||||
|         // 存储所有要素 | ||||
|         List<Map<String, Object>> features = new ArrayList<>(); | ||||
|         geoJson.put("features", features); | ||||
|  | ||||
|         // 获取所有图层 | ||||
|         int layerCount = dataSource.GetLayerCount(); | ||||
|         for (int i = 0; i < layerCount; i++) { | ||||
|             Layer layer = dataSource.GetLayer(i); | ||||
|             if (layer == null) continue; | ||||
|  | ||||
|             // 获取空间参考信息 | ||||
|             SpatialReference srs = layer.GetSpatialRef(); | ||||
|             if (srs != null && features.isEmpty()) { // 只添加一次CRS信息 | ||||
|                 Map<String, Object> crs = new HashMap<>(); | ||||
|                 Map<String, Object> crsProps = new HashMap<>(); | ||||
|                 crsProps.put("name", srs.GetAttrValue("AUTHORITY", 0) + ":" + srs.GetAttrValue("AUTHORITY", 1)); | ||||
|                 crs.put("type", "name"); | ||||
|                 crs.put("properties", crsProps); | ||||
|                 geoJson.put("crs", crs); | ||||
|     public static String readVectorToSpecifiedGeoJson(String filePath) { | ||||
|         DataSource dataSource = null; | ||||
|         try { | ||||
|             // 打开矢量文件,使用只读模式 | ||||
|             dataSource = ogr.Open(filePath, 0); | ||||
|             if (dataSource == null) { | ||||
|                 throw new RuntimeException("无法打开矢量文件: " + filePath + ", 错误信息: " + gdal.GetLastErrorMsg()); | ||||
|             } | ||||
|  | ||||
|             // 创建结果结构,预先估计容量 | ||||
|             Map<String, Object> result = new HashMap<>(2); | ||||
|             int layerCount = dataSource.GetLayerCount(); | ||||
|             List<Map<String, Object>> layerList = new ArrayList<>(layerCount); | ||||
|  | ||||
|             // 处理每个图层 | ||||
|             for (int i = 0; i < layerCount; i++) { | ||||
|                 Layer layer = dataSource.GetLayer(i); | ||||
|                 if (layer == null) continue; | ||||
|  | ||||
|                 // 为当前图层创建符合格式的 GeoJSON | ||||
|                 Map<String, Object> layerGeoJson = convertLayerToSpecifiedFormat(layer); | ||||
|                 if (layerGeoJson != null) { | ||||
|                     layerList.add(layerGeoJson); | ||||
|                 } | ||||
|                 // 提前释放图层资源 | ||||
|                 layer.delete(); | ||||
|             } | ||||
|  | ||||
|             result.put("list", layerList); | ||||
|  | ||||
|             // 使用单例ObjectMapper转换为JSON字符串 | ||||
|             return OBJECT_MAPPER.writeValueAsString(result); | ||||
|  | ||||
|         } catch (Exception e) { | ||||
|             throw new RuntimeException("处理矢量文件时发生错误: " + e.getMessage() + ", 详细错误: " + gdal.GetLastErrorMsg(), e); | ||||
|         } finally { | ||||
|             // 确保数据源被正确释放 | ||||
|             if (dataSource != null) { | ||||
|                 dataSource.delete(); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 将单个图层转换为符合指定格式的 GeoJSON | ||||
|      */ | ||||
|     private static Map<String, Object> convertLayerToSpecifiedFormat(Layer layer) { | ||||
|         try { | ||||
|             // 获取要素数量,预分配集合大小 | ||||
|             long featureCount = layer.GetFeatureCount(); | ||||
|             if (featureCount <= 0) { | ||||
|                 return null; // 跳过空图层 | ||||
|             } | ||||
|  | ||||
|             // 创建符合格式的 GeoJSON FeatureCollection | ||||
|             Map<String, Object> geoJson = new HashMap<>(3); | ||||
|             geoJson.put("type", "FeatureCollection"); | ||||
|             geoJson.put("name", layer.GetName()); | ||||
|  | ||||
|             // 预分配要素列表容量 | ||||
|             List<Map<String, Object>> features = new ArrayList<>((int) Math.min(featureCount, Integer.MAX_VALUE)); | ||||
|  | ||||
|             // 获取字段定义 | ||||
|             FeatureDefn featureDefn = layer.GetLayerDefn(); | ||||
|             int fieldCount = featureDefn.GetFieldCount(); | ||||
|  | ||||
|             // 读取所有要素 | ||||
|             // 缓存字段信息,避免重复获取 | ||||
|             String[] fieldNames = new String[fieldCount]; | ||||
|             int[] fieldTypes = new int[fieldCount]; | ||||
|             for (int j = 0; j < fieldCount; j++) { | ||||
|                 FieldDefn fieldDef = featureDefn.GetFieldDefn(j); | ||||
|                 fieldNames[j] = fieldDef.GetName(); | ||||
|                 fieldTypes[j] = fieldDef.GetFieldType(); | ||||
|             } | ||||
|  | ||||
|             // 读取图层中的所有要素 | ||||
|             layer.ResetReading(); | ||||
|             Feature feature; | ||||
|  | ||||
|             while ((feature = layer.GetNextFeature()) != null) { | ||||
|                 // 创建Feature对象 | ||||
|                 Map<String, Object> featureObj = new HashMap<>(); | ||||
|                 featureObj.put("type", "Feature"); | ||||
|  | ||||
|                 // 存储属性信息 | ||||
|                 Map<String, Object> properties = new HashMap<>(); | ||||
|                 // 可以添加图层名称作为属性 | ||||
|                 properties.put("layerName", layer.GetName()); | ||||
|                 Map<String, Object> featureMap = new HashMap<>(3); | ||||
|                 featureMap.put("type", "Feature"); | ||||
|  | ||||
|                 // 处理属性 | ||||
|                 Map<String, Object> properties = new HashMap<>(fieldCount); | ||||
|                 for (int j = 0; j < fieldCount; j++) { | ||||
|                     String fieldName = featureDefn.GetFieldDefn(j).GetName(); | ||||
|                     properties.put(fieldName, feature.GetFieldAsString(j)); | ||||
|                     properties.put(fieldNames[j], getFieldValue(feature, j, fieldTypes[j])); | ||||
|                 } | ||||
|                 featureObj.put("properties", properties); | ||||
|                 featureMap.put("properties", properties); | ||||
|  | ||||
|                 // 存储几何信息,转换为GeoJSON格式 | ||||
|                 // 处理几何信息 - 直接构建Map而不是先转JSON再解析 | ||||
|                 Geometry geometry = feature.GetGeometryRef(); | ||||
|                 if (geometry != null) { | ||||
|                 featureMap.put("geometry", convertGeometryToMap(geometry)); | ||||
|  | ||||
|                     String geometryJson = geometry.ExportToJson(); | ||||
|                     Map<String, Object> geometryObj = JsonUtil.jsonToMap(geometryJson); | ||||
|                     featureObj.put("geometry", geometryObj); | ||||
|                 } | ||||
|  | ||||
|                 features.add(featureObj); | ||||
|                 feature.delete(); | ||||
|                 features.add(featureMap); | ||||
|                 feature.delete(); // 及时释放要素资源 | ||||
|             } | ||||
|  | ||||
|             geoJson.put("features", features); | ||||
|             return geoJson; | ||||
|  | ||||
|         } catch (Exception e) { | ||||
|             System.err.println("转换图层 " + layer.GetName() + " 时发生错误: " + e.getMessage() + ", 详细错误: " + gdal.GetLastErrorMsg()); | ||||
|             return null; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 将Geometry直接转换为Map,避免JSON序列化/反序列化开销 | ||||
|      */ | ||||
|     private static Map<String, Object> convertGeometryToMap(Geometry geometry) { | ||||
|         if (geometry == null) { | ||||
|             return createEmptyMultiPolygon(); | ||||
|         } | ||||
|  | ||||
|         // 关闭数据源 | ||||
|         dataSource.delete(); | ||||
|         try { | ||||
|             String geometryType = geometry.GetGeometryName(); | ||||
|             Map<String, Object> geometryMap = new HashMap<>(2); | ||||
|  | ||||
|         // 转换为JSON并返回 | ||||
|         return JsonUtil.toJson(geoJson); | ||||
|             // 如果不是MultiPolygon,尝试转换 | ||||
|             if (!"MULTIPOLYGON".equals(geometryType)) { | ||||
|                 // 转换为MultiPolygon | ||||
|                 Geometry multiPolygon = geometry.MakeValid(); | ||||
|                 if (!"MULTIPOLYGON".equals(multiPolygon.GetGeometryName())) { | ||||
|                     multiPolygon = geometry.Buffer(0); // 修复几何问题 | ||||
|                 } | ||||
|                 if (!"MULTIPOLYGON".equals(multiPolygon.GetGeometryName())) { | ||||
|                     // 如果仍然不是MultiPolygon,创建空的 | ||||
|                     return createEmptyMultiPolygon(); | ||||
|                 } | ||||
|                 geometry = multiPolygon; | ||||
|             } | ||||
|  | ||||
|             geometryMap.put("type", "MultiPolygon"); | ||||
|             geometryMap.put("coordinates", getCoordinates(geometry)); | ||||
|  | ||||
|             return geometryMap; | ||||
|         } catch (Exception e) { | ||||
|             return createEmptyMultiPolygon(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 直接获取几何坐标,避免JSON转换 | ||||
|      */ | ||||
|     private static List<?> getCoordinates(Geometry geometry) { | ||||
|         List<Object> coordinates = new ArrayList<>(); | ||||
|  | ||||
|         if (geometry == null) { | ||||
|             return coordinates; | ||||
|         } | ||||
|  | ||||
|         int geometryCount = geometry.GetGeometryCount(); | ||||
|         for (int i = 0; i < geometryCount; i++) { | ||||
|             Geometry polygon = geometry.GetGeometryRef(i); | ||||
|             if (polygon == null) continue; | ||||
|  | ||||
|             List<Object> polygonCoords = new ArrayList<>(); | ||||
|             int ringCount = polygon.GetGeometryCount(); | ||||
|             for (int j = 0; j < ringCount; j++) { | ||||
|                 Geometry ring = polygon.GetGeometryRef(j); | ||||
|                 if (ring == null) continue; | ||||
|  | ||||
|                 List<List<Double>> ringCoords = new ArrayList<>(); | ||||
|                 int pointCount = ring.GetPointCount(); | ||||
|                 for (int k = 0; k < pointCount; k++) { | ||||
|                     double[] point = ring.GetPoint(k); | ||||
|                     List<Double> pointCoords = new ArrayList<>(2); | ||||
|                     pointCoords.add(point[0]); | ||||
|                     pointCoords.add(point[1]); | ||||
|                     ringCoords.add(pointCoords); | ||||
|                 } | ||||
|                 polygonCoords.add(ringCoords); | ||||
|             } | ||||
|             coordinates.add(polygonCoords); | ||||
|         } | ||||
|  | ||||
|         return coordinates; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 获取字段值、根据字段类型处理 | ||||
|      */ | ||||
|     private static Object getFieldValue(Feature feature, int fieldIndex, int fieldType) { | ||||
|         try { | ||||
|             switch (fieldType) { | ||||
|                 case ogr.OFTInteger: | ||||
|                     return feature.GetFieldAsInteger(fieldIndex); | ||||
|                 case ogr.OFTInteger64: | ||||
|                     return feature.GetFieldAsInteger64(fieldIndex); | ||||
|                 case ogr.OFTReal: | ||||
|                     return feature.GetFieldAsDouble(fieldIndex); | ||||
|                 case ogr.OFTString: | ||||
|                 case ogr.OFTStringList: | ||||
|                 case ogr.OFTWideString: | ||||
|                     return feature.GetFieldAsString(fieldIndex); | ||||
|                 case ogr.OFTDate: | ||||
|                 case ogr.OFTTime: | ||||
|                 case ogr.OFTDateTime: | ||||
|                     return feature.GetFieldAsString(fieldIndex); | ||||
|                 default: | ||||
|                     // 对于其他类型,统一返回字符串表示 | ||||
|                     return feature.GetFieldAsString(fieldIndex); | ||||
|             } | ||||
|         } catch (Exception e) { | ||||
|             return feature.GetFieldAsString(fieldIndex); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 创建空的 MultiPolygon 几何对象 | ||||
|      */ | ||||
|     private static Map<String, Object> createEmptyMultiPolygon() { | ||||
|         Map<String, Object> geometry = new HashMap<>(2); | ||||
|         geometry.put("type", "MultiPolygon"); | ||||
|         geometry.put("coordinates", new ArrayList<>()); | ||||
|         return geometry; | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -27,7 +27,7 @@ public class SQLiteUtil { | ||||
|     // 统一日期格式(LocalDateTime) | ||||
|     private static final DateTimeFormatter LOCAL_DATE_TIME_FORMATTER = DateTimeFormatter.ISO_LOCAL_DATE_TIME; | ||||
|  | ||||
|     // 连接池缓存:key=数据库文件路径,value=DBCP2数据源(支持多连接复用) | ||||
|     // 连接池缓存:key=数据库文件路径、value=DBCP2数据源(支持多连接复用) | ||||
|     private static final Map<String, BasicDataSource> DATA_SOURCE_POOL = new ConcurrentHashMap<>(); | ||||
|  | ||||
|     // 字段缓存:缓存类的字段映射(避免反射重复开销) | ||||
| @ -46,7 +46,7 @@ public class SQLiteUtil { | ||||
|         if (dbFilePath == null || dbFilePath.trim().isEmpty()) { | ||||
|             throw new IllegalArgumentException("数据库文件路径不能为空"); | ||||
|         } | ||||
|         // 不存在则创建数据源,存在则直接从池获取连接 | ||||
|         // 不存在则创建数据源、存在则直接从池获取连接 | ||||
|         BasicDataSource dataSource = DATA_SOURCE_POOL.computeIfAbsent(dbFilePath, SQLiteUtil::createDataSource); | ||||
|         return dataSource.getConnection(); | ||||
|     } | ||||
| @ -63,7 +63,7 @@ public class SQLiteUtil { | ||||
|         dataSource.setDriverClassName("org.sqlite.JDBC"); | ||||
|         dataSource.setUrl("jdbc:sqlite:" + dbFilePath); | ||||
|  | ||||
|         // 2. 连接池核心参数(根据并发量调整,SQLite不建议过多连接) | ||||
|         // 2. 连接池核心参数(根据并发量调整、SQLite不建议过多连接) | ||||
|         dataSource.setMaxTotal(30);         // 最大连接数:20-50(根据服务器CPU/内存调整) | ||||
|         dataSource.setMaxIdle(15);          // 最大空闲连接:保留部分连接避免频繁创建 | ||||
|         dataSource.setMinIdle(5);           // 最小空闲连接:保证基础并发响应速度 | ||||
| @ -84,7 +84,7 @@ public class SQLiteUtil { | ||||
|         try (Connection conn = dataSource.getConnection(); | ||||
|              Statement stmt = conn.createStatement()) { | ||||
|             stmt.execute("PRAGMA journal_mode=WAL;");        // 启用WAL模式:支持多读者+单写者(核心优化) | ||||
|             stmt.execute("PRAGMA cache_size=-20000;");       // 页面缓存20MB(负号表示KB单位,内存足可调大) | ||||
|             stmt.execute("PRAGMA cache_size=-20000;");       // 页面缓存20MB(负号表示KB单位、内存足可调大) | ||||
|             stmt.execute("PRAGMA synchronous=NORMAL;");     // 同步级别:平衡性能与安全(崩溃最多丢WAL日志) | ||||
|             stmt.execute("PRAGMA temp_store=MEMORY;");      // 临时表/索引存内存(减少磁盘IO) | ||||
|             stmt.execute("PRAGMA busy_timeout=2000;");       // 忙等待超时:2秒(避免瞬时并发锁等待) | ||||
| @ -165,7 +165,7 @@ public class SQLiteUtil { | ||||
|             throw new IllegalArgumentException("查询SQL不能为空"); | ||||
|         } | ||||
|  | ||||
|         // 预加载字段映射(缓存生效,避免重复反射) | ||||
|         // 预加载字段映射(缓存生效、避免重复反射) | ||||
|         Map<String, Field> fieldMap = getFieldMap(clazz); | ||||
|  | ||||
|         // try-with-resources:自动关闭Connection、PreparedStatement、ResultSet | ||||
| @ -175,7 +175,7 @@ public class SQLiteUtil { | ||||
|  | ||||
|             ResultSetMetaData metaData = rs.getMetaData(); | ||||
|             int columnCount = metaData.getColumnCount(); | ||||
|             // 预构建「列名-字段」映射(一次构建,循环复用) | ||||
|             // 预构建「列名-字段」映射(一次构建、循环复用) | ||||
|             ColumnFieldMapping[] mappings = buildColumnFieldMappings(metaData, columnCount, fieldMap); | ||||
|  | ||||
|             // 处理结果集(反射赋值) | ||||
| @ -254,7 +254,7 @@ public class SQLiteUtil { | ||||
|      * | ||||
|      * @param dbFilePath 数据库路径 | ||||
|      * @param sql        DDL语句 | ||||
|      * @param params     SQL参数(可选,如动态表名) | ||||
|      * @param params     SQL参数(可选、如动态表名) | ||||
|      */ | ||||
|     public static void executeDDL(String dbFilePath, String sql, List<Object> params) throws SQLException { | ||||
|         if (dbFilePath == null || dbFilePath.trim().isEmpty()) { | ||||
| @ -269,7 +269,7 @@ public class SQLiteUtil { | ||||
|             pstmt.execute(); | ||||
|         } catch (SQLException e) { | ||||
|             closeDataSource(dbFilePath); | ||||
|             throw new SQLException("执行DDL失败(SQL:" + sql + ",路径:" + dbFilePath + ")", e); | ||||
|             throw new SQLException("执行DDL失败(SQL:" + sql + "、路径:" + dbFilePath + ")", e); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @ -284,7 +284,7 @@ public class SQLiteUtil { | ||||
|     // ========================== 工具辅助方法 ========================== | ||||
|  | ||||
|     /** | ||||
|      * 创建PreparedStatement(复用Connection,避免重复获取) | ||||
|      * 创建PreparedStatement(复用Connection、避免重复获取) | ||||
|      * | ||||
|      * @param conn   已获取的Connection(从连接池来) | ||||
|      * @param sql    SQL语句 | ||||
| @ -362,7 +362,7 @@ public class SQLiteUtil { | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 获取类的字段缓存(含父类字段,一次反射永久缓存) | ||||
|      * 获取类的字段缓存(含父类字段、一次反射永久缓存) | ||||
|      * | ||||
|      * @param clazz 目标类 | ||||
|      * @return 字段名→Field的映射(不可修改) | ||||
| @ -385,7 +385,7 @@ public class SQLiteUtil { | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 优化的字段赋值(处理类型转换,避免重复异常捕获) | ||||
|      * 优化的字段赋值(处理类型转换、避免重复异常捕获) | ||||
|      * | ||||
|      * @param obj   目标对象 | ||||
|      * @param field 字段 | ||||
| @ -407,7 +407,7 @@ public class SQLiteUtil { | ||||
|                 field.set(obj, convertedValue); | ||||
|             } | ||||
|         } catch (IllegalAccessException e) { | ||||
|             System.err.println("警告:字段赋值失败(字段:" + field.getName() + ",值类型:" + value.getClass().getName() + "):" + e.getMessage()); | ||||
|             System.err.println("警告:字段赋值失败(字段:" + field.getName() + "、值类型:" + value.getClass().getName() + "):" + e.getMessage()); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @ -451,7 +451,7 @@ public class SQLiteUtil { | ||||
|         } | ||||
|  | ||||
|         // 不支持的类型转换(打印警告) | ||||
|         System.err.println("警告:不支持的类型转换(目标类型:" + targetType.getName() + ",原始值类型:" + value.getClass().getName() + ")"); | ||||
|         System.err.println("警告:不支持的类型转换(目标类型:" + targetType.getName() + "、原始值类型:" + value.getClass().getName() + ")"); | ||||
|         return null; | ||||
|     } | ||||
|  | ||||
| @ -547,7 +547,7 @@ public class SQLiteUtil { | ||||
|     // ========================== 内部辅助类 ========================== | ||||
|  | ||||
|     /** | ||||
|      * 列-字段映射模型(一次性构建,减少循环内计算) | ||||
|      * 列-字段映射模型(一次性构建、减少循环内计算) | ||||
|      */ | ||||
|     private static class ColumnFieldMapping { | ||||
|         String columnName; // 数据库列名 | ||||
|  | ||||
		Reference in New Issue
	
	Block a user
	 ZZX9599
					ZZX9599