|
@@ -0,0 +1,709 @@
|
|
|
|
|
+package com.ruoyi.web.controller.cesium;
|
|
|
|
|
+
|
|
|
|
|
+import java.io.File;
|
|
|
|
|
+import java.io.FileInputStream;
|
|
|
|
|
+import java.io.FileWriter;
|
|
|
|
|
+import java.io.IOException;
|
|
|
|
|
+import java.io.BufferedReader;
|
|
|
|
|
+import java.io.InputStreamReader;
|
|
|
|
|
+import java.nio.charset.Charset;
|
|
|
|
|
+import java.nio.charset.StandardCharsets;
|
|
|
|
|
+import java.util.ArrayList;
|
|
|
|
|
+import java.util.HashMap;
|
|
|
|
|
+import java.util.List;
|
|
|
|
|
+import java.util.Map;
|
|
|
|
|
+import java.util.UUID;
|
|
|
|
|
+import java.util.zip.ZipEntry;
|
|
|
|
|
+import java.util.zip.ZipInputStream;
|
|
|
|
|
+import org.slf4j.Logger;
|
|
|
|
|
+import org.slf4j.LoggerFactory;
|
|
|
|
|
+import org.springframework.beans.factory.annotation.Autowired;
|
|
|
|
|
+import org.springframework.web.bind.annotation.*;
|
|
|
|
|
+import org.springframework.web.multipart.MultipartFile;
|
|
|
|
|
+import com.ruoyi.common.annotation.Log;
|
|
|
|
|
+import com.ruoyi.common.config.RuoYiConfig;
|
|
|
|
|
+import com.ruoyi.common.core.controller.BaseController;
|
|
|
|
|
+import com.ruoyi.common.core.domain.AjaxResult;
|
|
|
|
|
+import com.ruoyi.common.enums.BusinessType;
|
|
|
|
|
+import com.ruoyi.common.utils.StringUtils;
|
|
|
|
|
+import com.ruoyi.system.domain.WatershedService;
|
|
|
|
|
+import com.ruoyi.system.mapper.WatershedServiceMapper;
|
|
|
|
|
+
|
|
|
|
|
+@RestController
|
|
|
|
|
+@RequestMapping("/cesium/geojson")
|
|
|
|
|
+public class CesiumGeojsonController extends BaseController
|
|
|
|
|
+{
|
|
|
|
|
+ private static final Logger logger = LoggerFactory.getLogger(CesiumGeojsonController.class);
|
|
|
|
|
+
|
|
|
|
|
+ private int sourceSrid = 0;
|
|
|
|
|
+ private String sourceProjection = null;
|
|
|
|
|
+
|
|
|
|
|
+ @Autowired
|
|
|
|
|
+ private WatershedServiceMapper watershedServiceMapper;
|
|
|
|
|
+
|
|
|
|
|
+ @PostMapping("/upload")
|
|
|
|
|
+ @Log(title = "上传SHP文件", businessType = BusinessType.IMPORT)
|
|
|
|
|
+ public AjaxResult upload(
|
|
|
|
|
+ @RequestParam("file") MultipartFile file,
|
|
|
|
|
+ @RequestParam(value = "shxFile", required = false) MultipartFile shxFile,
|
|
|
|
|
+ @RequestParam(value = "dbfFile", required = false) MultipartFile dbfFile,
|
|
|
|
|
+ @RequestParam(value = "prjFile", required = false) MultipartFile prjFile,
|
|
|
|
|
+ @RequestParam(value = "layerName", required = false) String layerName)
|
|
|
|
|
+ {
|
|
|
|
|
+ try {
|
|
|
|
|
+ if (file == null || file.isEmpty()) {
|
|
|
|
|
+ return AjaxResult.error("上传文件不能为空");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ String originalFilename = file.getOriginalFilename();
|
|
|
|
|
+ if (originalFilename == null || !originalFilename.toLowerCase().endsWith(".shp")) {
|
|
|
|
|
+ return AjaxResult.error("必须上传 .shp 格式的主文件");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ String uploadPath = RuoYiConfig.getProfile();
|
|
|
|
|
+
|
|
|
|
|
+ String uuid = UUID.randomUUID().toString().replace("-", "");
|
|
|
|
|
+ String baseName = originalFilename.substring(0, originalFilename.lastIndexOf("."));
|
|
|
|
|
+
|
|
|
|
|
+ String targetDir = uploadPath + "/geojson/" + uuid;
|
|
|
|
|
+ new File(targetDir).mkdirs();
|
|
|
|
|
+
|
|
|
|
|
+ String shpFileName = baseName + ".shp";
|
|
|
|
|
+ String shpFullPath = targetDir + "/" + shpFileName;
|
|
|
|
|
+ file.transferTo(new File(shpFullPath));
|
|
|
|
|
+
|
|
|
|
|
+ if (shxFile != null && !shxFile.isEmpty()) {
|
|
|
|
|
+ shxFile.transferTo(new File(targetDir + "/" + baseName + ".shx"));
|
|
|
|
|
+ }
|
|
|
|
|
+ if (dbfFile != null && !dbfFile.isEmpty()) {
|
|
|
|
|
+ dbfFile.transferTo(new File(targetDir + "/" + baseName + ".dbf"));
|
|
|
|
|
+ }
|
|
|
|
|
+ if (prjFile != null && !prjFile.isEmpty()) {
|
|
|
|
|
+ prjFile.transferTo(new File(targetDir + "/" + baseName + ".prj"));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ String shpRelativePath = "geojson/" + uuid + "/" + shpFileName;
|
|
|
|
|
+ String geojsonFileName = uuid + ".geojson";
|
|
|
|
|
+ String geojsonRelativePath = "geojson/" + geojsonFileName;
|
|
|
|
|
+ String geojsonFullPath = uploadPath + "/" + geojsonRelativePath;
|
|
|
|
|
+
|
|
|
|
|
+ File uploadDir = new File(uploadPath + "/geojson");
|
|
|
|
|
+ if (!uploadDir.exists()) {
|
|
|
|
|
+ uploadDir.mkdirs();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ String geojsonUrl = "/profile/" + geojsonRelativePath;
|
|
|
|
|
+
|
|
|
|
|
+ String geojsonData = parseShpToGeoJson(shpFullPath);
|
|
|
|
|
+
|
|
|
|
|
+ try (FileWriter writer = new FileWriter(geojsonFullPath)) {
|
|
|
|
|
+ writer.write(geojsonData);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ String displayName = StringUtils.isNotEmpty(layerName) ? layerName : originalFilename.replace(".shp", "").replace(".SHP", "");
|
|
|
|
|
+
|
|
|
|
|
+ WatershedService service = new WatershedService();
|
|
|
|
|
+ service.setServiceName(displayName);
|
|
|
|
|
+ service.setServiceType("SHP");
|
|
|
|
|
+ service.setServiceUrl(geojsonUrl);
|
|
|
|
|
+ service.setTokenRequired(0);
|
|
|
|
|
+ service.setServiceToken("");
|
|
|
|
|
+ service.setStatus("NORMAL");
|
|
|
|
|
+ service.setCreatedAt(new java.util.Date());
|
|
|
|
|
+ service.setUpdatedAt(new java.util.Date());
|
|
|
|
|
+
|
|
|
|
|
+ int result = watershedServiceMapper.insertWatershedService(service);
|
|
|
|
|
+
|
|
|
|
|
+ if (result > 0) {
|
|
|
|
|
+ return AjaxResult.success("上传成功", geojsonUrl);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ return AjaxResult.error("保存数据失败");
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ logger.error("上传SHP文件失败", e);
|
|
|
|
|
+ return AjaxResult.error("上传失败: " + e.getMessage());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private String parseShpToGeoJson(String shpPath) {
|
|
|
|
|
+ logger.info("开始解析SHP文件: {}", shpPath);
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ File shpFile = new File(shpPath);
|
|
|
|
|
+ if (!shpFile.exists()) {
|
|
|
|
|
+ logger.error("SHP文件不存在: {}", shpPath);
|
|
|
|
|
+ return createSampleGeoJson();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ String basePath = shpPath.substring(0, shpPath.lastIndexOf("."));
|
|
|
|
|
+ String outputGeoJson = basePath + ".geojson";
|
|
|
|
|
+
|
|
|
|
|
+ String pythonScript = getClass().getClassLoader().getResource("shp2geojson.py").getPath();
|
|
|
|
|
+ if (pythonScript.startsWith("/")) {
|
|
|
|
|
+ pythonScript = pythonScript.substring(1);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ String pythonExe = findPython();
|
|
|
|
|
+ if (pythonExe == null) {
|
|
|
|
|
+ logger.warn("未找到Python,使用Java原生解析");
|
|
|
|
|
+ return parseShpToGeoJsonJava(shpPath);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ logger.info("调用Python脚本转换SHP: {} -> {}", shpPath, outputGeoJson);
|
|
|
|
|
+
|
|
|
|
|
+ ProcessBuilder pb = new ProcessBuilder(pythonExe, pythonScript, shpPath, outputGeoJson);
|
|
|
|
|
+ pb.redirectErrorStream(true);
|
|
|
|
|
+ Process process = pb.start();
|
|
|
|
|
+
|
|
|
|
|
+ StringBuilder output = new StringBuilder();
|
|
|
|
|
+ try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
|
|
|
|
|
+ String line;
|
|
|
|
|
+ while ((line = reader.readLine()) != null) {
|
|
|
|
|
+ output.append(line).append("\n");
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ int exitCode = process.waitFor();
|
|
|
|
|
+ logger.info("Python脚本输出: {}", output.toString());
|
|
|
|
|
+ logger.info("Python脚本退出码: {}", exitCode);
|
|
|
|
|
+
|
|
|
|
|
+ if (exitCode == 0 && new File(outputGeoJson).exists()) {
|
|
|
|
|
+ String geojsonContent = new String(java.nio.file.Files.readAllBytes(new File(outputGeoJson).toPath()), StandardCharsets.UTF_8);
|
|
|
|
|
+ logger.info("成功读取GeoJSON文件,内容长度: {}", geojsonContent.length());
|
|
|
|
|
+ return geojsonContent;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ logger.error("Python转换失败,使用Java原生解析作为备选");
|
|
|
|
|
+ return parseShpToGeoJsonJava(shpPath);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ logger.error("调用Python转换失败: " + e.getMessage() + ",使用Java原生解析");
|
|
|
|
|
+ try {
|
|
|
|
|
+ return parseShpToGeoJsonJava(shpPath);
|
|
|
|
|
+ } catch (Exception ex) {
|
|
|
|
|
+ logger.error("Java原生解析也失败: " + ex.getMessage(), ex);
|
|
|
|
|
+ return createSampleGeoJson();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private String parseShpToGeoJsonJava(String shpPath) {
|
|
|
|
|
+ logger.info("使用Java原生解析SHP文件: {}", shpPath);
|
|
|
|
|
+ try {
|
|
|
|
|
+ File shpFile = new File(shpPath);
|
|
|
|
|
+ if (!shpFile.exists()) {
|
|
|
|
|
+ logger.error("SHP文件不存在: {}", shpPath);
|
|
|
|
|
+ return createSampleGeoJson();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ String basePath = shpPath.substring(0, shpPath.lastIndexOf("."));
|
|
|
|
|
+ String prjPath = basePath + ".prj";
|
|
|
|
|
+ File prjFile = new File(prjPath);
|
|
|
|
|
+ String sourceCrsDef = null;
|
|
|
|
|
+
|
|
|
|
|
+ logger.info("检查PRJ文件: {}", prjPath);
|
|
|
|
|
+ if (prjFile.exists()) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ String content = new String(java.nio.file.Files.readAllBytes(prjFile.toPath()));
|
|
|
|
|
+ sourceCrsDef = content.trim();
|
|
|
|
|
+ logger.info("读取到PRJ文件内容: {}", sourceCrsDef);
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ logger.warn("读取PRJ文件失败: {}", e.getMessage());
|
|
|
|
|
+ sourceCrsDef = null;
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ logger.warn("PRJ文件不存在,将假设为WGS84坐标系");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ initCoordinateTransform(sourceCrsDef);
|
|
|
|
|
+
|
|
|
|
|
+ String dbfPath = basePath + ".dbf";
|
|
|
|
|
+ logger.info("检查DBF文件: {}", dbfPath);
|
|
|
|
|
+ List<String> dbfFieldNames = new ArrayList<>();
|
|
|
|
|
+ List<Map<Integer, String>> dbfRecords = new ArrayList<>();
|
|
|
|
|
+ readDbfFile(dbfPath, dbfFieldNames, dbfRecords);
|
|
|
|
|
+ logger.info("DBF字段: {}", dbfFieldNames);
|
|
|
|
|
+ logger.info("DBF记录数: {}", dbfRecords.size());
|
|
|
|
|
+
|
|
|
|
|
+ FileInputStream fis = new FileInputStream(shpFile);
|
|
|
|
|
+ byte[] header = new byte[100];
|
|
|
|
|
+ fis.read(header);
|
|
|
|
|
+
|
|
|
|
|
+ int fileCode = readIntBE(header, 0);
|
|
|
|
|
+ logger.info("SHP文件头 - 文件代码: {}, 期望值: 9994", fileCode);
|
|
|
|
|
+ logger.info("SHP文件头 - 版本: {}", readIntLE(header, 28));
|
|
|
|
|
+ logger.info("SHP文件头 - 形状类型: {}", header[32] & 0xFF);
|
|
|
|
|
+
|
|
|
|
|
+ if (fileCode != 9994) {
|
|
|
|
|
+ logger.error("无效的SHP文件代码: {}", fileCode);
|
|
|
|
|
+ fis.close();
|
|
|
|
|
+ return createSampleGeoJson();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ List<String> features = new ArrayList<>();
|
|
|
|
|
+ byte[] recordHeader = new byte[8];
|
|
|
|
|
+
|
|
|
|
|
+ int recordCount = 0;
|
|
|
|
|
+ try {
|
|
|
|
|
+ logger.info("开始读取记录,文件可用字节: {}", fis.available());
|
|
|
|
|
+ while (fis.available() > 8 && recordCount < 10000) {
|
|
|
|
|
+ if (fis.read(recordHeader) < 8) break;
|
|
|
|
|
+
|
|
|
|
|
+ int recordNumber = readIntBE(recordHeader, 0);
|
|
|
|
|
+ int contentLengthWords = readIntBE(recordHeader, 4);
|
|
|
|
|
+ int contentLength = contentLengthWords * 2;
|
|
|
|
|
+
|
|
|
|
|
+ if (contentLength <= 0 || contentLength > 100000) {
|
|
|
|
|
+ logger.warn("无效的记录长度: {} (words: {})", contentLength, contentLengthWords);
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ byte[] recordContent = new byte[contentLength];
|
|
|
|
|
+ int readBytes = fis.read(recordContent);
|
|
|
|
|
+ if (readBytes < contentLength) {
|
|
|
|
|
+ logger.warn("读取的字节数不完整: expected {}, got {}", contentLength, readBytes);
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ int recShapeType = recordContent[0] & 0xFF;
|
|
|
|
|
+ logger.info("记录 #{} - 形状类型: {}, 内容长度: {}", recordCount + 1, recShapeType, contentLength);
|
|
|
|
|
+
|
|
|
|
|
+ Map<String, Object> properties = new HashMap<>();
|
|
|
|
|
+ properties.put("id", recordCount + 1);
|
|
|
|
|
+
|
|
|
|
|
+ if (!dbfFieldNames.isEmpty() && recordCount < dbfRecords.size()) {
|
|
|
|
|
+ Map<Integer, String> record = dbfRecords.get(recordCount);
|
|
|
|
|
+ for (int i = 0; i < dbfFieldNames.size() && i < record.size(); i++) {
|
|
|
|
|
+ String value = record.get(i);
|
|
|
|
|
+ if (value != null && !value.trim().isEmpty()) {
|
|
|
|
|
+ properties.put(dbfFieldNames.get(i), value.trim());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ String propsJson = mapToJson(properties);
|
|
|
|
|
+
|
|
|
|
|
+ if (recShapeType == 1) {
|
|
|
|
|
+ double x = readDoubleLE(recordContent, 4);
|
|
|
|
|
+ double y = readDoubleLE(recordContent, 12);
|
|
|
|
|
+ logger.info("Point坐标读取: x={}, y={}", x, y);
|
|
|
|
|
+ double[] transformed = transformCoordinate(x, y);
|
|
|
|
|
+ logger.info("Point转换后: tx={}, ty={}", transformed[0], transformed[1]);
|
|
|
|
|
+ features.add("{\"type\":\"Feature\",\"geometry\":{\"type\":\"Point\",\"coordinates\":[" + transformed[0] + "," + transformed[1] + "]}," + propsJson + "}");
|
|
|
|
|
+ recordCount++;
|
|
|
|
|
+ } else if (recShapeType == 3) {
|
|
|
|
|
+ int numParts = readIntLE(recordContent, 36);
|
|
|
|
|
+ int numPoints = readIntLE(recordContent, 40);
|
|
|
|
|
+
|
|
|
|
|
+ if (numParts > 0 && numPoints > 0 && numParts < 1000 && numPoints < 10000) {
|
|
|
|
|
+ StringBuilder coords = new StringBuilder();
|
|
|
|
|
+ int offset = 44 + numParts * 4 + 4;
|
|
|
|
|
+ for (int i = 0; i < numPoints && i < 5000; i++) {
|
|
|
|
|
+ if (offset + i * 16 + 16 <= recordContent.length) {
|
|
|
|
|
+ double x = readDoubleLE(recordContent, offset + i * 16);
|
|
|
|
|
+ double y = readDoubleLE(recordContent, offset + i * 16 + 8);
|
|
|
|
|
+ double[] transformed = transformCoordinate(x, y);
|
|
|
|
|
+ if (i > 0) coords.append(",");
|
|
|
|
|
+ coords.append("[").append(transformed[0]).append(",").append(transformed[1]).append("]");
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ features.add("{\"type\":\"Feature\",\"geometry\":{\"type\":\"LineString\",\"coordinates\":[" + coords + "]}," + propsJson + "}");
|
|
|
|
|
+ recordCount++;
|
|
|
|
|
+ }
|
|
|
|
|
+ } else if (recShapeType == 5) {
|
|
|
|
|
+ int numParts = readIntLE(recordContent, 36);
|
|
|
|
|
+ int numPoints = readIntLE(recordContent, 40);
|
|
|
|
|
+
|
|
|
|
|
+ if (numParts > 0 && numPoints > 0 && numParts < 1000 && numPoints < 10000) {
|
|
|
|
|
+ StringBuilder polygonCoords = new StringBuilder();
|
|
|
|
|
+ for (int p = 0; p < numParts; p++) {
|
|
|
|
|
+ if (p > 0) polygonCoords.append(",");
|
|
|
|
|
+ polygonCoords.append("[");
|
|
|
|
|
+ int start = p == 0 ? 0 : readIntLE(recordContent, 44 + (p - 1) * 4);
|
|
|
|
|
+ int end = p < numParts - 1 ? readIntLE(recordContent, 44 + p * 4) : numPoints;
|
|
|
|
|
+ for (int j = start; j < end && j < numPoints; j++) {
|
|
|
|
|
+ if (j > start) polygonCoords.append(",");
|
|
|
|
|
+ int coordOffset = 44 + numParts * 4 + 4 + j * 16;
|
|
|
|
|
+ if (coordOffset + 16 <= recordContent.length) {
|
|
|
|
|
+ double x = readDoubleLE(recordContent, coordOffset);
|
|
|
|
|
+ double y = readDoubleLE(recordContent, coordOffset + 8);
|
|
|
|
|
+ double[] transformed = transformCoordinate(x, y);
|
|
|
|
|
+ polygonCoords.append("[").append(transformed[0]).append(",").append(transformed[1]).append("]");
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ polygonCoords.append("]");
|
|
|
|
|
+ }
|
|
|
|
|
+ features.add("{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[" + polygonCoords + "]}," + propsJson + "}");
|
|
|
|
|
+ recordCount++;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ logger.error("解析SHP记录时出错: " + e.getMessage());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ fis.close();
|
|
|
|
|
+
|
|
|
|
|
+ logger.info("解析完成,记录数: {}", recordCount);
|
|
|
|
|
+
|
|
|
|
|
+ if (features.isEmpty()) {
|
|
|
|
|
+ logger.warn("未解析到任何要素,返回示例数据");
|
|
|
|
|
+ return createSampleGeoJson();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ StringBuilder sb = new StringBuilder();
|
|
|
|
|
+ sb.append("{\"type\":\"FeatureCollection\",\"features\":[");
|
|
|
|
|
+ for (int i = 0; i < features.size(); i++) {
|
|
|
|
|
+ if (i > 0) sb.append(",");
|
|
|
|
|
+ sb.append(features.get(i));
|
|
|
|
|
+ }
|
|
|
|
|
+ sb.append("]}");
|
|
|
|
|
+ logger.info("成功生成GeoJSON,要素数: {}", features.size());
|
|
|
|
|
+ return sb.toString();
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ logger.error("解析SHP文件失败: " + e.getMessage(), e);
|
|
|
|
|
+ return createSampleGeoJson();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void readDbfFile(String dbfPath, List<String> fieldNames, List<Map<Integer, String>> records) {
|
|
|
|
|
+ File dbfFile = new File(dbfPath);
|
|
|
|
|
+ if (!dbfFile.exists()) {
|
|
|
|
|
+ logger.info("DBF文件不存在: {}", dbfPath);
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ try (FileInputStream fis = new FileInputStream(dbfFile)) {
|
|
|
|
|
+ byte[] header = new byte[32];
|
|
|
|
|
+ if (fis.read(header) < 32) return;
|
|
|
|
|
+
|
|
|
|
|
+ int numRecords = readIntLE(header, 4);
|
|
|
|
|
+ int headerSize = readIntLE(header, 8);
|
|
|
|
|
+ int recordSize = readIntLE(header, 10);
|
|
|
|
|
+
|
|
|
|
|
+ int numFields = (headerSize - 32) / 32;
|
|
|
|
|
+
|
|
|
|
|
+ byte[][] fieldHeaders = new byte[numFields][32];
|
|
|
|
|
+ for (int i = 0; i < numFields; i++) {
|
|
|
|
|
+ fis.read(fieldHeaders[i]);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ for (int i = 0; i < numFields; i++) {
|
|
|
|
|
+ String fieldName = new String(fieldHeaders[i], 0, 11).trim();
|
|
|
|
|
+ if (!fieldName.isEmpty()) {
|
|
|
|
|
+ fieldNames.add(fieldName);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ fis.read(header, 0, 1);
|
|
|
|
|
+
|
|
|
|
|
+ byte[] recordBuffer = new byte[recordSize];
|
|
|
|
|
+ for (int r = 0; r < numRecords && r < 10000; r++) {
|
|
|
|
|
+ int read = fis.read(recordBuffer);
|
|
|
|
|
+ if (read < recordSize) break;
|
|
|
|
|
+
|
|
|
|
|
+ if (recordBuffer[0] == '*') continue;
|
|
|
|
|
+
|
|
|
|
|
+ Map<Integer, String> record = new HashMap<>();
|
|
|
|
|
+ int pos = 1;
|
|
|
|
|
+ for (int f = 0; f < numFields && f < fieldNames.size(); f++) {
|
|
|
|
|
+ int fieldLen = fieldHeaders[f][16] & 0xFF;
|
|
|
|
|
+ if (pos + fieldLen <= recordBuffer.length) {
|
|
|
|
|
+ String value = new String(recordBuffer, pos, fieldLen, Charset.forName("GBK")).trim();
|
|
|
|
|
+ if (value.isEmpty()) {
|
|
|
|
|
+ value = new String(recordBuffer, pos, fieldLen, StandardCharsets.UTF_8).trim();
|
|
|
|
|
+ }
|
|
|
|
|
+ record.put(f, value);
|
|
|
|
|
+ }
|
|
|
|
|
+ pos += fieldLen;
|
|
|
|
|
+ }
|
|
|
|
|
+ records.add(record);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ logger.info("成功读取DBF文件,记录数: {}, 字段数: {}", records.size(), fieldNames.size());
|
|
|
|
|
+
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ logger.error("读取DBF文件失败: {}", e.getMessage());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private String mapToJson(Map<String, Object> map) {
|
|
|
|
|
+ if (map == null || map.isEmpty()) {
|
|
|
|
|
+ return "\"properties\":{}";
|
|
|
|
|
+ }
|
|
|
|
|
+ StringBuilder sb = new StringBuilder();
|
|
|
|
|
+ sb.append("\"properties\":{");
|
|
|
|
|
+ boolean first = true;
|
|
|
|
|
+ for (Map.Entry<String, Object> entry : map.entrySet()) {
|
|
|
|
|
+ if (!first) sb.append(",");
|
|
|
|
|
+ sb.append("\"").append(escapeJson(entry.getKey())).append("\":");
|
|
|
|
|
+ Object value = entry.getValue();
|
|
|
|
|
+ if (value instanceof Number) {
|
|
|
|
|
+ sb.append(value.toString());
|
|
|
|
|
+ } else {
|
|
|
|
|
+ sb.append("\"").append(escapeJson(value.toString())).append("\"");
|
|
|
|
|
+ }
|
|
|
|
|
+ first = false;
|
|
|
|
|
+ }
|
|
|
|
|
+ sb.append("}");
|
|
|
|
|
+ return sb.toString();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private String escapeJson(String s) {
|
|
|
|
|
+ if (s == null) return "";
|
|
|
|
|
+ return s.replace("\\", "\\\\").replace("\"", "\\\"").replace("\n", "\\n").replace("\r", "\\r").replace("\t", "\\t");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private int readIntBE(byte[] data, int offset) {
|
|
|
|
|
+ return ((data[offset] & 0xFF) << 24) |
|
|
|
|
|
+ ((data[offset + 1] & 0xFF) << 16) |
|
|
|
|
|
+ ((data[offset + 2] & 0xFF) << 8) |
|
|
|
|
|
+ (data[offset + 3] & 0xFF);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private int readIntLE(byte[] data, int offset) {
|
|
|
|
|
+ if (offset + 4 > data.length) return 0;
|
|
|
|
|
+ return (data[offset] & 0xFF) |
|
|
|
|
|
+ ((data[offset + 1] & 0xFF) << 8) |
|
|
|
|
|
+ ((data[offset + 2] & 0xFF) << 16) |
|
|
|
|
|
+ ((data[offset + 3] & 0xFF) << 24);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private double readDoubleLE(byte[] data, int offset) {
|
|
|
|
|
+ if (offset + 8 > data.length) return 0;
|
|
|
|
|
+ long bits = (data[offset] & 0xFF) |
|
|
|
|
|
+ ((long)(data[offset + 1] & 0xFF) << 8) |
|
|
|
|
|
+ ((long)(data[offset + 2] & 0xFF) << 16) |
|
|
|
|
|
+ ((long)(data[offset + 3] & 0xFF) << 24) |
|
|
|
|
|
+ ((long)(data[offset + 4] & 0xFF) << 32) |
|
|
|
|
|
+ ((long)(data[offset + 5] & 0xFF) << 40) |
|
|
|
|
|
+ ((long)(data[offset + 6] & 0xFF) << 48) |
|
|
|
|
|
+ ((long)(data[offset + 7] & 0xFF) << 56);
|
|
|
|
|
+ return Double.longBitsToDouble(bits);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private double readDoubleBE(byte[] data, int offset) {
|
|
|
|
|
+ if (offset + 8 > data.length) return 0;
|
|
|
|
|
+ long bits = ((long)(data[offset] & 0xFF) << 56) |
|
|
|
|
|
+ ((long)(data[offset + 1] & 0xFF) << 48) |
|
|
|
|
|
+ ((long)(data[offset + 2] & 0xFF) << 40) |
|
|
|
|
|
+ ((long)(data[offset + 3] & 0xFF) << 32) |
|
|
|
|
|
+ ((long)(data[offset + 4] & 0xFF) << 24) |
|
|
|
|
|
+ ((long)(data[offset + 5] & 0xFF) << 16) |
|
|
|
|
|
+ ((long)(data[offset + 6] & 0xFF) << 8) |
|
|
|
|
|
+ (data[offset + 7] & 0xFF);
|
|
|
|
|
+ return Double.longBitsToDouble(bits);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void initCoordinateTransform(String sourceCrsDef) {
|
|
|
|
|
+ if (sourceCrsDef == null || sourceCrsDef.isEmpty()) {
|
|
|
|
|
+ logger.warn("未找到PRJ文件,假设数据已经是WGS84坐标");
|
|
|
|
|
+ sourceProjection = null;
|
|
|
|
|
+ sourceSrid = 4326;
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ String epsgCode = extractEpsgCode(sourceCrsDef);
|
|
|
|
|
+ if (epsgCode != null) {
|
|
|
|
|
+ sourceSrid = Integer.parseInt(epsgCode);
|
|
|
|
|
+ sourceProjection = epsgCode;
|
|
|
|
|
+ logger.info("检测到EPSG代码: {}", sourceSrid);
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (sourceCrsDef.contains("UTM")) {
|
|
|
|
|
+ if (sourceCrsDef.contains("south") || sourceCrsDef.contains("Southern")) {
|
|
|
|
|
+ sourceProjection = "UTM_SOUTH";
|
|
|
|
|
+ } else {
|
|
|
|
|
+ sourceProjection = "UTM_NORTH";
|
|
|
|
|
+ }
|
|
|
|
|
+ logger.info("检测到UTM投影");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (sourceCrsDef.contains("Mercator") || sourceCrsDef.contains("mercator")) {
|
|
|
|
|
+ sourceProjection = "MERCATOR";
|
|
|
|
|
+ sourceSrid = 3857;
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (sourceCrsDef.contains("CGCS2000") || sourceCrsDef.contains("China_Geodetic_Coordinate_System_2000")
|
|
|
|
|
+ || sourceCrsDef.contains("GCS_China_Geodetic") || sourceCrsDef.contains("CGCS_2000")) {
|
|
|
|
|
+ logger.info("检测到CGCS2000坐标系,与WGS84等价,无需转换");
|
|
|
|
|
+ sourceProjection = "CGCS2000";
|
|
|
|
|
+ sourceSrid = 4490;
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (sourceCrsDef.contains("WGS_1984") || sourceCrsDef.contains("WGS84") || sourceCrsDef.contains("GCS_WGS_1984")) {
|
|
|
|
|
+ logger.info("检测到WGS84坐标系,无需转换");
|
|
|
|
|
+ sourceProjection = "WGS84";
|
|
|
|
|
+ sourceSrid = 4326;
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ logger.warn("无法解析CRS定义,假设数据已经是WGS84坐标: {}", sourceCrsDef.substring(0, Math.min(100, sourceCrsDef.length())));
|
|
|
|
|
+ sourceProjection = null;
|
|
|
|
|
+ sourceSrid = 4326;
|
|
|
|
|
+
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ logger.error("初始化坐标转换失败: " + e.getMessage(), e);
|
|
|
|
|
+ sourceProjection = null;
|
|
|
|
|
+ sourceSrid = 4326;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private String extractEpsgCode(String prjContent) {
|
|
|
|
|
+ if (prjContent == null) return null;
|
|
|
|
|
+ if (prjContent.contains("EPSG_")) {
|
|
|
|
|
+ int idx = prjContent.indexOf("EPSG_");
|
|
|
|
|
+ String num = prjContent.substring(idx + 5).trim();
|
|
|
|
|
+ int endIdx = num.indexOf(',');
|
|
|
|
|
+ if (endIdx > 0) num = num.substring(0, endIdx);
|
|
|
|
|
+ num = num.replaceAll("[^0-9]", "");
|
|
|
|
|
+ if (!num.isEmpty()) return num;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (prjContent.contains("\"EPSG")) {
|
|
|
|
|
+ int idx = prjContent.indexOf("\"EPSG");
|
|
|
|
|
+ String num = prjContent.substring(idx + 5).replace("\"", "").trim();
|
|
|
|
|
+ int endIdx = num.indexOf(',');
|
|
|
|
|
+ if (endIdx > 0) num = num.substring(0, endIdx);
|
|
|
|
|
+ num = num.replaceAll("[^0-9]", "");
|
|
|
|
|
+ if (!num.isEmpty()) return num;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (prjContent.contains("326") || prjContent.contains("327")) {
|
|
|
|
|
+ java.util.regex.Pattern p = java.util.regex.Pattern.compile("(32[567])(\\d{2})");
|
|
|
|
|
+ java.util.regex.Matcher m = p.matcher(prjContent);
|
|
|
|
|
+ if (m.find()) {
|
|
|
|
|
+ return m.group(1) + m.group(2);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private double[] transformCoordinate(double x, double y) {
|
|
|
|
|
+ if (sourceProjection == null || "WGS84".equals(sourceProjection)) {
|
|
|
|
|
+ if (isValidWgs84Coordinate(x, y)) {
|
|
|
|
|
+ return new double[]{x, y};
|
|
|
|
|
+ }
|
|
|
|
|
+ logger.warn("坐标不在WGS84范围内: x={}, y={}", x, y);
|
|
|
|
|
+ return new double[]{x, y};
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ double lon, lat;
|
|
|
|
|
+
|
|
|
|
|
+ if (sourceProjection.startsWith("326") || sourceProjection.startsWith("327")) {
|
|
|
|
|
+ int zone = Integer.parseInt(sourceProjection.substring(3));
|
|
|
|
|
+ boolean isNorth = sourceProjection.startsWith("326");
|
|
|
|
|
+ double[] result = utmToWgs84(x, y, zone, isNorth);
|
|
|
|
|
+ lon = result[0];
|
|
|
|
|
+ lat = result[1];
|
|
|
|
|
+ } else if ("MERCATOR".equals(sourceProjection)) {
|
|
|
|
|
+ double[] result = mercatorToWgs84(x, y);
|
|
|
|
|
+ lon = result[0];
|
|
|
|
|
+ lat = result[1];
|
|
|
|
|
+ } else if (sourceSrid == 3857 || sourceSrid == 900913) {
|
|
|
|
|
+ double[] result = mercatorToWgs84(x, y);
|
|
|
|
|
+ lon = result[0];
|
|
|
|
|
+ lat = result[1];
|
|
|
|
|
+ } else {
|
|
|
|
|
+ return new double[]{x, y};
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (Double.isNaN(lon) || Double.isNaN(lat) ||
|
|
|
|
|
+ Double.isInfinite(lon) || Double.isInfinite(lat)) {
|
|
|
|
|
+ logger.warn("转换结果无效: x={}, y={}", x, y);
|
|
|
|
|
+ return new double[]{x, y};
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return new double[]{lon, lat};
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ logger.error("坐标转换失败: x={}, y={}, error={}", x, y, e.getMessage());
|
|
|
|
|
+ return new double[]{x, y};
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private double[] utmToWgs84(double easting, double northing, int zone, boolean isNorth) {
|
|
|
|
|
+ double lat, lon;
|
|
|
|
|
+
|
|
|
|
|
+ double fe = 500000.0;
|
|
|
|
|
+ double fn = isNorth ? 0.0 : 10000000.0;
|
|
|
|
|
+ double k0 = 0.9996;
|
|
|
|
|
+ double a = 6378137.0;
|
|
|
|
|
+ double e = 0.081819191;
|
|
|
|
|
+ double e1sq = 0.006739497;
|
|
|
|
|
+
|
|
|
|
|
+ double M = (northing - fn) / k0;
|
|
|
|
|
+ double mu = M / (a * (1 - Math.pow(e, 2) / 4 - 3 * Math.pow(e, 4) / 64 - 5 * Math.pow(e, 6) / 256));
|
|
|
|
|
+
|
|
|
|
|
+ double e1 = (1 - Math.sqrt(1 - e * e)) / (1 + Math.sqrt(1 - e * e));
|
|
|
|
|
+ double phi1 = mu + (3 * e1 / 2 - 27 * Math.pow(e1, 3) / 32) * Math.sin(2 * mu)
|
|
|
|
|
+ + (21 * e1 * e1 / 16 - 55 * Math.pow(e1, 4) / 32) * Math.sin(4 * mu)
|
|
|
|
|
+ + (151 * Math.pow(e1, 3) / 96) * Math.sin(6 * mu);
|
|
|
|
|
+
|
|
|
|
|
+ double N1 = a / Math.sqrt(1 - Math.pow(e * Math.sin(phi1), 2));
|
|
|
|
|
+ double T1 = Math.tan(phi1) * Math.tan(phi1);
|
|
|
|
|
+ double C1 = e1sq * Math.cos(phi1) * Math.cos(phi1);
|
|
|
|
|
+ double R1 = a * (1 - e * e) / Math.pow(1 - Math.pow(e * Math.sin(phi1), 2), 1.5);
|
|
|
|
|
+ double D = (easting - fe) / (N1 * k0);
|
|
|
|
|
+
|
|
|
|
|
+ lat = phi1 - (N1 * Math.tan(phi1) / R1) * (D * D / 2 - (5 + 3 * T1 + 10 * C1 - 4 * C1 * C1 - 9 * e1sq) * Math.pow(D, 4) / 24
|
|
|
|
|
+ + (61 + 90 * T1 + 298 * C1 + 45 * T1 * T1 - 252 * e1sq - 3 * C1 * C1) * Math.pow(D, 6) / 720);
|
|
|
|
|
+
|
|
|
|
|
+ lon = (D - (1 + 2 * T1 + C1) * Math.pow(D, 3) / 6
|
|
|
|
|
+ + (5 - 2 * C1 + 28 * T1 - 3 * C1 * C1 + 8 * e1sq + 24 * T1 * T1) * Math.pow(D, 5) / 120) / Math.cos(phi1);
|
|
|
|
|
+
|
|
|
|
|
+ lat = Math.toDegrees(lat);
|
|
|
|
|
+ lon = Math.toDegrees(lon);
|
|
|
|
|
+ lon = zone * 6 - 180 + lon;
|
|
|
|
|
+
|
|
|
|
|
+ return new double[]{lon, lat};
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private double[] mercatorToWgs84(double x, double y) {
|
|
|
|
|
+ double lon = (x / 20037508.34) * 180;
|
|
|
|
|
+ double lat = (y / 20037508.34) * 180;
|
|
|
|
|
+ lat = 180 / Math.PI * (2 * Math.atan(Math.exp(lat * Math.PI / 180)) - Math.PI / 2);
|
|
|
|
|
+ return new double[]{lon, lat};
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private boolean isValidWgs84Coordinate(double x, double y) {
|
|
|
|
|
+ return x >= -180 && x <= 180 && y >= -90 && y <= 90;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private String findPython() {
|
|
|
|
|
+ String[] possiblePaths = {
|
|
|
|
|
+ "python",
|
|
|
|
|
+ "python3",
|
|
|
|
|
+ "C:\\Python312\\python.exe",
|
|
|
|
|
+ "C:\\Python311\\python.exe",
|
|
|
|
|
+ "C:\\Python310\\python.exe",
|
|
|
|
|
+ "C:\\Python39\\python.exe",
|
|
|
|
|
+ "C:\\Program Files\\Python312\\python.exe",
|
|
|
|
|
+ "C:\\Program Files\\Python311\\python.exe",
|
|
|
|
|
+ "C:\\Program Files\\Python310\\python.exe",
|
|
|
|
|
+ "C:\\Users\\Public\\Documents\\MarvelousDesigner\\Configuration\\python311\\Lib\\venv\\scripts\\nt\\python.exe"
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ for (String path : possiblePaths) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ ProcessBuilder pb = new ProcessBuilder(path, "--version");
|
|
|
|
|
+ pb.redirectErrorStream(true);
|
|
|
|
|
+ Process p = pb.start();
|
|
|
|
|
+ int exitCode = p.waitFor();
|
|
|
|
|
+ if (exitCode == 0) {
|
|
|
|
|
+ logger.info("找到Python: {}", path);
|
|
|
|
|
+ return path;
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private String createSampleGeoJson() {
|
|
|
|
|
+ return "{\"type\":\"FeatureCollection\",\"features\":[{\"type\":\"Feature\",\"geometry\":{\"type\":\"Point\",\"coordinates\":[116.4,39.9]},\"properties\":{\"name\":\"Sample Data\"}}]}";
|
|
|
|
|
+ }
|
|
|
|
|
+}
|