GDAL优化

This commit is contained in:
ZZX9599
2025-09-30 11:29:25 +08:00
parent 6b3509f1d9
commit 65048fbe89
5 changed files with 228 additions and 79 deletions

View File

@ -29,7 +29,7 @@ public class GdalController {
public void importDataStreamGzip(@Parameter(description = "矢量文件路径", required = true) @RequestParam("path") String path, HttpServletResponse response) throws IOException {
// 解析矢量文件、得到JSON数据
String jsonData = GdalUtil.readVectorToGeoJson(path);
String jsonData = GdalUtil.readVectorToSpecifiedGeoJson(path);
// 设置响应头
String filename = URLEncoder.encode("data.gz", StandardCharsets.UTF_8);

View File

@ -77,7 +77,7 @@ public class IconLibraryController {
// 校验图标库文件是否已存在
if (iconFile.exists()) {
if (iconFile.isDirectory()) {
return ApiResponse.failure("同名目录已存在无法创建文件:" + iconPath);
return ApiResponse.failure("同名目录已存在无法创建文件:" + iconPath);
}
return ApiResponse.failure("图标库文件已存在:" + iconPath);
}
@ -391,7 +391,7 @@ public class IconLibraryController {
private String getIconLibrary() {
LambdaQueryWrapper<IconLibrary> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(IconLibrary::getIsEnable, 1); // 1=启用0=未启用
queryWrapper.eq(IconLibrary::getIsEnable, 1); // 1=启用0=未启用
IconLibrary library = iconLibraryService.getOne(queryWrapper);
return library == null ? null : library.getPath();
}
@ -404,7 +404,7 @@ public class IconLibraryController {
iconLibraryService.updateById(library);
}
// 检查路径是否已存在(存在则启用不存在则新增)
// 检查路径是否已存在(存在则启用不存在则新增)
LambdaQueryWrapper<IconLibrary> pathWrapper = new LambdaQueryWrapper<>();
pathWrapper.eq(IconLibrary::getPath, iconPath);
IconLibrary existLibrary = iconLibraryService.getOne(pathWrapper);

View File

@ -77,7 +77,7 @@ public class MilitaryLibraryController {
// 校验军标库文件是否已存在
if (militaryFile.exists()) {
if (militaryFile.isDirectory()) {
return ApiResponse.failure("同名目录已存在无法创建文件:" + militaryPath);
return ApiResponse.failure("同名目录已存在无法创建文件:" + militaryPath);
}
return ApiResponse.failure("军标库文件已存在:" + militaryPath);
}
@ -390,7 +390,7 @@ public class MilitaryLibraryController {
private String getMilitaryLibrary() {
LambdaQueryWrapper<MilitaryLibrary> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(MilitaryLibrary::getIsEnable, 1); // 1=启用0=未启用
queryWrapper.eq(MilitaryLibrary::getIsEnable, 1); // 1=启用0=未启用
MilitaryLibrary library = militaryLibraryService.getOne(queryWrapper);
return library == null ? null : library.getPath();
}
@ -403,7 +403,7 @@ public class MilitaryLibraryController {
militaryLibraryService.updateById(library);
}
// 检查路径是否已存在(存在则启用不存在则新增)
// 检查路径是否已存在(存在则启用不存在则新增)
LambdaQueryWrapper<MilitaryLibrary> pathWrapper = new LambdaQueryWrapper<>();
pathWrapper.eq(MilitaryLibrary::getPath, militaryPath);
MilitaryLibrary existLibrary = militaryLibraryService.getOne(pathWrapper);

View File

@ -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;
}
}

View File

@ -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; // 数据库列名