运维模块补充
This commit is contained in:
@ -1,5 +1,7 @@
|
||||
package org.dromara.system.api;
|
||||
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
|
||||
/**
|
||||
* @author lilemy
|
||||
* @date 2025-09-10 16:15
|
||||
@ -14,4 +16,10 @@ public interface RemoteProjectService {
|
||||
*/
|
||||
String selectProjectNameById(Long projectId);
|
||||
|
||||
/**
|
||||
*校验用户是否拥有操作项目的权限
|
||||
* @param projectId
|
||||
* @param userId
|
||||
*/
|
||||
void validAuth(Long projectId, Long userId);
|
||||
}
|
||||
|
||||
@ -189,4 +189,5 @@ public interface RemoteUserService {
|
||||
*/
|
||||
Map<Long, String> selectPostNamesByIds(List<Long> postIds);
|
||||
|
||||
RemoteUserVo selectUserByPhonenumber(String phone);
|
||||
}
|
||||
|
||||
@ -0,0 +1,28 @@
|
||||
package org.dromara.system.api.utils;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
|
||||
/**
|
||||
* @author lilemy
|
||||
* @date 2025/5/27 9:16
|
||||
*/
|
||||
public class BigDecimalUtil {
|
||||
|
||||
/**
|
||||
* 计算百分比
|
||||
*
|
||||
* @param dividend 被除数
|
||||
* @param divisor 除数
|
||||
* @return 百分比(保留2位小数)如 12.34%
|
||||
*/
|
||||
public static BigDecimal toPercentage(BigDecimal dividend, BigDecimal divisor) {
|
||||
if (dividend == null || divisor == null || divisor.compareTo(BigDecimal.ZERO) == 0) {
|
||||
return BigDecimal.valueOf(0.00);
|
||||
}
|
||||
return dividend
|
||||
.multiply(new BigDecimal("100"))
|
||||
.divide(divisor, 2, RoundingMode.HALF_UP);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,100 @@
|
||||
package org.dromara.system.api.utils;
|
||||
|
||||
import org.apache.poi.util.Units;
|
||||
import org.apache.poi.xwpf.usermodel.XWPFDocument;
|
||||
import org.apache.poi.xwpf.usermodel.XWPFRun;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.DirectoryStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
|
||||
/**
|
||||
* @author lilemy
|
||||
* @date 2025/4/17 14:53
|
||||
*/
|
||||
public class DocumentUtil {
|
||||
|
||||
/**
|
||||
* 在给定的 run 里插入图片,并按原始大小(或缩放后大小)设置宽高。
|
||||
*
|
||||
* @param run 要插入图片的 XWPFRun
|
||||
* @param imagePath 本地图片路径或 URL(这里示例用本地文件)
|
||||
* @param document 当前文档对象
|
||||
* @param maxWidthPx 最大允许宽度(像素),如果原图更宽就按比例缩放;设置为 <=0 则不缩放
|
||||
*/
|
||||
public static void insertImageDynamic(XWPFRun run,
|
||||
String imagePath,
|
||||
XWPFDocument document,
|
||||
int maxWidthPx) throws Exception {
|
||||
// 1. 先把图片读到 byte[]
|
||||
byte[] imgBytes = Files.readAllBytes(Paths.get(imagePath));
|
||||
// 2. 用 ImageIO 读出宽高(像素)
|
||||
BufferedImage img = ImageIO.read(new ByteArrayInputStream(imgBytes));
|
||||
int widthPx = img.getWidth();
|
||||
int heightPx = img.getHeight();
|
||||
// 3. 如果指定了最大宽度,而且原图更宽,则按比例缩放
|
||||
if (maxWidthPx > 0 && widthPx > maxWidthPx) {
|
||||
double ratio = (double) maxWidthPx / widthPx;
|
||||
widthPx = maxWidthPx;
|
||||
heightPx = (int) (heightPx * ratio);
|
||||
}
|
||||
// 4. 把像素转换成 EMU
|
||||
int widthEmu = Units.pixelToEMU(widthPx);
|
||||
int heightEmu = Units.pixelToEMU(heightPx);
|
||||
// 5. 插入图片
|
||||
String lower = imagePath.toLowerCase();
|
||||
int format = lower.endsWith(".png") ? XWPFDocument.PICTURE_TYPE_PNG
|
||||
: lower.endsWith(".gif") ? XWPFDocument.PICTURE_TYPE_GIF
|
||||
: lower.endsWith(".jpeg") ? XWPFDocument.PICTURE_TYPE_JPEG
|
||||
: lower.endsWith(".jpg") ? XWPFDocument.PICTURE_TYPE_JPEG
|
||||
: XWPFDocument.PICTURE_TYPE_PNG; // 默认当 PNG
|
||||
try (InputStream picIn = new ByteArrayInputStream(imgBytes)) {
|
||||
run.addPicture(picIn, format, imagePath, widthEmu, heightEmu);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 递归将 sourceDir 中的所有文件和子目录,按照相对于 rootDir 的路径写入 ZipOutputStream。
|
||||
*
|
||||
* @param rootDir 源目录的根,用来计算相对路径
|
||||
* @param sourceDir 当前递归到的目录
|
||||
* @param zos ZIP 输出流
|
||||
*/
|
||||
public static void zipDirectory(Path rootDir, Path sourceDir, ZipOutputStream zos) throws IOException {
|
||||
// 遍历当前目录下的所有文件和文件夹
|
||||
try (DirectoryStream<Path> stream = Files.newDirectoryStream(sourceDir)) {
|
||||
for (Path entry : stream) {
|
||||
if (Files.isDirectory(entry)) {
|
||||
// 如果是目录,递归
|
||||
zipDirectory(rootDir, entry, zos);
|
||||
} else {
|
||||
// 如果是文件,创建一个 ZipEntry,路径以 '/' 分隔
|
||||
Path relativePath = rootDir.relativize(entry);
|
||||
String zipEntryName = relativePath.toString().replace(File.separatorChar, '/');
|
||||
ZipEntry zipEntry = new ZipEntry(zipEntryName);
|
||||
zos.putNextEntry(zipEntry);
|
||||
// 把文件内容写入 ZIP
|
||||
try (InputStream is = Files.newInputStream(entry)) {
|
||||
byte[] buffer = new byte[4096];
|
||||
int len;
|
||||
while ((len = is.read(buffer)) != -1) {
|
||||
zos.write(buffer, 0, len);
|
||||
}
|
||||
}
|
||||
zos.closeEntry();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,97 @@
|
||||
package org.dromara.system.api.utils;
|
||||
|
||||
import cn.hutool.json.JSONObject;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.common.core.exception.ServiceException;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author lilemy
|
||||
* @date 2025/4/23 10:42
|
||||
*/
|
||||
@Slf4j
|
||||
public class Dxf2JsonUtil {
|
||||
|
||||
/**
|
||||
* dxf转json
|
||||
*
|
||||
* @param exePath dxf2json.exe路径
|
||||
* @param inputDXFPath 输入dxf文件路径
|
||||
* @param outputJSONPath 输出json文件路径
|
||||
* @param sourceEPSG 源坐标系
|
||||
* @param targetEPSG 目标坐标系
|
||||
*/
|
||||
public static void dxf2json(String exePath, String inputDXFPath, String outputJSONPath, String sourceEPSG, String targetEPSG) {
|
||||
// 判断对应文件是否存在
|
||||
File exeFile = new File(exePath);
|
||||
if (!exeFile.exists()) {
|
||||
throw new ServiceException("转换程序不存在!");
|
||||
}
|
||||
File inputDXFFile = new File(inputDXFPath);
|
||||
if (!inputDXFFile.exists()) {
|
||||
throw new ServiceException("待转换 dxf 文件不存在!");
|
||||
}
|
||||
// 构造命令行参数
|
||||
List<String> parameters = buildParameter(exePath, inputDXFPath, outputJSONPath, sourceEPSG, targetEPSG);
|
||||
// 执行命令行
|
||||
ProcessBuilder builder = new ProcessBuilder(parameters);
|
||||
// 合并标准错误和输出
|
||||
builder.redirectErrorStream(true);
|
||||
try {
|
||||
Process process = builder.start();
|
||||
// 读取输出
|
||||
BufferedReader reader = new BufferedReader(
|
||||
new InputStreamReader(process.getInputStream(), "GBK")
|
||||
);
|
||||
String line;
|
||||
log.info("dxf 转 json 程序开始执行,程序路径:{},输入 dxf 路径:{},输出 json 文件路径:{},源坐标系:{},模板坐标系:{}",
|
||||
exePath, inputDXFPath, outputJSONPath, sourceEPSG, targetEPSG);
|
||||
while ((line = reader.readLine()) != null) {
|
||||
log.info("dxf 转 json 程序执行中:{}", line);
|
||||
JSONObject jsonObject = JSONUtil.parseObj(line);
|
||||
Integer code = jsonObject.get("code", Integer.class);
|
||||
if (code != 0 && code != 200) {
|
||||
throw new ServiceException("dxf 转 json 程序执行出错!");
|
||||
}
|
||||
}
|
||||
int exitCode = process.waitFor();
|
||||
log.info("dxf 转 json 程序执行完毕,程序退出码:{}", exitCode);
|
||||
reader.close();
|
||||
} catch (IOException | InterruptedException e) {
|
||||
log.error("执行 dxf 转 json 命令行时出错", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造命令行参数
|
||||
*
|
||||
* @param exePath dxf2json.exe路径
|
||||
* @param inputDXFPath 输入dxf文件路径
|
||||
* @param outputJSONPath 输出json文件路径
|
||||
* @param sourceEPSG 源坐标系
|
||||
* @param targetEPSG 目标坐标系
|
||||
* @return 命令行参数
|
||||
*/
|
||||
public static List<String> buildParameter(String exePath,
|
||||
String inputDXFPath,
|
||||
String outputJSONPath,
|
||||
String sourceEPSG,
|
||||
String targetEPSG) {
|
||||
// 构造命令行
|
||||
return Arrays.asList(
|
||||
exePath,
|
||||
inputDXFPath,
|
||||
outputJSONPath,
|
||||
sourceEPSG,
|
||||
targetEPSG
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,24 @@
|
||||
package org.dromara.system.api.utils;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import java.net.http.HttpClient;
|
||||
import java.time.Duration;
|
||||
|
||||
/**
|
||||
* @Author 铁憨憨
|
||||
* @Date 2025/7/18 10:16
|
||||
* @Version 1.0
|
||||
*
|
||||
* HttpClient 设计为可重用、线程安全的组件,其内部维护了连接池等资源,适合在多个接口调用中共享使用,所以交给Spring Bean 管理
|
||||
*/
|
||||
@Configuration
|
||||
public class HttpClientConfig {
|
||||
@Bean
|
||||
public HttpClient httpClient() {
|
||||
return HttpClient.newBuilder()
|
||||
.connectTimeout(Duration.ofSeconds(10))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,56 @@
|
||||
package org.dromara.system.api.utils;
|
||||
|
||||
import cn.hutool.core.util.HexUtil;
|
||||
|
||||
|
||||
import cn.hutool.crypto.SecureUtil;
|
||||
import cn.hutool.crypto.symmetric.AES;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
|
||||
/**
|
||||
* @author lilemy
|
||||
* @date 2025/6/25 10:57
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class IdCardEncryptorUtil {
|
||||
|
||||
@Value("${id-card.encrypt-key}")
|
||||
private String encryptKeyHex;
|
||||
|
||||
private AES aes;
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
byte[] keyBytes = HexUtil.decodeHex(encryptKeyHex);
|
||||
this.aes = SecureUtil.aes(keyBytes);
|
||||
log.info("身份证 AES 加解密工具初始化成功");
|
||||
}
|
||||
|
||||
/**
|
||||
* 加密
|
||||
*
|
||||
* @param idCard 身份证号码
|
||||
* @return 加密后的身份证号码
|
||||
*/
|
||||
public String encrypt(String idCard) {
|
||||
return aes.encryptBase64(idCard);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解密
|
||||
*
|
||||
* @param encrypted 密文
|
||||
* @return 解密后的身份证号码
|
||||
*/
|
||||
public String decrypt(String encrypted) {
|
||||
if (encrypted == null) {
|
||||
return null;
|
||||
}
|
||||
return aes.decryptStr(encrypted);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,316 @@
|
||||
//package org.dromara.system.api.utils;
|
||||
//
|
||||
//import cn.hutool.json.JSONUtil;
|
||||
//import org.dromara.common.constant.GeoJsonConstant;
|
||||
//import org.dromara.common.core.constant.HttpStatus;
|
||||
//import org.dromara.common.core.exception.ServiceException;
|
||||
//import org.dromara.common.domain.GeoPoint;
|
||||
//import org.dromara.facility.domain.dto.geojson.FacFeatureByPlane;
|
||||
//import org.dromara.facility.domain.dto.geojson.FacFeatureByPoint;
|
||||
//import org.dromara.facility.domain.dto.geojson.FacGeometry;
|
||||
//import org.dromara.facility.domain.dto.geojson.FacGeometryByPoint;
|
||||
//import org.locationtech.jts.geom.*;
|
||||
//import org.locationtech.jts.index.strtree.STRtree;
|
||||
//
|
||||
//import java.util.HashMap;
|
||||
//import java.util.List;
|
||||
//import java.util.Map;
|
||||
//import java.util.stream.Collectors;
|
||||
//
|
||||
///**
|
||||
// * @author lilemy
|
||||
// * @date 2025/4/24 11:48
|
||||
// */
|
||||
//public class JSTUtil {
|
||||
//
|
||||
// private static final GeometryFactory geometryFactory = new GeometryFactory();
|
||||
//
|
||||
// /**
|
||||
// * 获取最近点的名称
|
||||
// *
|
||||
// * @param target 目标点
|
||||
// * @param nameGeoJson 点对象列表
|
||||
// * @return 最近点的名称
|
||||
// */
|
||||
// public static String findNearestText(List<Double> target, List<FacFeatureByPoint> nameGeoJson) {
|
||||
// Point targetPoint = geometryFactory.createPoint(new Coordinate(target.get(0), target.get(1)));
|
||||
// FacFeatureByPoint nearestFeature = null;
|
||||
// double minDistance = Double.MAX_VALUE;
|
||||
// for (FacFeatureByPoint feature : nameGeoJson) {
|
||||
// FacGeometryByPoint geometry = feature.getGeometry();
|
||||
// List<Double> coords = geometry.getCoordinates();
|
||||
// if (coords != null && coords.size() == 2) {
|
||||
// Point currentPoint = geometryFactory.createPoint(new Coordinate(coords.get(0), coords.get(1)));
|
||||
// double distance = targetPoint.distance(currentPoint);
|
||||
// if (distance < minDistance) {
|
||||
// minDistance = distance;
|
||||
// nearestFeature = feature;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// if (nearestFeature != null && nearestFeature.getProperties() != null) {
|
||||
// return nearestFeature.getProperties().getText();
|
||||
// }
|
||||
// return null; // 如果没找到合适的点
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * 点是否在平面内
|
||||
// *
|
||||
// * @param planeLists 平面坐标
|
||||
// * @param pointList 点坐标
|
||||
// * @return 点是否在平面内
|
||||
// */
|
||||
// public static Boolean pointIsWithInPlane(List<List<Double>> planeLists, List<Double> pointList) {
|
||||
// // 构建平面
|
||||
// Coordinate[] coordinates = getPlaneCoordinate(planeLists);
|
||||
// Polygon polygon = geometryFactory.createPolygon(coordinates);
|
||||
// // 构建待判断点
|
||||
// Point point = geometryFactory.createPoint(new Coordinate(pointList.get(0), pointList.get(1)));
|
||||
// // 判断是否在多边形内
|
||||
// return polygon.contains(point);
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * 获取平面内点列表集合
|
||||
// *
|
||||
// * @param planeLists 平面坐标列表
|
||||
// * @param pointLists 点坐标列表集合
|
||||
// * @return 平面内点坐标列表集合
|
||||
// */
|
||||
// public static List<List<Double>> getPointInPlaneList(List<List<Double>> planeLists, List<List<Double>> pointLists) {
|
||||
// // 构建平面
|
||||
// Coordinate[] coordinates = getPlaneCoordinate(planeLists);
|
||||
// LinearRing shell = geometryFactory.createLinearRing(coordinates);
|
||||
// Polygon polygon = geometryFactory.createPolygon(shell);
|
||||
// // 获取平面内点结合
|
||||
// return pointLists.stream().filter(pointList -> {
|
||||
// // 构建待判断点
|
||||
// Point point = geometryFactory.createPoint(new Coordinate(pointList.get(0), pointList.get(1)));
|
||||
// // 判断是否在多边形内
|
||||
// return polygon.contains(point);
|
||||
// }).toList();
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * 平面是否在平面内
|
||||
// *
|
||||
// * @param referencePlane 参考平面
|
||||
// * @param comparePlane 比较平面
|
||||
// * @return 平面是否在平面内
|
||||
// */
|
||||
// public static Boolean planeIsWithInPlane(List<List<Double>> referencePlane, List<List<Double>> comparePlane) {
|
||||
// // 构建参考平面
|
||||
// Coordinate[] referenceCoordinates = getPlaneCoordinate(referencePlane);
|
||||
// Polygon referencePolygon = geometryFactory.createPolygon(referenceCoordinates);
|
||||
// // 构建比较平面
|
||||
// Coordinate[] compareCoordinates = getPlaneCoordinate(comparePlane);
|
||||
// Polygon comparePolygon = geometryFactory.createPolygon(compareCoordinates);
|
||||
// // 判断是否在多边形内
|
||||
// return referencePolygon.contains(comparePolygon);
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * 判断两个平面是否相交
|
||||
// *
|
||||
// * @param referencePlane 参考平面
|
||||
// * @param comparePlane 待比较平面
|
||||
// * @return 平面是否相交
|
||||
// */
|
||||
// public static Boolean arePolygonsIntersecting(List<List<Double>> referencePlane, List<List<Double>> comparePlane) {
|
||||
// // 构建 Polygon A(参考面)
|
||||
// Coordinate[] coordsA = referencePlane.stream()
|
||||
// .map(p -> new Coordinate(p.getFirst(), p.get(1)))
|
||||
// .toArray(Coordinate[]::new);
|
||||
// Polygon polygonA = geometryFactory.createPolygon(coordsA);
|
||||
// // 构建 Polygon B(比较面)
|
||||
// Coordinate[] coordsB = comparePlane.stream()
|
||||
// .map(p -> new Coordinate(p.getFirst(), p.get(1)))
|
||||
// .toArray(Coordinate[]::new);
|
||||
// Polygon polygonB = geometryFactory.createPolygon(coordsB);
|
||||
// // 使用 JTS 判断是否相交
|
||||
// return polygonA.intersects(polygonB);
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * 获取平面坐标数组
|
||||
// *
|
||||
// * @param planeLists 平面坐标列表
|
||||
// * @return 平面坐标数组
|
||||
// */
|
||||
// public static Coordinate[] getPlaneCoordinate(List<List<Double>> planeLists) {
|
||||
// return planeLists.stream().map(planeList ->
|
||||
// new Coordinate(planeList.getFirst(), planeList.get(1)))
|
||||
// .toList().toArray(new Coordinate[0]);
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * 获取二维坐标
|
||||
// *
|
||||
// * @param geometry 几何对象
|
||||
// * @return 二维坐标
|
||||
// */
|
||||
// public static List<List<Double>> getTwoDimensionalCoordinates(FacGeometry geometry) {
|
||||
// String type = geometry.getType();
|
||||
// List<Object> coordinates = geometry.getCoordinates();
|
||||
// return switch (type) {
|
||||
// case GeoJsonConstant.POINT -> throw new ServiceException("点位无法创建方阵", HttpStatus.BAD_REQUEST);
|
||||
// case GeoJsonConstant.LINE -> coordinates.stream()
|
||||
// .filter(obj -> obj instanceof List<?>)
|
||||
// .map(obj -> ((List<?>) obj).stream()
|
||||
// .filter(num -> num instanceof Number)
|
||||
// .map(num -> ((Number) num).doubleValue())
|
||||
// .collect(Collectors.toList()))
|
||||
// .collect(Collectors.toList());
|
||||
// case GeoJsonConstant.POLYGON -> coordinates.stream()
|
||||
// .filter(obj -> obj instanceof List<?>)
|
||||
// .flatMap(obj -> ((List<?>) obj).stream())
|
||||
// .filter(pointObj -> pointObj instanceof List<?>)
|
||||
// .map(pointObj -> ((List<?>) pointObj).stream()
|
||||
// .filter(num -> num instanceof Number)
|
||||
// .map(num -> ((Number) num).doubleValue())
|
||||
// .collect(Collectors.toList()))
|
||||
// .collect(Collectors.toList());
|
||||
// default -> throw new ServiceException("暂不支持该类型", HttpStatus.BAD_REQUEST);
|
||||
// };
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * 匹配最近的点,获取该点的名称
|
||||
// *
|
||||
// * @param polygon 平面
|
||||
// * @param points 点列表集合
|
||||
// * @return 最近点的名称
|
||||
// */
|
||||
// public static String findNearestPointText(List<List<Double>> polygon, List<FacFeatureByPoint> points) {
|
||||
// if (polygon == null || polygon.size() < 3 || points == null || points.isEmpty()) {
|
||||
// return null;
|
||||
// }
|
||||
// // 1. 构建 Polygon
|
||||
// Coordinate[] polygonCoords = polygon.stream()
|
||||
// .map(coord -> new Coordinate(coord.getFirst(), coord.get(1)))
|
||||
// .toArray(Coordinate[]::new);
|
||||
// Polygon jtsPolygon = geometryFactory.createPolygon(polygonCoords);
|
||||
// // 2. 构建空间索引(JTS STRtree)
|
||||
// STRtree spatialIndex = new STRtree();
|
||||
// Map<Coordinate, FacFeatureByPoint> coordToFeatureMap = new HashMap<>();
|
||||
// for (FacFeatureByPoint feature : points) {
|
||||
// List<Double> coords = feature.getGeometry().getCoordinates();
|
||||
// if (coords == null || coords.size() != 2) continue;
|
||||
// Coordinate coord = new Coordinate(coords.get(0), coords.get(1));
|
||||
// Point point = geometryFactory.createPoint(coord);
|
||||
// // 用点的 Envelope 加入索引
|
||||
// spatialIndex.insert(point.getEnvelopeInternal(), point);
|
||||
// coordToFeatureMap.put(coord, feature);
|
||||
// }
|
||||
// // 3. 查询距离 polygon 最近的点
|
||||
// // 用 polygon 中心点附近构造 Envelope(扩大一些范围)
|
||||
// Envelope searchEnv = jtsPolygon.getEnvelopeInternal();
|
||||
// searchEnv.expandBy(10); // 扩大搜索半径
|
||||
// @SuppressWarnings("unchecked")
|
||||
// List<Point> candidatePoints = spatialIndex.query(searchEnv);
|
||||
// double minDistance = Double.MAX_VALUE;
|
||||
// Coordinate nearestCoord = null;
|
||||
// for (Point point : candidatePoints) {
|
||||
// double distance = point.distance(jtsPolygon);
|
||||
// if (distance < minDistance) {
|
||||
// minDistance = distance;
|
||||
// nearestCoord = point.getCoordinate();
|
||||
// }
|
||||
// }
|
||||
// if (nearestCoord != null) {
|
||||
// FacFeatureByPoint nearestFeature = coordToFeatureMap.get(nearestCoord);
|
||||
// if (nearestFeature != null && nearestFeature.getProperties() != null) {
|
||||
// return nearestFeature.getProperties().getText();
|
||||
// }
|
||||
// }
|
||||
// return null;
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * 匹配最近的面,获取该面的信息
|
||||
// *
|
||||
// * @param pointFeature 点位
|
||||
// * @param polygons 平面列表
|
||||
// * @return 最近面的信息
|
||||
// */
|
||||
// public static FacFeatureByPlane findNearestOrContainingPolygon(
|
||||
// FacFeatureByPoint pointFeature,
|
||||
// List<FacFeatureByPlane> polygons
|
||||
// ) {
|
||||
// if (pointFeature == null || polygons == null || polygons.isEmpty()) {
|
||||
// return null;
|
||||
// }
|
||||
// List<Double> coords = pointFeature.getGeometry().getCoordinates();
|
||||
// if (coords == null || coords.size() != 2) return null;
|
||||
// Coordinate pointCoord = new Coordinate(coords.get(0), coords.get(1));
|
||||
// Point point = geometryFactory.createPoint(pointCoord);
|
||||
// FacFeatureByPlane nearestPolygon = null;
|
||||
// double minDistance = Double.MAX_VALUE;
|
||||
// for (FacFeatureByPlane polygonFeature : polygons) {
|
||||
// if (polygonFeature == null || polygonFeature.getGeometry() == null) {
|
||||
// continue; // 跳过空对象
|
||||
// }
|
||||
// List<List<Double>> polyCoords = polygonFeature.getGeometry().getCoordinates().getFirst();
|
||||
// Coordinate[] polygonCoords = polyCoords.stream()
|
||||
// .map(c -> new Coordinate(c.getFirst(), c.get(1)))
|
||||
// .toArray(Coordinate[]::new);
|
||||
// Polygon polygon = geometryFactory.createPolygon(polygonCoords);
|
||||
// // 优先使用包含点的 polygon
|
||||
// if (polygon.contains(point)) {
|
||||
// return polygonFeature;
|
||||
// }
|
||||
// double distance = polygon.distance(point);
|
||||
// if (distance < minDistance) {
|
||||
// minDistance = distance;
|
||||
// nearestPolygon = polygonFeature;
|
||||
// }
|
||||
// }
|
||||
// return nearestPolygon;
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * 判断一个点是否在多个区域中,返回第一个匹配区域的点集合(否则 null)
|
||||
// *
|
||||
// * @param lat 纬度
|
||||
// * @param lng 经度
|
||||
// * @param rangeListJson 多边形列表,每个为 JSON 数组(多边形的点数组)
|
||||
// * @return 匹配区域的 List<Point>,否则 null
|
||||
// */
|
||||
// public static List<GeoPoint> findMatchingRange(String lat, String lng, List<String> rangeListJson) {
|
||||
// double latitude = Double.parseDouble(lat);
|
||||
// double longitude = Double.parseDouble(lng);
|
||||
// Point point = geometryFactory.createPoint(new Coordinate(longitude, latitude));
|
||||
// for (String rangeJson : rangeListJson) {
|
||||
// List<GeoPoint> polygonPoints = JSONUtil.toList(rangeJson, GeoPoint.class);
|
||||
// if (polygonPoints.size() < 3) continue; // 不是有效多边形
|
||||
//
|
||||
// Polygon polygon = buildPolygon(polygonPoints);
|
||||
// if (polygon.contains(point)) {
|
||||
// return polygonPoints; // 找到匹配范围
|
||||
// }
|
||||
// }
|
||||
// return null;
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * 将点集合转换为 JTS 多边形
|
||||
// */
|
||||
// private static Polygon buildPolygon(List<GeoPoint> points) {
|
||||
// Coordinate[] coordinates = points.stream()
|
||||
// .map(p -> new Coordinate(p.getLng(), p.getLat()))
|
||||
// .toArray(Coordinate[]::new);
|
||||
//
|
||||
// // 需要闭合坐标环(首尾相连)
|
||||
// if (!coordinates[0].equals2D(coordinates[coordinates.length - 1])) {
|
||||
// Coordinate[] closed = new Coordinate[coordinates.length + 1];
|
||||
// System.arraycopy(coordinates, 0, closed, 0, coordinates.length);
|
||||
// closed[closed.length - 1] = coordinates[0];
|
||||
// coordinates = closed;
|
||||
// }
|
||||
//
|
||||
// LinearRing shell = geometryFactory.createLinearRing(coordinates);
|
||||
// return geometryFactory.createPolygon(shell);
|
||||
// }
|
||||
//
|
||||
//}
|
||||
@ -0,0 +1,24 @@
|
||||
package org.dromara.system.api.utils;
|
||||
|
||||
import cn.hutool.json.JSONArray;
|
||||
|
||||
/**
|
||||
* @author lilemy
|
||||
* @date 2025/5/30 14:55
|
||||
*/
|
||||
public class JsonDimensionUtil {
|
||||
|
||||
/**
|
||||
* 判断 JSONArray 的嵌套维度
|
||||
*
|
||||
* @param array 需要判断的 JSONArray
|
||||
* @return 返回维度层级:1 表示一维,2 表示二维,3 表示三维 ...
|
||||
*/
|
||||
public static int getJsonArrayDepth(Object array) {
|
||||
if (array instanceof JSONArray jsonArray && !jsonArray.isEmpty()) {
|
||||
return 1 + getJsonArrayDepth(jsonArray.getFirst());
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,34 @@
|
||||
//package org.dromara.system.api.utils;
|
||||
//
|
||||
//import cn.hutool.core.collection.CollUtil;
|
||||
//import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
//
|
||||
//import java.util.Collections;
|
||||
//import java.util.List;
|
||||
//import java.util.function.Function;
|
||||
//
|
||||
///**
|
||||
// * @author lilemy
|
||||
// * @date 2025/5/28 11:25
|
||||
// */
|
||||
//public class PageConvertUtil {
|
||||
//
|
||||
// /**
|
||||
// * 将 Page<T> 转换为 Page<V>
|
||||
// *
|
||||
// * @param source 原始分页数据
|
||||
// * @param mapper 实体 -> VO 的转换函数
|
||||
// * @return Page<V>
|
||||
// */
|
||||
// public static <T, V> Page<V> convert(Page<T> source, Function<T, V> mapper) {
|
||||
// Page<V> target = new Page<>(source.getCurrent(), source.getSize(), source.getTotal());
|
||||
// if (CollUtil.isEmpty(source.getRecords())) {
|
||||
// target.setRecords(Collections.emptyList());
|
||||
// } else {
|
||||
// List<V> voList = source.getRecords().stream().map(mapper).toList();
|
||||
// target.setRecords(voList);
|
||||
// }
|
||||
// return target;
|
||||
// }
|
||||
//
|
||||
//}
|
||||
Reference in New Issue
Block a user