|
@@ -9,18 +9,15 @@ import com.shkpr.service.alambizplugin.constants.GisMetadataDefine;
|
|
import com.shkpr.service.alambizplugin.constants.GisSurveyExcelDefine;
|
|
import com.shkpr.service.alambizplugin.constants.GisSurveyExcelDefine;
|
|
import com.shkpr.service.alambizplugin.constants.GisSurveySystemCheckKeys;
|
|
import com.shkpr.service.alambizplugin.constants.GisSurveySystemCheckKeys;
|
|
import com.shkpr.service.alambizplugin.constants.GisSurveySystemCheckResultHead;
|
|
import com.shkpr.service.alambizplugin.constants.GisSurveySystemCheckResultHead;
|
|
|
|
+import com.shkpr.service.alambizplugin.constants.GisSurveyTemplateDefine;
|
|
import com.shkpr.service.alambizplugin.constants.LogFlagBusiType;
|
|
import com.shkpr.service.alambizplugin.constants.LogFlagBusiType;
|
|
-import com.shkpr.service.alambizplugin.dto.GisMetadataLayerTemplate;
|
|
|
|
-import com.shkpr.service.alambizplugin.dto.GisSurveyLayerApplyPoint;
|
|
|
|
-import com.shkpr.service.alambizplugin.dto.GisSurveySystemCheckElement;
|
|
|
|
-import com.shkpr.service.alambizplugin.dto.GisSurveySystemCheckId;
|
|
|
|
-import com.shkpr.service.alambizplugin.dto.GisSurveySystemCheckResultDetail;
|
|
|
|
-import com.shkpr.service.alambizplugin.dto.GisSurveyThirdImportElement;
|
|
|
|
-import com.shkpr.service.alambizplugin.dto.GisSurveyThirdImportResultDetail;
|
|
|
|
-import com.shkpr.service.alambizplugin.dto.TypeDefine;
|
|
|
|
|
|
+import com.shkpr.service.alambizplugin.dto.*;
|
|
import com.shkpr.service.alambizplugin.exception.UncheckedInterruptedException;
|
|
import com.shkpr.service.alambizplugin.exception.UncheckedInterruptedException;
|
|
import org.apache.commons.lang3.StringUtils;
|
|
import org.apache.commons.lang3.StringUtils;
|
|
import org.locationtech.jts.geom.Coordinate;
|
|
import org.locationtech.jts.geom.Coordinate;
|
|
|
|
+import org.locationtech.jts.geom.Envelope;
|
|
|
|
+import org.locationtech.jts.index.strtree.STRtree;
|
|
|
|
+import org.locationtech.jts.util.NumberUtil;
|
|
import org.springframework.scheduling.annotation.Async;
|
|
import org.springframework.scheduling.annotation.Async;
|
|
import org.springframework.scheduling.annotation.AsyncResult;
|
|
import org.springframework.scheduling.annotation.AsyncResult;
|
|
import org.springframework.stereotype.Component;
|
|
import org.springframework.stereotype.Component;
|
|
@@ -29,9 +26,12 @@ import org.springframework.util.concurrent.ListenableFuture;
|
|
import java.math.BigDecimal;
|
|
import java.math.BigDecimal;
|
|
import java.math.RoundingMode;
|
|
import java.math.RoundingMode;
|
|
import java.nio.file.Path;
|
|
import java.nio.file.Path;
|
|
|
|
+import java.util.ArrayList;
|
|
|
|
+import java.util.Collections;
|
|
import java.util.HashMap;
|
|
import java.util.HashMap;
|
|
import java.util.List;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.Map;
|
|
|
|
+import java.util.Objects;
|
|
import java.util.stream.Collectors;
|
|
import java.util.stream.Collectors;
|
|
import java.util.stream.IntStream;
|
|
import java.util.stream.IntStream;
|
|
|
|
|
|
@@ -134,53 +134,106 @@ public class DuplicatePointsFinder {
|
|
*
|
|
*
|
|
* @param points 点集合
|
|
* @param points 点集合
|
|
* @param typeDefines 类型定义
|
|
* @param typeDefines 类型定义
|
|
|
|
+ * @param define 系统检查定义
|
|
* @return 重复点
|
|
* @return 重复点
|
|
*/
|
|
*/
|
|
@Async
|
|
@Async
|
|
public ListenableFuture<GisSurveySystemCheckResultDetail> findDuplicatePoints(List<GisSurveyLayerApplyPoint> points
|
|
public ListenableFuture<GisSurveySystemCheckResultDetail> findDuplicatePoints(List<GisSurveyLayerApplyPoint> points
|
|
- , List<TypeDefine> typeDefines, GisSurveySystemCheckId systemCheckId) throws InterruptedException {
|
|
|
|
|
|
+ , List<TypeDefine> typeDefines, GisSurveySystemCheckDefine define, GisSurveySystemCheckId systemCheckId) throws InterruptedException {
|
|
//经纬度精度
|
|
//经纬度精度
|
|
- int lonScale = getScale(typeDefines, LNG_KEY);
|
|
|
|
|
|
+ int lngScale = getScale(typeDefines, LNG_KEY);
|
|
int latScale = getScale(typeDefines, LAT_KEY);
|
|
int latScale = getScale(typeDefines, LAT_KEY);
|
|
|
|
+ //偏差集合
|
|
|
|
+ List<GisSurveySystemCheckDeviation> deviations = define != null ? define.getDeviation() : Collections.emptyList();
|
|
|
|
+ //经纬度公差
|
|
|
|
+ double lngTolerance = deviations.stream()
|
|
|
|
+ .filter(deviation -> Objects.equals(GisSurveyTemplateDefine.LNG, deviation.getProperty()))
|
|
|
|
+ .map(GisSurveySystemCheckDeviation::getTolerance)
|
|
|
|
+ .findFirst().orElse(0d);
|
|
|
|
+ double latTolerance = deviations.stream()
|
|
|
|
+ .filter(deviation -> Objects.equals(GisSurveyTemplateDefine.LAT, deviation.getProperty()))
|
|
|
|
+ .map(GisSurveySystemCheckDeviation::getTolerance)
|
|
|
|
+ .findFirst().orElse(0d);
|
|
|
|
+ //高程公差
|
|
|
|
+ double elevationTolerance = deviations.stream()
|
|
|
|
+ .filter(deviation -> Objects.equals(GisSurveyTemplateDefine.ELEVATION, deviation.getProperty()))
|
|
|
|
+ .map(GisSurveySystemCheckDeviation::getTolerance)
|
|
|
|
+ .findFirst().orElse(0d);
|
|
|
|
|
|
LogPrintMgr.getInstance().printLogMsg(LogLevelFlag.LOG_INFO, mBizType, mStrClassName,
|
|
LogPrintMgr.getInstance().printLogMsg(LogLevelFlag.LOG_INFO, mBizType, mStrClassName,
|
|
- String.format("开始执行寻找重复点 经度精度:%s 纬度精度:%s ======>", lonScale, latScale));
|
|
|
|
|
|
+ String.format("开始执行寻找重复点 经度精度:%s 纬度精度:%s 经度公差:%s 纬度公差:%s 高程公差:%s ======>",
|
|
|
|
+ lngScale, latScale, lngTolerance, latTolerance, elevationTolerance));
|
|
long begin = System.currentTimeMillis();
|
|
long begin = System.currentTimeMillis();
|
|
|
|
|
|
- try {
|
|
|
|
- //根据经纬度分组
|
|
|
|
- Map<String, List<GisSurveyLayerApplyPoint>> keyToPoints = points.parallelStream()
|
|
|
|
- //响应中断
|
|
|
|
- .peek(it -> {
|
|
|
|
- if (Thread.currentThread().isInterrupted())
|
|
|
|
- throw new UncheckedInterruptedException(new InterruptedException());
|
|
|
|
- })
|
|
|
|
- .collect(Collectors.groupingBy(point -> {
|
|
|
|
- //抹去经纬度精度,并设置为分组条件
|
|
|
|
- Coordinate coordinate = point.getGis().getCoordinate();
|
|
|
|
- BigDecimal bdLon = new BigDecimal(Double.toString(coordinate.getX())).setScale(lonScale, RoundingMode.DOWN);
|
|
|
|
- BigDecimal bdLat = new BigDecimal(Double.toString(coordinate.getY())).setScale(latScale, RoundingMode.DOWN);
|
|
|
|
- return String.format("%s%s%s", bdLon.toPlainString(), bdLat.toPlainString(), point.getElevation());
|
|
|
|
- }, Collectors.toList()));
|
|
|
|
|
|
+ //创建空间索引
|
|
|
|
+ STRtree tree = new STRtree();
|
|
|
|
+ //元素存入索引
|
|
|
|
+ for (GisSurveyLayerApplyPoint point : points) {
|
|
|
|
+ if (point.getGis() == null) continue;
|
|
|
|
+ tree.insert(
|
|
|
|
+ calcEnvelope(point.getGis().getCoordinate(), lngScale, latScale, lngTolerance, latTolerance),
|
|
|
|
+ point
|
|
|
|
+ );
|
|
|
|
+ }
|
|
|
|
+ tree.build();
|
|
|
|
+
|
|
|
|
+ //创建并查集
|
|
|
|
+ UnionFind uf = new UnionFind(points);
|
|
|
|
+
|
|
|
|
+ //遍历点元素,并合并并查集
|
|
|
|
+ for (GisSurveyLayerApplyPoint point1 : points) {
|
|
|
|
+ if (point1.getGis() == null) continue;
|
|
//响应中断
|
|
//响应中断
|
|
if (Thread.interrupted()) throw new InterruptedException();
|
|
if (Thread.interrupted()) throw new InterruptedException();
|
|
- //并行流寻找重复点
|
|
|
|
- List<List<GisSurveySystemCheckElement>> groupElements = keyToPoints.values().parallelStream()
|
|
|
|
|
|
+
|
|
|
|
+ //查询索引内元素
|
|
|
|
+ List<?> candidates = tree.query(calcEnvelope(
|
|
|
|
+ point1.getGis().getCoordinate(),
|
|
|
|
+ lngScale, latScale,
|
|
|
|
+ lngTolerance, latTolerance));
|
|
|
|
+ for (Object candidate : candidates) {
|
|
|
|
+ GisSurveyLayerApplyPoint point2 = (GisSurveyLayerApplyPoint) candidate;
|
|
|
|
+ //排除code相同的元素
|
|
|
|
+ if (Objects.equals(point1.getCode(), point2.getCode()) || point2.getGis() == null) continue;
|
|
|
|
+ //响应中断
|
|
|
|
+ if (Thread.interrupted()) throw new InterruptedException();
|
|
|
|
+ //判断是否完全重叠
|
|
|
|
+ if (calcFullOverlap(point1, point2, lngScale, latScale, lngTolerance, latTolerance, elevationTolerance)) {
|
|
|
|
+ //合并重叠点
|
|
|
|
+ uf.union(point1.getCode(), point2.getCode());
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ //按并查集结果分组
|
|
|
|
+ Map<Integer, List<GisSurveySystemCheckElement>> groupMap = new HashMap<>();
|
|
|
|
+ for (GisSurveyLayerApplyPoint point : points) {
|
|
|
|
+ if (point.getGis() == null) continue;
|
|
|
|
+ //响应中断
|
|
|
|
+ if (Thread.interrupted()) throw new InterruptedException();
|
|
|
|
+ //获取并查集的索引
|
|
|
|
+ Integer index = uf.getIndex(point.getCode());
|
|
|
|
+ if (index == null) continue;
|
|
|
|
+ //获取父节点的索引
|
|
|
|
+ int root = uf.find(index);
|
|
|
|
+ //获取当前父节点组
|
|
|
|
+ List<GisSurveySystemCheckElement> group = groupMap
|
|
|
|
+ .computeIfAbsent(root, k -> new ArrayList<>());
|
|
|
|
+ //填入当前元素
|
|
|
|
+ group.add(BeanUtil.copy(point, GisSurveySystemCheckElement.class));
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ try {
|
|
|
|
+ //并行流过滤大于2的元素
|
|
|
|
+ List<List<GisSurveySystemCheckElement>> collect = groupMap.values().parallelStream()
|
|
//响应中断
|
|
//响应中断
|
|
.peek(it -> {
|
|
.peek(it -> {
|
|
if (Thread.currentThread().isInterrupted())
|
|
if (Thread.currentThread().isInterrupted())
|
|
throw new UncheckedInterruptedException(new InterruptedException());
|
|
throw new UncheckedInterruptedException(new InterruptedException());
|
|
})
|
|
})
|
|
- //过滤组内大于1
|
|
|
|
- .filter(group -> group.size() > 1)
|
|
|
|
- //转为返回元素
|
|
|
|
- .map(group -> group.stream()
|
|
|
|
- .map(point -> BeanUtil.copy(point, GisSurveySystemCheckElement.class))
|
|
|
|
- .collect(Collectors.toList())
|
|
|
|
- )
|
|
|
|
|
|
+ .filter(it -> it.size() > 1)
|
|
.collect(Collectors.toList());
|
|
.collect(Collectors.toList());
|
|
-
|
|
|
|
- return new AsyncResult<>(createResult(groupElements, systemCheckId, begin));
|
|
|
|
|
|
+ return new AsyncResult<>(createResult(collect, systemCheckId, begin));
|
|
} catch (UncheckedInterruptedException e) {
|
|
} catch (UncheckedInterruptedException e) {
|
|
throw e.getCause();
|
|
throw e.getCause();
|
|
}
|
|
}
|
|
@@ -214,6 +267,59 @@ public class DuplicatePointsFinder {
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
/**
|
|
|
|
+ * 计算边界
|
|
|
|
+ *
|
|
|
|
+ * @param coordinate 坐标
|
|
|
|
+ * @param lngScale 经度精度
|
|
|
|
+ * @param latScale 经度精度
|
|
|
|
+ * @param lngTolerance 经度公差
|
|
|
|
+ * @param latTolerance 纬度公差
|
|
|
|
+ * @return 边界
|
|
|
|
+ */
|
|
|
|
+ private Envelope calcEnvelope(Coordinate coordinate, int lngScale, int latScale, double lngTolerance, double latTolerance) {
|
|
|
|
+ //抹去经纬度精度
|
|
|
|
+ BigDecimal bdLng = new BigDecimal(Double.toString(coordinate.getX())).setScale(lngScale, RoundingMode.DOWN);
|
|
|
|
+ BigDecimal bdLat = new BigDecimal(Double.toString(coordinate.getY())).setScale(latScale, RoundingMode.DOWN);
|
|
|
|
+
|
|
|
|
+ //计算公差边界
|
|
|
|
+ double minX = bdLng.subtract(BigDecimal.valueOf(lngTolerance)).doubleValue();
|
|
|
|
+ double maxX = bdLng.add(BigDecimal.valueOf(lngTolerance)).doubleValue();
|
|
|
|
+ double minY = bdLat.subtract(BigDecimal.valueOf(latTolerance)).doubleValue();
|
|
|
|
+ double maxY = bdLat.add(BigDecimal.valueOf(latTolerance)).doubleValue();
|
|
|
|
+
|
|
|
|
+ return new Envelope(minX, maxX, minY, maxY);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * 计算完全重叠
|
|
|
|
+ *
|
|
|
|
+ * @param point1 点1
|
|
|
|
+ * @param point2 点2
|
|
|
|
+ * @param lngScale 经度精度
|
|
|
|
+ * @param latScale 经度精度
|
|
|
|
+ * @param lngTolerance 经度公差
|
|
|
|
+ * @param latTolerance 纬度公差
|
|
|
|
+ * @param elevationTolerance 高程公差
|
|
|
|
+ * @return 重叠状态
|
|
|
|
+ */
|
|
|
|
+ private boolean calcFullOverlap(GisSurveyLayerApplyPoint point1, GisSurveyLayerApplyPoint point2
|
|
|
|
+ , int lngScale, int latScale
|
|
|
|
+ , double lngTolerance, double latTolerance, double elevationTolerance) {
|
|
|
|
+ //获取点1xyz
|
|
|
|
+ double x1 = BigDecimal.valueOf(point1.getGis().getX()).setScale(lngScale, RoundingMode.DOWN).doubleValue();
|
|
|
|
+ double y1 = BigDecimal.valueOf(point1.getGis().getY()).setScale(latScale, RoundingMode.DOWN).doubleValue();
|
|
|
|
+ double z1 = point1.getElevation();
|
|
|
|
+ //获取点2xyz
|
|
|
|
+ double x2 = BigDecimal.valueOf(point2.getGis().getX()).setScale(lngScale, RoundingMode.DOWN).doubleValue();
|
|
|
|
+ double y2 = BigDecimal.valueOf(point2.getGis().getY()).setScale(latScale, RoundingMode.DOWN).doubleValue();
|
|
|
|
+ double z2 = point2.getElevation();
|
|
|
|
+ //比较xyz
|
|
|
|
+ if (!NumberUtil.equalsWithTolerance(x1, x2, lngTolerance)) return false;
|
|
|
|
+ if (!NumberUtil.equalsWithTolerance(y1, y2, latTolerance)) return false;
|
|
|
|
+ return NumberUtil.equalsWithTolerance(z1, z2, elevationTolerance);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
* 创建结果
|
|
* 创建结果
|
|
*
|
|
*
|
|
* @param data 数据
|
|
* @param data 数据
|
|
@@ -265,4 +371,78 @@ public class DuplicatePointsFinder {
|
|
return map;
|
|
return map;
|
|
})).collect(Collectors.toList());
|
|
})).collect(Collectors.toList());
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * 并查集最
|
|
|
|
+ * <p>简单的实现</p>
|
|
|
|
+ */
|
|
|
|
+ private static class UnionFind {
|
|
|
|
+ // 点标识符(code)到索引的映射
|
|
|
|
+ private final Map<String, Integer> indexMapping = new HashMap<>();
|
|
|
|
+ //父节点信息
|
|
|
|
+ private final int[] parent;
|
|
|
|
+ //树高度
|
|
|
|
+ private final short[] rank;
|
|
|
|
+
|
|
|
|
+ public UnionFind(List<GisSurveyLayerApplyPoint> points) {
|
|
|
|
+ //过滤有效点
|
|
|
|
+ List<GisSurveyLayerApplyPoint> validPoints = points.stream()
|
|
|
|
+ .filter(p -> p.getGis() != null)
|
|
|
|
+ .collect(Collectors.toList());
|
|
|
|
+ //初始化关系和树高度
|
|
|
|
+ parent = new int[validPoints.size()];
|
|
|
|
+ rank = new short[validPoints.size()];
|
|
|
|
+ //初始化,每个节点的父节点是自己
|
|
|
|
+ for (int i = 0; i < validPoints.size(); i++) {
|
|
|
|
+ parent[i] = i;
|
|
|
|
+ rank[i] = 0;
|
|
|
|
+ indexMapping.put(validPoints.get(i).getCode(), i);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * 查找根节点
|
|
|
|
+ *
|
|
|
|
+ * @param i 当前节点索引
|
|
|
|
+ * @return 根结点
|
|
|
|
+ */
|
|
|
|
+ public int find(int i) {
|
|
|
|
+ //路径压缩
|
|
|
|
+ if (parent[i] != i) parent[i] = find(parent[i]);
|
|
|
|
+ return parent[i];
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * 获取索引
|
|
|
|
+ *
|
|
|
|
+ * @param code 节点code
|
|
|
|
+ * @return 并查集索引
|
|
|
|
+ */
|
|
|
|
+ public Integer getIndex(String code) {
|
|
|
|
+ return indexMapping.get(code);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * 合并两个节点关系
|
|
|
|
+ *
|
|
|
|
+ * @param code1 节点1的code
|
|
|
|
+ * @param code2 节点2的code
|
|
|
|
+ */
|
|
|
|
+ public void union(String code1, String code2) {
|
|
|
|
+ //获取索引
|
|
|
|
+ int i1 = indexMapping.get(code1);
|
|
|
|
+ int i2 = indexMapping.get(code2);
|
|
|
|
+ //获取根节点
|
|
|
|
+ int root1 = find(i1);
|
|
|
|
+ int root2 = find(i2);
|
|
|
|
+ // 已在同一根节点
|
|
|
|
+ if (root1 == root2) return;
|
|
|
|
+ //按秩合并,如 当前根秩 大于 目标根秩,则 目标根 为 当前根 的下游线
|
|
|
|
+ if (rank[root1] > rank[root2]) parent[root2] = root1;
|
|
|
|
+ else {
|
|
|
|
+ parent[root1] = root2;
|
|
|
|
+ if (rank[root1] == rank[root2]) rank[root2]++;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
}
|
|
}
|