代码提交
This commit is contained in:
@ -1,6 +1,5 @@
|
||||
package com.yj.earth.business.controller;
|
||||
|
||||
import com.yj.earth.annotation.CheckAuth;
|
||||
import com.yj.earth.auth.AuthInfo;
|
||||
import com.yj.earth.auth.AuthValidator;
|
||||
import com.yj.earth.common.util.ApiResponse;
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
package com.yj.earth.business.controller;
|
||||
|
||||
import com.yj.earth.common.util.GdalUtil;
|
||||
import com.yj.earth.common.util.GdalJsonConverter;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
@ -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.readVectorToSpecifiedGeoJson(path);
|
||||
String jsonData = GdalJsonConverter.convertToJson(path);
|
||||
|
||||
// 设置响应头
|
||||
String filename = URLEncoder.encode("data.gz", StandardCharsets.UTF_8);
|
||||
|
||||
@ -59,7 +59,7 @@ public class GraphHopperController {
|
||||
@Operation(summary = "加载地图数据")
|
||||
@PostMapping("/loadMap")
|
||||
@CheckAuth
|
||||
public ApiResponse webLoadMap(@Parameter(description = "文件路径") @RequestParam String path) {
|
||||
public ApiResponse loadMap(@Parameter(description = "文件路径") @RequestParam String path) {
|
||||
File osmFile = new File(path);
|
||||
if (!osmFile.exists()) {
|
||||
return ApiResponse.failure("地图文件不存在: " + path);
|
||||
@ -106,6 +106,44 @@ public class GraphHopperController {
|
||||
return ApiResponse.success(null);
|
||||
}
|
||||
|
||||
@Operation(summary = "清除地图服务")
|
||||
@PostMapping("/clearMap")
|
||||
@CheckAuth
|
||||
public ApiResponse clearMap() {
|
||||
// 防止并发操作(与加载操作互斥)
|
||||
if (isLoading.get()) {
|
||||
return ApiResponse.failure("地图正在加载中、无法清除、请稍后再试");
|
||||
}
|
||||
|
||||
// 标记正在处理清除操作
|
||||
isLoading.set(true);
|
||||
try {
|
||||
// 1. 关闭当前GraphHopper实例
|
||||
if (currentHopper != null) {
|
||||
log.info("开始关闭当前地图服务实例");
|
||||
currentHopper.close();
|
||||
currentHopper = null;
|
||||
log.info("地图服务实例已关闭");
|
||||
}
|
||||
|
||||
// 2. 删除地图数据目录
|
||||
log.info("开始删除地图数据目录");
|
||||
deleteOldGraphDir();
|
||||
log.info("地图数据目录已删除");
|
||||
|
||||
// 3. 重置状态变量
|
||||
isLoaded.set(false);
|
||||
|
||||
return ApiResponse.success("地图服务已成功清除");
|
||||
} catch (Exception e) {
|
||||
log.error("清除地图服务失败", e);
|
||||
return ApiResponse.failure("清除地图服务失败: " + e.getMessage());
|
||||
} finally {
|
||||
// 释放操作锁
|
||||
isLoading.set(false);
|
||||
}
|
||||
}
|
||||
|
||||
@Operation(summary = "路径规划")
|
||||
@PostMapping("/route")
|
||||
@CheckAuth
|
||||
@ -126,7 +164,7 @@ public class GraphHopperController {
|
||||
}
|
||||
ghPoints.add(new GHPoint(request.getEndLat(), request.getEndLng())); // 终点
|
||||
|
||||
// 构建请求(仅指定Profile、无setWeighting)
|
||||
// 构建请求
|
||||
String targetProfile = request.getProfile() != null ? request.getProfile() : "car";
|
||||
GHRequest ghRequest = new GHRequest(ghPoints)
|
||||
.setProfile(targetProfile);
|
||||
@ -139,12 +177,14 @@ public class GraphHopperController {
|
||||
// 检查是否有超出范围的错误
|
||||
boolean hasOutOfBoundsError = response.getErrors().stream()
|
||||
.anyMatch(e -> e instanceof com.graphhopper.util.exceptions.PointOutOfBoundsException);
|
||||
|
||||
boolean hasPointNotFoundError = response.getErrors().stream()
|
||||
.anyMatch(e -> e instanceof com.graphhopper.util.exceptions.PointNotFoundException);
|
||||
if (hasOutOfBoundsError) {
|
||||
// 返回超出范围的特定格式响应
|
||||
return ApiResponse.failure("路径超出地图范围");
|
||||
} else if (hasPointNotFoundError) {
|
||||
return ApiResponse.failure("未超地图范围但找不到路径点");
|
||||
} else {
|
||||
return ApiResponse.failure("路径计算失败: " + response.getErrors().toString());
|
||||
return ApiResponse.failure("路径计算异常: " + response.getErrors().get(0).getMessage());
|
||||
}
|
||||
}
|
||||
// 解析结果
|
||||
|
||||
@ -0,0 +1,89 @@
|
||||
package com.yj.earth.business.controller;
|
||||
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.yj.earth.business.domain.PbfInfo;
|
||||
import com.yj.earth.business.service.PbfInfoService;
|
||||
import com.yj.earth.common.util.ApiResponse;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.List;
|
||||
|
||||
@Tag(name = "地图文件管理")
|
||||
@RestController
|
||||
@RequestMapping("/pbfInfo")
|
||||
public class PbfInfoController {
|
||||
|
||||
@Resource
|
||||
private PbfInfoService pbfInfoService;
|
||||
@Resource
|
||||
private GraphHopperController graphHopperController;
|
||||
|
||||
@Operation(summary = "添加地图文件")
|
||||
@PostMapping("/add")
|
||||
public ApiResponse add(@Parameter(description = "地图文件路径") @RequestParam(required = true) String path) {
|
||||
PbfInfo pbfInfo = new PbfInfo();
|
||||
pbfInfo.setPath(path);
|
||||
pbfInfo.setName(FileUtil.mainName(path));
|
||||
pbfInfo.setIsEnable(0);
|
||||
pbfInfoService.save(pbfInfo);
|
||||
return ApiResponse.success(null);
|
||||
}
|
||||
|
||||
@Operation(summary = "删除地图文件")
|
||||
@PostMapping("/delete")
|
||||
public ApiResponse delete(@Parameter(description = "地图文件ID") @RequestParam(required = true) String id) {
|
||||
pbfInfoService.removeById(id);
|
||||
return ApiResponse.success(null);
|
||||
}
|
||||
|
||||
@Operation(summary = "启用地图文件")
|
||||
@PostMapping("/enable")
|
||||
public ApiResponse enable(@Parameter(description = "地图文件ID") @RequestParam(required = true) String id) {
|
||||
// 根据文件ID查询数据
|
||||
PbfInfo pbfInfo = pbfInfoService.getById(id);
|
||||
pbfInfo.setIsEnable(1);
|
||||
pbfInfoService.updateById(pbfInfo);
|
||||
// 调用地图服务
|
||||
graphHopperController.loadMap(pbfInfo.getPath());
|
||||
// 除这条数据以外的全部为禁用
|
||||
List<PbfInfo> list = pbfInfoService.list();
|
||||
for (PbfInfo info : list) {
|
||||
if (!info.getId().equals(id)) {
|
||||
info.setIsEnable(0);
|
||||
pbfInfoService.updateById(info);
|
||||
}
|
||||
}
|
||||
return ApiResponse.success(null);
|
||||
}
|
||||
|
||||
@Operation(summary = "禁用地图文件")
|
||||
@PostMapping("/disable")
|
||||
public ApiResponse disable(@Parameter(description = "地图文件ID") @RequestParam(required = true) String id) {
|
||||
PbfInfo pbfInfo = new PbfInfo();
|
||||
pbfInfo.setId(id);
|
||||
pbfInfo.setIsEnable(0);
|
||||
pbfInfoService.updateById(pbfInfo);
|
||||
graphHopperController.clearMap();
|
||||
return ApiResponse.success(null);
|
||||
}
|
||||
|
||||
@Operation(summary = "获取所有地图文件")
|
||||
@PostMapping("/list")
|
||||
public ApiResponse list() {
|
||||
LambdaQueryWrapper<PbfInfo> queryWrapper = new QueryWrapper<PbfInfo>().lambda();
|
||||
// 把启用的排在最前面
|
||||
queryWrapper.orderByDesc(PbfInfo::getIsEnable);
|
||||
queryWrapper.orderByAsc(PbfInfo::getCreatedAt);
|
||||
return ApiResponse.success(pbfInfoService.list(queryWrapper));
|
||||
}
|
||||
}
|
||||
37
src/main/java/com/yj/earth/business/domain/PbfInfo.java
Normal file
37
src/main/java/com/yj/earth/business/domain/PbfInfo.java
Normal file
@ -0,0 +1,37 @@
|
||||
package com.yj.earth.business.domain;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.FieldFill;
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
@Data
|
||||
public class PbfInfo implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "主键")
|
||||
private String id;
|
||||
@Schema(description = "文件路径")
|
||||
private String path;
|
||||
@Schema(description = "文件名称")
|
||||
private String name;
|
||||
@Schema(description = "是否启用")
|
||||
private Integer isEnable;
|
||||
@Schema(description = "创建时间")
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private LocalDateTime createdAt;
|
||||
@Schema(description = "更新时间")
|
||||
@TableField(fill = FieldFill.UPDATE)
|
||||
private LocalDateTime updatedAt;
|
||||
}
|
||||
@ -0,0 +1,18 @@
|
||||
package com.yj.earth.business.mapper;
|
||||
|
||||
import com.yj.earth.business.domain.PbfInfo;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Mapper 接口
|
||||
* </p>
|
||||
*
|
||||
* @author 周志雄
|
||||
* @since 2025-09-30
|
||||
*/
|
||||
@Mapper
|
||||
public interface PbfInfoMapper extends BaseMapper<PbfInfo> {
|
||||
|
||||
}
|
||||
@ -0,0 +1,16 @@
|
||||
package com.yj.earth.business.service;
|
||||
|
||||
import com.yj.earth.business.domain.PbfInfo;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 服务类
|
||||
* </p>
|
||||
*
|
||||
* @author 周志雄
|
||||
* @since 2025-09-30
|
||||
*/
|
||||
public interface PbfInfoService extends IService<PbfInfo> {
|
||||
|
||||
}
|
||||
@ -0,0 +1,20 @@
|
||||
package com.yj.earth.business.service.impl;
|
||||
|
||||
import com.yj.earth.business.domain.PbfInfo;
|
||||
import com.yj.earth.business.mapper.PbfInfoMapper;
|
||||
import com.yj.earth.business.service.PbfInfoService;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 服务实现类
|
||||
* </p>
|
||||
*
|
||||
* @author 周志雄
|
||||
* @since 2025-09-30
|
||||
*/
|
||||
@Service
|
||||
public class PbfInfoServiceImpl extends ServiceImpl<PbfInfoMapper, PbfInfo> implements PbfInfoService {
|
||||
|
||||
}
|
||||
@ -31,6 +31,7 @@ public class SaTokenConfig implements WebMvcConfigurer {
|
||||
excludePathPatterns.add("/iconLibrary/data/icon/**");
|
||||
excludePathPatterns.add("/militaryLibrary/data/military/**");
|
||||
excludePathPatterns.add("/modelLibrary/data/**");
|
||||
excludePathPatterns.add("/**");
|
||||
|
||||
// 注册 Sa-Token 拦截器
|
||||
registry.addInterceptor(new SaInterceptor(handle -> {
|
||||
|
||||
293
src/main/java/com/yj/earth/common/convert/MilitaryConverter.java
Normal file
293
src/main/java/com/yj/earth/common/convert/MilitaryConverter.java
Normal file
@ -0,0 +1,293 @@
|
||||
package com.yj.earth.common.convert;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.sql.*;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Base64;
|
||||
|
||||
public class MilitaryConverter {
|
||||
private static final String JDBC_DRIVER = "org.sqlite.JDBC";
|
||||
// 源数据库和目标数据库路径
|
||||
private String sourceDbPath;
|
||||
private String targetDbPath;
|
||||
// 批处理大小、可根据内存情况调整
|
||||
private static final int BATCH_SIZE = 100;
|
||||
|
||||
public MilitaryConverter(String sourceDbPath, String targetDbPath) {
|
||||
this.sourceDbPath = sourceDbPath;
|
||||
this.targetDbPath = targetDbPath;
|
||||
}
|
||||
|
||||
public void convert() {
|
||||
Connection sourceConn = null;
|
||||
Connection targetConn = null;
|
||||
try {
|
||||
// 加载驱动
|
||||
Class.forName(JDBC_DRIVER);
|
||||
// 连接源数据库和目标数据库
|
||||
sourceConn = DriverManager.getConnection("jdbc:sqlite:" + sourceDbPath);
|
||||
targetConn = DriverManager.getConnection("jdbc:sqlite:" + targetDbPath);
|
||||
// 禁用自动提交、以便在出现错误时可以回滚
|
||||
targetConn.setAutoCommit(false);
|
||||
// 创建目标表结构
|
||||
createTargetTables(targetConn);
|
||||
// 复制并转换数据
|
||||
copyJunBiaoTypesData(sourceConn, targetConn);
|
||||
copyJunBiaosData(sourceConn, targetConn);
|
||||
// 为military表添加索引
|
||||
createMilitaryTableIndexes(targetConn);
|
||||
// 提交事务
|
||||
targetConn.commit();
|
||||
System.out.println("数据库转换成功!");
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
try {
|
||||
if (targetConn != null) {
|
||||
targetConn.rollback();
|
||||
System.out.println("转换失败、已回滚操作!");
|
||||
}
|
||||
} catch (SQLException ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
} finally {
|
||||
// 关闭连接
|
||||
try {
|
||||
if (sourceConn != null) sourceConn.close();
|
||||
if (targetConn != null) targetConn.close();
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void createTargetTables(Connection conn) throws SQLException {
|
||||
System.out.println("开始创建目标表结构...");
|
||||
Statement stmt = conn.createStatement();
|
||||
// 创建military_type表
|
||||
String sql = """
|
||||
CREATE TABLE "military_type" (
|
||||
"id" TEXT,
|
||||
"name" TEXT,
|
||||
"parent_id" TEXT,
|
||||
"tree_index" INTEGER,
|
||||
"created_at" TEXT,
|
||||
"updated_at" TEXT,
|
||||
PRIMARY KEY ("id")
|
||||
);
|
||||
""";
|
||||
stmt.execute(sql);
|
||||
// 创建military表
|
||||
sql = """
|
||||
CREATE TABLE "military" (
|
||||
"id" TEXT,
|
||||
"military_type_id" TEXT,
|
||||
"military_name" TEXT,
|
||||
"military_type" TEXT,
|
||||
"military_data" BLOB,
|
||||
"created_at" TEXT,
|
||||
"updated_at" TEXT,
|
||||
PRIMARY KEY ("id")
|
||||
);
|
||||
""";
|
||||
stmt.execute(sql);
|
||||
stmt.close();
|
||||
System.out.println("目标表结构创建完成");
|
||||
}
|
||||
|
||||
/**
|
||||
* 为 military 表创建索引
|
||||
*/
|
||||
private void createMilitaryTableIndexes(Connection conn) throws SQLException {
|
||||
System.out.println("开始创建索引...");
|
||||
Statement stmt = conn.createStatement();
|
||||
|
||||
String sql = """
|
||||
CREATE INDEX idx_military_covering ON military(
|
||||
military_type_id,
|
||||
id,
|
||||
military_name,
|
||||
military_type,
|
||||
created_at,
|
||||
updated_at
|
||||
);
|
||||
""";
|
||||
stmt.execute(sql);
|
||||
|
||||
stmt.close();
|
||||
System.out.println("military表索引创建完成");
|
||||
}
|
||||
|
||||
private int getTotalRecords(Connection conn, String tableName) throws SQLException {
|
||||
// 只统计未删除的数据
|
||||
PreparedStatement stmt = conn.prepareStatement(
|
||||
"SELECT COUNT(*) AS total FROM " + tableName + " WHERE deleted_at IS NULL"
|
||||
);
|
||||
ResultSet rs = stmt.executeQuery();
|
||||
int total = rs.next() ? rs.getInt("total") : 0;
|
||||
rs.close();
|
||||
stmt.close();
|
||||
return total;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从jun_biao_types表复制数据到military_type表
|
||||
*/
|
||||
private void copyJunBiaoTypesData(Connection sourceConn, Connection targetConn) throws SQLException {
|
||||
int totalRecords = getTotalRecords(sourceConn, "jun_biao_types");
|
||||
System.out.println("开始转换 jun_biao_types 表数据、共" + totalRecords + "条记录");
|
||||
|
||||
// 查询未删除的类型数据
|
||||
PreparedStatement sourceStmt = sourceConn.prepareStatement(
|
||||
"SELECT * FROM jun_biao_types WHERE deleted_at IS NULL"
|
||||
);
|
||||
ResultSet rs = sourceStmt.executeQuery();
|
||||
|
||||
PreparedStatement targetStmt = targetConn.prepareStatement(
|
||||
"INSERT INTO military_type (id, name, parent_id, tree_index, created_at, updated_at) " +
|
||||
"VALUES (?, ?, ?, ?, ?, ?)"
|
||||
);
|
||||
int count = 0;
|
||||
|
||||
while (rs.next()) {
|
||||
targetStmt.setString(1, rs.getString("type_id"));
|
||||
targetStmt.setString(2, rs.getString("type_name"));
|
||||
if ("-1".equals(rs.getString("p_id"))) {
|
||||
targetStmt.setNull(3, Types.VARCHAR);
|
||||
} else {
|
||||
targetStmt.setString(3, rs.getString("p_id"));
|
||||
targetStmt.setString(3, rs.getString("p_id"));
|
||||
}
|
||||
targetStmt.setInt(4, 0);
|
||||
targetStmt.setObject(5, LocalDateTime.now());
|
||||
targetStmt.setObject(6, LocalDateTime.now());
|
||||
|
||||
// 添加到批处理
|
||||
targetStmt.addBatch();
|
||||
count++;
|
||||
|
||||
// 每达到批处理大小或最后一条记录时执行批处理
|
||||
if (count % BATCH_SIZE == 0 || count == totalRecords) {
|
||||
targetStmt.executeBatch();
|
||||
displayProgress(count, totalRecords, "jun_biao_types 表");
|
||||
}
|
||||
}
|
||||
|
||||
System.out.println("\n成功转换 jun_biao_types 表数据:" + count + "条记录");
|
||||
rs.close();
|
||||
sourceStmt.close();
|
||||
targetStmt.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* 从jun_biaos表复制数据到military表
|
||||
*/
|
||||
private void copyJunBiaosData(Connection sourceConn, Connection targetConn) throws SQLException {
|
||||
int totalRecords = getTotalRecords(sourceConn, "jun_biaos");
|
||||
System.out.println("开始转换 jun_biaos 表数据、共" + totalRecords + "条记录");
|
||||
|
||||
// 对于大字段、使用向前滚动的结果集、避免缓存所有数据
|
||||
PreparedStatement sourceStmt = sourceConn.prepareStatement(
|
||||
"SELECT * FROM jun_biaos WHERE deleted_at IS NULL",
|
||||
ResultSet.TYPE_FORWARD_ONLY,
|
||||
ResultSet.CONCUR_READ_ONLY
|
||||
);
|
||||
sourceStmt.setFetchSize(100);
|
||||
ResultSet rs = sourceStmt.executeQuery();
|
||||
|
||||
PreparedStatement targetStmt = targetConn.prepareStatement(
|
||||
"INSERT INTO military (id, military_type_id, military_name, military_type, military_data, " +
|
||||
"created_at, updated_at) " +
|
||||
"VALUES (?, ?, ?, ?, ?, ?, ?)"
|
||||
);
|
||||
|
||||
int count = 0;
|
||||
while (rs.next()) {
|
||||
targetStmt.setString(1, rs.getString("jun_biao_id")); // 关联ID对应
|
||||
targetStmt.setString(2, rs.getString("p_id")); // 类型ID对应
|
||||
targetStmt.setString(3, rs.getString("name")); // 名称对应
|
||||
targetStmt.setString(4, "svg"); // 内容类型对应
|
||||
byte[] dataBytes = rs.getBytes("data"); // 二进制数据对应
|
||||
if (dataBytes != null) {
|
||||
targetStmt.setBytes(5, convertBase64ToPlainSvg(dataBytes));
|
||||
} else {
|
||||
targetStmt.setNull(5, Types.BLOB);
|
||||
}
|
||||
targetStmt.setObject(6, LocalDateTime.now()); // 使用原始创建时间
|
||||
targetStmt.setObject(7, LocalDateTime.now()); // 使用原始更新时间
|
||||
|
||||
// 添加到批处理
|
||||
targetStmt.addBatch();
|
||||
count++;
|
||||
// 执行批处理
|
||||
if (count % BATCH_SIZE == 0 || count == totalRecords) {
|
||||
targetStmt.executeBatch();
|
||||
displayProgress(count, totalRecords, "jun_biaos 表");
|
||||
}
|
||||
}
|
||||
|
||||
System.out.println("\n成功转换 jun_biaos 表数据:" + count + "条记录");
|
||||
rs.close();
|
||||
sourceStmt.close();
|
||||
targetStmt.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示进度信息
|
||||
*/
|
||||
private void displayProgress(int current, int total, String tableName) {
|
||||
double percentage = (double) current / total * 100;
|
||||
// 清除当前行并显示进度
|
||||
System.out.printf("\r%s: 已完成 %.1f%% (%d/%d条)", tableName, percentage, current, total);
|
||||
}
|
||||
|
||||
|
||||
// Base64 SVG的前缀标识
|
||||
private static final String SVG_BASE64_PREFIX = "data:image/svg+xml;base64,";
|
||||
|
||||
/**
|
||||
* 将Base64格式的SVG字节数组转换为明文SVG字节数组
|
||||
*
|
||||
* @param base64SvgBytes 包含Base64编码的SVG字节数组(可带前缀)
|
||||
* @return 解码后的明文SVG字节数组
|
||||
* @throws IllegalArgumentException 如果输入不是有效的Base64格式或为空
|
||||
*/
|
||||
public static byte[] convertBase64ToPlainSvg(byte[] base64SvgBytes) {
|
||||
// 验证输入参数
|
||||
if (base64SvgBytes == null || base64SvgBytes.length == 0) {
|
||||
throw new IllegalArgumentException("输入的Base64 SVG字节数组不能为空");
|
||||
}
|
||||
|
||||
// 将字节数组转换为字符串、处理可能存在的前缀
|
||||
String base64SvgStr = new String(base64SvgBytes, StandardCharsets.UTF_8);
|
||||
String pureBase64Str = base64SvgStr;
|
||||
|
||||
// 移除前缀(如果存在)
|
||||
if (base64SvgStr.startsWith(SVG_BASE64_PREFIX)) {
|
||||
pureBase64Str = base64SvgStr.substring(SVG_BASE64_PREFIX.length());
|
||||
}
|
||||
|
||||
try {
|
||||
// 执行Base64解码并返回字节数组
|
||||
return Base64.getDecoder().decode(pureBase64Str);
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new IllegalArgumentException("无效的Base64格式: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
// 源数据库路径
|
||||
String sourcePath = "F:\\YJEarth新.junbiao";
|
||||
// 目标数据库路径
|
||||
String targetPath = "F:\\通用军标库.junbiao";
|
||||
System.out.println("开始数据库转换...");
|
||||
System.out.println("源数据库: " + sourcePath);
|
||||
System.out.println("目标数据库: " + targetPath);
|
||||
long startTime = System.currentTimeMillis();
|
||||
// 创建转换器并执行转换
|
||||
MilitaryConverter converter = new MilitaryConverter(sourcePath, targetPath);
|
||||
converter.convert();
|
||||
long endTime = System.currentTimeMillis();
|
||||
double elapsedTime = (endTime - startTime) / 1000.0;
|
||||
System.out.printf("转换完成、耗时: %.2f秒%n", elapsedTime);
|
||||
}
|
||||
}
|
||||
@ -1,11 +1,9 @@
|
||||
package com.yj.earth.common.util;
|
||||
|
||||
import cn.hutool.core.lang.UUID;
|
||||
package com.yj.earth.common.convert;
|
||||
|
||||
import java.sql.*;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
public class SQLiteConverter {
|
||||
public class ModelConverter {
|
||||
private static final String JDBC_DRIVER = "org.sqlite.JDBC";
|
||||
// 源数据库和目标数据库路径
|
||||
private String sourceDbPath;
|
||||
@ -13,7 +11,7 @@ public class SQLiteConverter {
|
||||
// 批处理大小、可根据内存情况调整
|
||||
private static final int BATCH_SIZE = 100;
|
||||
|
||||
public SQLiteConverter(String sourceDbPath, String targetDbPath) {
|
||||
public ModelConverter(String sourceDbPath, String targetDbPath) {
|
||||
this.sourceDbPath = sourceDbPath;
|
||||
this.targetDbPath = targetDbPath;
|
||||
}
|
||||
@ -236,7 +234,7 @@ public class SQLiteConverter {
|
||||
System.out.println("目标数据库: " + targetPath);
|
||||
long startTime = System.currentTimeMillis();
|
||||
// 创建转换器并执行转换
|
||||
SQLiteConverter converter = new SQLiteConverter(sourcePath, targetPath);
|
||||
ModelConverter converter = new ModelConverter(sourcePath, targetPath);
|
||||
converter.convert();
|
||||
long endTime = System.currentTimeMillis();
|
||||
double elapsedTime = (endTime - startTime) / 1000.0;
|
||||
@ -34,7 +34,7 @@ public class CodeUtil {
|
||||
}
|
||||
|
||||
// 传入需要生成代码的表名
|
||||
Generation("web_source");
|
||||
Generation("pbf_info");
|
||||
}
|
||||
|
||||
public static void Generation(String... tableName) {
|
||||
|
||||
470
src/main/java/com/yj/earth/common/util/GdalJsonConverter.java
Normal file
470
src/main/java/com/yj/earth/common/util/GdalJsonConverter.java
Normal file
@ -0,0 +1,470 @@
|
||||
package com.yj.earth.common.util;
|
||||
|
||||
import org.gdal.gdal.gdal;
|
||||
import org.gdal.ogr.*;
|
||||
import org.gdal.osr.CoordinateTransformation;
|
||||
import org.gdal.osr.SpatialReference;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.gdal.ogr.ogrConstants.*;
|
||||
|
||||
/**
|
||||
* GDAL地理数据转JSON工具类
|
||||
*/
|
||||
import org.gdal.gdal.gdal;
|
||||
import org.gdal.ogr.*;
|
||||
import org.gdal.osr.SpatialReference;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* GDAL地理数据转JSON工具类
|
||||
*/
|
||||
public class GdalJsonConverter {
|
||||
|
||||
static {
|
||||
// 初始化GDAL
|
||||
gdal.AllRegister();
|
||||
}
|
||||
|
||||
/**
|
||||
* 将地理数据文件转换为指定结构的JSON
|
||||
* @param filePath 文件绝对路径
|
||||
* @return JSON字符串
|
||||
*/
|
||||
public static String convertToJson(String filePath) {
|
||||
return convertToJson(filePath, "");
|
||||
}
|
||||
|
||||
/**
|
||||
* 将地理数据文件转换为指定结构的JSON
|
||||
* @param filePath 文件绝对路径
|
||||
* @param targetCrs 目标坐标系(如:"EPSG:4326"),为空则不转换
|
||||
* @return JSON字符串
|
||||
*/
|
||||
public static String convertToJson(String filePath, String targetCrs) {
|
||||
try {
|
||||
// 打开数据源
|
||||
DataSource dataSource = ogr.Open(filePath, 0);
|
||||
if (dataSource == null) {
|
||||
throw new RuntimeException("无法打开文件: " + filePath);
|
||||
}
|
||||
|
||||
JSONObject result = new JSONObject();
|
||||
JSONArray listArray = new JSONArray();
|
||||
|
||||
// 获取所有图层
|
||||
int layerCount = dataSource.GetLayerCount();
|
||||
for (int i = 0; i < layerCount; i++) {
|
||||
Layer layer = dataSource.GetLayer(i);
|
||||
String layerName = layer.GetName();
|
||||
|
||||
JSONObject featureCollection = createFeatureCollection(layer, layerName, targetCrs);
|
||||
if (featureCollection != null) {
|
||||
listArray.put(featureCollection);
|
||||
}
|
||||
}
|
||||
|
||||
result.put("list", listArray);
|
||||
dataSource.delete();
|
||||
|
||||
return result.toString(2); // 缩进2个空格,美化输出
|
||||
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("转换失败: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建FeatureCollection对象
|
||||
*/
|
||||
private static JSONObject createFeatureCollection(Layer layer, String layerName, String targetCrs) throws JSONException {
|
||||
JSONObject featureCollection = new JSONObject();
|
||||
featureCollection.put("type", "FeatureCollection");
|
||||
featureCollection.put("name", layerName);
|
||||
|
||||
// 获取源坐标系
|
||||
String crsSrc = getLayerCrs(layer);
|
||||
featureCollection.put("crs_src", crsSrc != null ? crsSrc : "");
|
||||
|
||||
// 设置目标坐标系
|
||||
featureCollection.put("crs_dst", targetCrs != null ? targetCrs : "");
|
||||
|
||||
JSONArray featuresArray = new JSONArray();
|
||||
Feature feature;
|
||||
|
||||
// 重置图层读取位置
|
||||
layer.ResetReading();
|
||||
|
||||
// 获取图层定义,用于字段信息
|
||||
FeatureDefn layerDefn = layer.GetLayerDefn();
|
||||
|
||||
// 如果需要坐标转换,创建坐标转换对象
|
||||
SpatialReference sourceSrs = layer.GetSpatialRef();
|
||||
SpatialReference targetSrs = null;
|
||||
CoordinateTransformation coordTrans = null;
|
||||
|
||||
if (targetCrs != null && !targetCrs.isEmpty() && sourceSrs != null) {
|
||||
try {
|
||||
targetSrs = new SpatialReference();
|
||||
if (targetCrs.startsWith("EPSG:")) {
|
||||
int epsgCode = Integer.parseInt(targetCrs.substring(5));
|
||||
targetSrs.ImportFromEPSG(epsgCode);
|
||||
} else {
|
||||
targetSrs.ImportFromProj4(targetCrs);
|
||||
}
|
||||
coordTrans = new CoordinateTransformation(sourceSrs, targetSrs);
|
||||
} catch (Exception e) {
|
||||
System.err.println("坐标转换初始化失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
while ((feature = layer.GetNextFeature()) != null) {
|
||||
JSONObject featureObj = createFeature(feature, layerDefn, coordTrans, targetSrs != null);
|
||||
if (featureObj != null) {
|
||||
featuresArray.put(featureObj);
|
||||
}
|
||||
feature.delete();
|
||||
}
|
||||
|
||||
// 清理资源
|
||||
if (coordTrans != null) {
|
||||
coordTrans.delete();
|
||||
}
|
||||
if (targetSrs != null) {
|
||||
targetSrs.delete();
|
||||
}
|
||||
|
||||
featureCollection.put("features", featuresArray);
|
||||
return featureCollection;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取图层的坐标系信息
|
||||
*/
|
||||
private static String getLayerCrs(Layer layer) {
|
||||
try {
|
||||
SpatialReference srs = layer.GetSpatialRef();
|
||||
if (srs != null) {
|
||||
// 优先尝试获取PROJ4字符串
|
||||
String proj4 = srs.ExportToProj4();
|
||||
if (proj4 != null && !proj4.trim().isEmpty()) {
|
||||
return proj4.trim();
|
||||
}
|
||||
|
||||
// 如果PROJ4为空,尝试获取WKT
|
||||
String wkt = srs.ExportToWkt();
|
||||
if (wkt != null && !wkt.trim().isEmpty()) {
|
||||
return wkt.trim();
|
||||
}
|
||||
|
||||
// 最后尝试获取EPSG代码
|
||||
String authorityCode = srs.GetAuthorityCode(null);
|
||||
String authorityName = srs.GetAuthorityName(null);
|
||||
if (authorityName != null && authorityCode != null) {
|
||||
return authorityName + ":" + authorityCode;
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
System.err.println("获取坐标系信息失败: " + e.getMessage());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建单个Feature对象
|
||||
*/
|
||||
private static JSONObject createFeature(Feature feature, FeatureDefn layerDefn,
|
||||
CoordinateTransformation coordTrans, boolean transformCoords) throws JSONException {
|
||||
JSONObject featureObj = new JSONObject();
|
||||
featureObj.put("type", "Feature");
|
||||
|
||||
// 处理属性字段
|
||||
JSONObject properties = new JSONObject();
|
||||
int fieldCount = layerDefn.GetFieldCount();
|
||||
|
||||
for (int i = 0; i < fieldCount; i++) {
|
||||
FieldDefn fieldDefn = layerDefn.GetFieldDefn(i);
|
||||
String fieldName = fieldDefn.GetName();
|
||||
Object fieldValue = getFieldValue(feature, fieldDefn, i);
|
||||
|
||||
if (fieldValue != null) {
|
||||
properties.put(fieldName, fieldValue);
|
||||
}
|
||||
}
|
||||
|
||||
featureObj.put("properties", properties);
|
||||
|
||||
// 处理几何图形
|
||||
Geometry geometry = feature.GetGeometryRef();
|
||||
if (geometry != null && !geometry.IsEmpty()) {
|
||||
Geometry geomToUse = geometry;
|
||||
|
||||
// 如果需要坐标转换
|
||||
if (transformCoords && coordTrans != null) {
|
||||
try {
|
||||
Geometry transformedGeom = geometry.Clone();
|
||||
transformedGeom.Transform(coordTrans);
|
||||
geomToUse = transformedGeom;
|
||||
} catch (Exception e) {
|
||||
System.err.println("坐标转换失败: " + e.getMessage());
|
||||
// 如果转换失败,使用原始几何
|
||||
geomToUse = geometry;
|
||||
}
|
||||
}
|
||||
|
||||
JSONObject geometryObj = convertGeometryToJson(geomToUse);
|
||||
featureObj.put("geometry", geometryObj);
|
||||
|
||||
// 清理临时几何对象
|
||||
if (geomToUse != geometry) {
|
||||
geomToUse.delete();
|
||||
}
|
||||
} else {
|
||||
// 如果没有几何信息,创建空的MultiPolygon
|
||||
featureObj.put("geometry", createEmptyMultiPolygon());
|
||||
}
|
||||
|
||||
return featureObj;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取字段值
|
||||
*/
|
||||
private static Object getFieldValue(Feature feature, FieldDefn fieldDefn, int fieldIndex) {
|
||||
int fieldType = fieldDefn.GetFieldType();
|
||||
|
||||
if (feature.IsFieldSet(fieldIndex)) {
|
||||
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:
|
||||
return feature.GetFieldAsString(fieldIndex);
|
||||
case ogr.OFTDate:
|
||||
case ogr.OFTTime:
|
||||
case ogr.OFTDateTime:
|
||||
return feature.GetFieldAsString(fieldIndex);
|
||||
default:
|
||||
return feature.GetFieldAsString(fieldIndex);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将几何图形转换为JSON
|
||||
*/
|
||||
private static JSONObject convertGeometryToJson(Geometry geometry) throws JSONException {
|
||||
JSONObject geometryObj = new JSONObject();
|
||||
String geometryType = geometry.GetGeometryName();
|
||||
|
||||
// 根据几何类型处理
|
||||
switch (geometry.GetGeometryType()) {
|
||||
case wkbPoint:
|
||||
case wkbPoint25D:
|
||||
geometryObj.put("type", "Point");
|
||||
geometryObj.put("coordinates", getPointCoordinates(geometry));
|
||||
break;
|
||||
case wkbLineString:
|
||||
case wkbLineString25D:
|
||||
geometryObj.put("type", "LineString");
|
||||
geometryObj.put("coordinates", getLineStringCoordinates(geometry));
|
||||
break;
|
||||
case wkbPolygon:
|
||||
case wkbPolygon25D:
|
||||
geometryObj.put("type", "Polygon");
|
||||
geometryObj.put("coordinates", getPolygonCoordinates(geometry));
|
||||
break;
|
||||
case wkbMultiPoint:
|
||||
case wkbMultiPoint25D:
|
||||
geometryObj.put("type", "MultiPoint");
|
||||
geometryObj.put("coordinates", getMultiPointCoordinates(geometry));
|
||||
break;
|
||||
case wkbMultiLineString:
|
||||
case wkbMultiLineString25D:
|
||||
geometryObj.put("type", "MultiLineString");
|
||||
geometryObj.put("coordinates", getMultiLineStringCoordinates(geometry));
|
||||
break;
|
||||
case wkbMultiPolygon:
|
||||
case wkbMultiPolygon25D:
|
||||
geometryObj.put("type", "MultiPolygon");
|
||||
geometryObj.put("coordinates", getMultiPolygonCoordinates(geometry));
|
||||
break;
|
||||
case wkbGeometryCollection:
|
||||
case wkbGeometryCollection25D:
|
||||
geometryObj.put("type", "GeometryCollection");
|
||||
geometryObj.put("geometries", getGeometryCollection(geometry));
|
||||
break;
|
||||
default:
|
||||
// 默认使用WKT格式
|
||||
geometryObj.put("type", geometryType);
|
||||
geometryObj.put("wkt", geometry.ExportToWkt());
|
||||
break;
|
||||
}
|
||||
|
||||
return geometryObj;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取点坐标
|
||||
*/
|
||||
private static JSONArray getPointCoordinates(Geometry geometry) throws JSONException {
|
||||
JSONArray coords = new JSONArray();
|
||||
coords.put(geometry.GetX());
|
||||
coords.put(geometry.GetY());
|
||||
if (geometry.GetGeometryType() == wkbPoint25D) {
|
||||
coords.put(geometry.GetZ());
|
||||
}
|
||||
return coords;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取线坐标
|
||||
*/
|
||||
private static JSONArray getLineStringCoordinates(Geometry geometry) throws JSONException {
|
||||
JSONArray coords = new JSONArray();
|
||||
int pointCount = geometry.GetPointCount();
|
||||
for (int i = 0; i < pointCount; i++) {
|
||||
JSONArray point = new JSONArray();
|
||||
point.put(geometry.GetX(i));
|
||||
point.put(geometry.GetY(i));
|
||||
if (geometry.GetGeometryType() == wkbLineString25D) {
|
||||
point.put(geometry.GetZ(i));
|
||||
}
|
||||
coords.put(point);
|
||||
}
|
||||
return coords;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取多边形坐标
|
||||
*/
|
||||
private static JSONArray getPolygonCoordinates(Geometry geometry) throws JSONException {
|
||||
JSONArray rings = new JSONArray();
|
||||
|
||||
// 外环
|
||||
Geometry exteriorRing = geometry.GetGeometryRef(0);
|
||||
rings.put(getLineStringCoordinates(exteriorRing));
|
||||
|
||||
// 内环(如果有)
|
||||
int ringCount = geometry.GetGeometryCount();
|
||||
for (int i = 1; i < ringCount; i++) {
|
||||
Geometry interiorRing = geometry.GetGeometryRef(i);
|
||||
rings.put(getLineStringCoordinates(interiorRing));
|
||||
}
|
||||
|
||||
return rings;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取多点坐标
|
||||
*/
|
||||
private static JSONArray getMultiPointCoordinates(Geometry geometry) throws JSONException {
|
||||
JSONArray points = new JSONArray();
|
||||
int geometryCount = geometry.GetGeometryCount();
|
||||
for (int i = 0; i < geometryCount; i++) {
|
||||
Geometry point = geometry.GetGeometryRef(i);
|
||||
points.put(getPointCoordinates(point));
|
||||
}
|
||||
return points;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取多线坐标
|
||||
*/
|
||||
private static JSONArray getMultiLineStringCoordinates(Geometry geometry) throws JSONException {
|
||||
JSONArray lines = new JSONArray();
|
||||
int geometryCount = geometry.GetGeometryCount();
|
||||
for (int i = 0; i < geometryCount; i++) {
|
||||
Geometry line = geometry.GetGeometryRef(i);
|
||||
lines.put(getLineStringCoordinates(line));
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取多多边形坐标
|
||||
*/
|
||||
private static JSONArray getMultiPolygonCoordinates(Geometry geometry) throws JSONException {
|
||||
JSONArray polygons = new JSONArray();
|
||||
int geometryCount = geometry.GetGeometryCount();
|
||||
for (int i = 0; i < geometryCount; i++) {
|
||||
Geometry polygon = geometry.GetGeometryRef(i);
|
||||
polygons.put(getPolygonCoordinates(polygon));
|
||||
}
|
||||
return polygons;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取几何集合
|
||||
*/
|
||||
private static JSONArray getGeometryCollection(Geometry geometry) throws JSONException {
|
||||
JSONArray geometries = new JSONArray();
|
||||
int geometryCount = geometry.GetGeometryCount();
|
||||
for (int i = 0; i < geometryCount; i++) {
|
||||
Geometry geom = geometry.GetGeometryRef(i);
|
||||
geometries.put(convertGeometryToJson(geom));
|
||||
}
|
||||
return geometries;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建空的MultiPolygon几何
|
||||
*/
|
||||
private static JSONObject createEmptyMultiPolygon() throws JSONException {
|
||||
JSONObject geometry = new JSONObject();
|
||||
geometry.put("type", "MultiPolygon");
|
||||
geometry.put("coordinates", new JSONArray());
|
||||
return geometry;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文件信息(辅助方法)
|
||||
*/
|
||||
public static Map<String, Object> getFileInfo(String filePath) {
|
||||
Map<String, Object> info = new HashMap<>();
|
||||
try {
|
||||
DataSource dataSource = ogr.Open(filePath, 0);
|
||||
if (dataSource != null) {
|
||||
info.put("driver", dataSource.GetDriver().GetName());
|
||||
info.put("layerCount", dataSource.GetLayerCount());
|
||||
|
||||
List<Map<String, Object>> layers = new ArrayList<>();
|
||||
for (int i = 0; i < dataSource.GetLayerCount(); i++) {
|
||||
Layer layer = dataSource.GetLayer(i);
|
||||
Map<String, Object> layerInfo = new HashMap<>();
|
||||
layerInfo.put("name", layer.GetName());
|
||||
layerInfo.put("featureCount", layer.GetFeatureCount());
|
||||
layerInfo.put("geometryType", ogr.GeometryTypeToName(layer.GetGeomType()));
|
||||
|
||||
// 添加坐标系信息
|
||||
String crs = getLayerCrs(layer);
|
||||
layerInfo.put("crs", crs != null ? crs : "未知");
|
||||
|
||||
layers.add(layerInfo);
|
||||
}
|
||||
info.put("layers", layers);
|
||||
dataSource.delete();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
info.put("error", e.getMessage());
|
||||
}
|
||||
return info;
|
||||
}
|
||||
}
|
||||
@ -1,247 +0,0 @@
|
||||
package com.yj.earth.common.util;
|
||||
|
||||
import org.gdal.gdal.gdal;
|
||||
import org.gdal.ogr.*;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
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
|
||||
*/
|
||||
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) {
|
||||
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++) {
|
||||
properties.put(fieldNames[j], getFieldValue(feature, j, fieldTypes[j]));
|
||||
}
|
||||
featureMap.put("properties", properties);
|
||||
|
||||
// 处理几何信息 - 直接构建Map而不是先转JSON再解析
|
||||
Geometry geometry = feature.GetGeometryRef();
|
||||
featureMap.put("geometry", convertGeometryToMap(geometry));
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
try {
|
||||
String geometryType = geometry.GetGeometryName();
|
||||
Map<String, Object> geometryMap = new HashMap<>(2);
|
||||
|
||||
// 如果不是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;
|
||||
}
|
||||
}
|
||||
@ -89,7 +89,8 @@ public class SQLiteUtil {
|
||||
stmt.execute("PRAGMA temp_store=MEMORY;"); // 临时表/索引存内存(减少磁盘IO)
|
||||
stmt.execute("PRAGMA busy_timeout=2000;"); // 忙等待超时:2秒(避免瞬时并发锁等待)
|
||||
} catch (SQLException e) {
|
||||
throw new RuntimeException("初始化SQLite数据源失败(路径:" + dbFilePath + ")", e);
|
||||
e.printStackTrace();
|
||||
throw new RuntimeException("初始化SQLite数据源失败(路径:" + dbFilePath + ")+ 原因是:" + e.getMessage());
|
||||
}
|
||||
|
||||
return dataSource;
|
||||
|
||||
@ -52,6 +52,7 @@ public class DatabaseManager {
|
||||
classes.add(IconLibrary.class);
|
||||
classes.add(BusinessConfig.class);
|
||||
classes.add(WebSource.class);
|
||||
classes.add(PbfInfo.class);
|
||||
ENTITY_CLASSES = Collections.unmodifiableList(classes);
|
||||
}
|
||||
|
||||
|
||||
22
src/main/java/com/yj/earth/design/PbfInfo.java
Normal file
22
src/main/java/com/yj/earth/design/PbfInfo.java
Normal file
@ -0,0 +1,22 @@
|
||||
package com.yj.earth.design;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
public class PbfInfo {
|
||||
@Schema(description = "主键")
|
||||
private String id;
|
||||
@Schema(description = "文件路径")
|
||||
private String path;
|
||||
@Schema(description = "文件名称")
|
||||
private String name;
|
||||
@Schema(description = "是否启用")
|
||||
private Integer isEnable;
|
||||
@Schema(description = "创建时间")
|
||||
private LocalDateTime createdAt;
|
||||
@Schema(description = "更新时间")
|
||||
private LocalDateTime updatedAt;
|
||||
}
|
||||
@ -3,12 +3,37 @@ package com.yj.earth.model;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
public class RouteResponse {
|
||||
private Double distanceKm; // 距离(公里)
|
||||
private Double timeMinutes; // 时间(分钟)
|
||||
private List<Point> pathPoints; // 路径点列表
|
||||
private List<RouteInfo> routes;
|
||||
|
||||
public RouteResponse() {}
|
||||
|
||||
public RouteResponse(Double distance, Double time, List<Point> points) {
|
||||
this.routes = new ArrayList<>();
|
||||
this.routes.add(new RouteInfo(distance, time, points));
|
||||
}
|
||||
|
||||
// 多条路线的构造函数
|
||||
public RouteResponse(List<RouteInfo> routes) {
|
||||
this.routes = routes;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class RouteInfo {
|
||||
private Double distance; // 公里
|
||||
private Double time; // 分钟
|
||||
private List<Point> points;
|
||||
|
||||
public RouteInfo() {}
|
||||
|
||||
public RouteInfo(Double distance, Double time, List<Point> points) {
|
||||
this.distance = distance;
|
||||
this.time = time;
|
||||
this.points = points;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,7 +8,7 @@ import lombok.Data;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
public class MilitaryVo extends Military {
|
||||
public class MilitaryVo{
|
||||
@Schema(description = "军标类型名称")
|
||||
private String militaryTypeName;
|
||||
@Schema(description = "军标数据URL")
|
||||
|
||||
@ -46,4 +46,4 @@ graphhopper:
|
||||
- foot
|
||||
|
||||
sync:
|
||||
folder: E:\\地理资源
|
||||
folder: F:\POI
|
||||
|
||||
123
src/main/resources/data.json
Normal file
123
src/main/resources/data.json
Normal file
@ -0,0 +1,123 @@
|
||||
{
|
||||
"list": [
|
||||
{
|
||||
"type": "FeatureCollection",
|
||||
"name": "JobDetails",
|
||||
"features": [
|
||||
{
|
||||
"type": "Feature",
|
||||
"properties": {
|
||||
"JobDetails": "执行(Manage Map Cache Tiles): ManageMapCacheTiles 商河电子地图:MapServer 2254.4677204803465;1127.2338602401733;563.61693012008664 3 RECREATE_ALL_TILES \"-180 -90 180 90\" # 要素集",
|
||||
"JobID": "j140c6b943560494f9ac1ec04f87b9593",
|
||||
"JobType": "RECREATE_ALL_TILES",
|
||||
"NumThreads": 3,
|
||||
"Scales": "2254.4677204803465;1127.2338602401733;563.61693012008664",
|
||||
"Shape_Area": 64799.99999999998,
|
||||
"Shape_Length": 1080
|
||||
},
|
||||
"geometry": {
|
||||
"type": "MultiPolygon",
|
||||
"coordinates": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Feature",
|
||||
"properties": {
|
||||
"JobDetails": "执行(Manage Map Cache Tiles): ManageMapCacheTiles 商河电子地图:MapServer 2254.4677204803465;1127.2338602401733;563.61693012008664 3 RECREATE_ALL_TILES \"116.9772866 37.112894162 117.439131151 37.53690506\" # 要素集",
|
||||
"JobID": "je0622d35aeda4b8592e766171053dbbd",
|
||||
"JobType": "RECREATE_ALL_TILES",
|
||||
"NumThreads": 3,
|
||||
"Scales": "2254.4677204803465;1127.2338602401733;563.61693012008664",
|
||||
"Shape_Area": 0.11797651104571212,
|
||||
"Shape_Length": 2.9442372034773885
|
||||
},
|
||||
"geometry": {
|
||||
"type": "MultiPolygon",
|
||||
"coordinates": []
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "FeatureCollection",
|
||||
"name": "JobStatus",
|
||||
"features": [
|
||||
{
|
||||
"type": "Feature",
|
||||
"properties": {
|
||||
"ActualTiles": 2326528,
|
||||
"CancelJobID": "j140c6b943560494f9ac1ec04f87b9593",
|
||||
"DoneTasks": 142,
|
||||
"EndTime": "2023/10/13 14:32:09+00",
|
||||
"ExpectedTiles": 0,
|
||||
"FailedTasks": 0,
|
||||
"JobID": "j140c6b943560494f9ac1ec04f87b9593",
|
||||
"JobStatus": "CANCELED",
|
||||
"JobType": "RECREATE_ALL_TILES",
|
||||
"PercentComplete": 0,
|
||||
"Scale": 2254.4677204803465,
|
||||
"Shape_Area": 64799.99999999998,
|
||||
"Shape_Length": 1080,
|
||||
"StartTime": "2023/10/13 12:31:12+00",
|
||||
"TotalTasks": 2097152,
|
||||
"percent": 0
|
||||
},
|
||||
"geometry": {
|
||||
"type": "MultiPolygon",
|
||||
"coordinates": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Feature",
|
||||
"properties": {
|
||||
"ActualTiles": 0,
|
||||
"CancelJobID": "j140c6b943560494f9ac1ec04f87b9593",
|
||||
"DoneTasks": 0,
|
||||
"EndTime": "2023/10/13 14:32:09+00",
|
||||
"ExpectedTiles": 0,
|
||||
"FailedTasks": 0,
|
||||
"JobID": "j140c6b943560494f9ac1ec04f87b9593",
|
||||
"JobStatus": "CANCELED",
|
||||
"JobType": "RECREATE_ALL_TILES",
|
||||
"PercentComplete": 0,
|
||||
"Scale": 1127.2338602401733,
|
||||
"Shape_Area": 64799.99999999998,
|
||||
"Shape_Length": 1080,
|
||||
"StartTime": "2023/10/13 12:31:12+00",
|
||||
"TotalTasks": 8388608,
|
||||
"percent": 0
|
||||
},
|
||||
"geometry": {
|
||||
"type": "MultiPolygon",
|
||||
"coordinates": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Feature",
|
||||
"properties": {
|
||||
"ActualTiles": 0,
|
||||
"CancelJobID": "j140c6b943560494f9ac1ec04f87b9593",
|
||||
"DoneTasks": 0,
|
||||
"EndTime": "2023/10/13 14:32:09+00",
|
||||
"ExpectedTiles": 0,
|
||||
"FailedTasks": 0,
|
||||
"JobID": "j140c6b943560494f9ac1ec04f87b9593",
|
||||
"JobStatus": "CANCELED",
|
||||
"JobType": "RECREATE_ALL_TILES",
|
||||
"PercentComplete": 0,
|
||||
"Scale": 563.6169301200866,
|
||||
"Shape_Area": 64799.99999999998,
|
||||
"Shape_Length": 1080,
|
||||
"StartTime": "2023/10/13 12:31:12+00",
|
||||
"TotalTasks": 33554432,
|
||||
"percent": 0
|
||||
},
|
||||
"geometry": {
|
||||
"type": "MultiPolygon",
|
||||
"coordinates": []
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
20
src/main/resources/mapper/PbfInfoMapper.xml
Normal file
20
src/main/resources/mapper/PbfInfoMapper.xml
Normal file
@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.yj.earth.business.mapper.PbfInfoMapper">
|
||||
|
||||
<!-- 通用查询映射结果 -->
|
||||
<resultMap id="BaseResultMap" type="com.yj.earth.business.domain.PbfInfo">
|
||||
<id column="id" property="id" />
|
||||
<result column="path" property="path" />
|
||||
<result column="name" property="name" />
|
||||
<result column="is_enable" property="isEnable" />
|
||||
<result column="created_at" property="createdAt" />
|
||||
<result column="updated_at" property="updatedAt" />
|
||||
</resultMap>
|
||||
|
||||
<!-- 通用查询结果列 -->
|
||||
<sql id="Base_Column_List">
|
||||
id, path, name, is_enable, created_at, updated_at
|
||||
</sql>
|
||||
|
||||
</mapper>
|
||||
Reference in New Issue
Block a user