package com.shkpr.service.alambizplugin.components.checker; import com.global.base.log.LogLevelFlag; import com.global.base.log.LogPrintMgr; import com.shkpr.service.alambizplugin.commtools.BeanUtil; import com.shkpr.service.alambizplugin.components.AsyncResultManager; import com.shkpr.service.alambizplugin.constants.GisSurveySystemCheckKeys; import com.shkpr.service.alambizplugin.constants.GisSurveySystemCheckResultHead; import com.shkpr.service.alambizplugin.constants.LogFlagBusiType; import com.shkpr.service.alambizplugin.dto.GisSurveyLayerApplyLine; import com.shkpr.service.alambizplugin.dto.GisSurveySystemCheckElement; import com.shkpr.service.alambizplugin.dto.GisSurveySystemCheckId; import com.shkpr.service.alambizplugin.dto.GisSurveySystemCheckResultDetail; import org.apache.commons.collections4.CollectionUtils; import org.locationtech.jts.algorithm.Orientation; import org.locationtech.jts.geom.Coordinate; import org.locationtech.jts.geom.LineSegment; import org.locationtech.jts.geom.LineString; import org.locationtech.jts.index.strtree.STRtree; import org.springframework.scheduling.annotation.Async; import org.springframework.scheduling.annotation.AsyncResult; import org.springframework.stereotype.Component; import org.springframework.util.concurrent.ListenableFuture; import java.nio.file.Path; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; import java.util.stream.IntStream; /** * 寻找重叠线 * * @author 欧阳劲驰 * @since 0.0.1 */ @Component public class OverlapLinesFinder { //双精度误差 private static final double EPSILON = Double.longBitsToDouble(0x3ca0000000000000L); /** * log */ private final String mStrClassName; private final String mBizType; private final AsyncResultManager asyncResultManager; public OverlapLinesFinder(AsyncResultManager asyncResultManager) { mStrClassName = "OverlapLinesFinder"; mBizType = LogFlagBusiType.BUSI_GIS_SURVEY.toStrValue(); this.asyncResultManager = asyncResultManager; } /** * 寻找重叠线 *

返回的为重叠线组,未重叠的线不会出现在返回结果

*

一组线,仅为两条重叠的线

*

如完全重叠,则全分为一组,会出现多条线一组的情况

* * @param lines 线集合 * @return 重叠线分组 */ @Async public ListenableFuture findOverlapLines(List lines , GisSurveySystemCheckId systemCheckId) throws InterruptedException { LogPrintMgr.getInstance().printLogMsg(LogLevelFlag.LOG_INFO, mBizType, mStrClassName, "开始执行寻找重叠线========>"); long begin = System.currentTimeMillis(); //创建空间索引 STRtree tree = new STRtree(); //元素存入索引 for (GisSurveyLayerApplyLine layerApplyLine : lines) { if (layerApplyLine.getGis() == null) continue; LineString line = layerApplyLine.getGis(); tree.insert(line.getEnvelopeInternal(), layerApplyLine); } tree.build(); //结果 List> groupElements = new ArrayList<>(); //处理过的code Set processedCodes = new HashSet<>(); //完全重叠过的code Set fullOverlapCodes = new HashSet<>(); //遍历所有线 for (GisSurveyLayerApplyLine line1 : lines) { if (line1.getGis() == null) continue; //响应中断 if (Thread.interrupted()) throw new InterruptedException(); //完全重叠组 List fullOverlaps = new ArrayList<>(); //查询索引内元素 List candidates = tree.query(line1.getGis().getEnvelopeInternal()); for (Object candidate : candidates) { GisSurveyLayerApplyLine line2 = (GisSurveyLayerApplyLine) candidate; //排除code相同或已经处理的元素, if (Objects.equals(line1.getCode(), line2.getCode()) || processedCodes.contains(line2.getCode()) || line2.getGis() == null ) continue; //响应中断 if (Thread.interrupted()) throw new InterruptedException(); //判断是否完全重叠(两线都未被标记过) if (!fullOverlapCodes.contains(line1.getCode()) && !fullOverlapCodes.contains(line2.getCode()) && calcFullOverlap(line1, line2)) { fullOverlaps.add(BeanUtil.copy(line2, GisSurveySystemCheckElement.class)); } //判断是否部分重叠(排除完全重叠) if (!calcFullOverlap(line1, line2) && calcPartialOverlap(line1, line2)) { // 创建两两一组的重叠线对 groupElements.add(Arrays.asList( BeanUtil.copy(line1, GisSurveySystemCheckElement.class), BeanUtil.copy(line2, GisSurveySystemCheckElement.class) )); } } //如完全重叠组不为空,则存入结果 if (CollectionUtils.isNotEmpty(fullOverlaps)) { //存入自身 fullOverlaps.add(BeanUtil.copy(line1, GisSurveySystemCheckElement.class)); //填入结果 groupElements.add(fullOverlaps); //标记完全重叠 fullOverlapCodes.addAll(fullOverlaps.stream() .map(GisSurveySystemCheckElement::getCode) .collect(Collectors.toSet()) ); } //存入code,避免重复处理 processedCodes.add(line1.getCode()); } return new AsyncResult<>(createResult(groupElements, systemCheckId, begin)); } /** * 计算完全重叠 * * @param line1 线段1 * @param line2 线段2 * @return 重叠状态 */ public boolean calcFullOverlap(GisSurveyLayerApplyLine line1, GisSurveyLayerApplyLine line2) { //取出四个点 Coordinate a = line1.getGis().getCoordinateN(0); Coordinate b = line1.getGis().getCoordinateN(1); Coordinate c = line2.getGis().getCoordinateN(0); Coordinate d = line2.getGis().getCoordinateN(1); if (a == null || b == null || c == null || d == null) return false; return (a.equals2D(c) && b.equals2D(d)) || (a.equals2D(d) && b.equals2D(c)); } /** * 计算部分重叠线 * * @param line1 线段1 * @param line2 线段2 * @return 重叠状态 */ public boolean calcPartialOverlap(GisSurveyLayerApplyLine line1, GisSurveyLayerApplyLine line2) { //取出四个点 Coordinate a = line1.getGis().getCoordinateN(0); Coordinate b = line1.getGis().getCoordinateN(1); Coordinate c = line2.getGis().getCoordinateN(0); Coordinate d = line2.getGis().getCoordinateN(1); //点数量判断 if (a == null || b == null || c == null || d == null) return false; //检查C和D是否在AB的直线上(叉积为0,则方向一致) if (Orientation.index(a, b, c) != 0 || Orientation.index(a, b, d) != 0) return false; LineSegment seg1 = new LineSegment(a, b); LineSegment seg2 = new LineSegment(c, d); //四点共点判断 if (seg1.equals(seg2)) return true; //获取C和D对于线段1的投影因子 double tC = seg1.projectionFactor(c); double tD = seg1.projectionFactor(d); //获取最小和最大投影因子 double cdMin = Math.min(tC, tD); double cdMax = Math.max(tC, tD); //排除前后延伸联通线 if (cdMin == 1 && cdMax >= 1) return false; if (cdMax == 0 && cdMin <= 0) return false; //判断最大因子是否向前延伸或处于线段内,并且最小因子是否向后延伸或处于线段内 return (cdMax + EPSILON >= 0) && (cdMin - EPSILON <= 1); } /** * 创建结果 * * @param data 数据 * @param systemCheckId 系统检查id * @param begin 开始时间 * @return 结果 */ private GisSurveySystemCheckResultDetail createResult(List> data , GisSurveySystemCheckId systemCheckId, long begin) { long end = System.currentTimeMillis(); LogPrintMgr.getInstance().printLogMsg(LogLevelFlag.LOG_INFO, mBizType, mStrClassName , String.format( "结束执行寻找重叠线,用时(毫秒):%d" , (end - begin) ) ); //数据大小 final int size = data.size(); //结果flag final String FLAG = systemCheckId.getFlag(); //写入json和excel结果 Path jsonPath = asyncResultManager.writeJson(data, FLAG, GisSurveySystemCheckKeys.OVERLAP_LINES + ".json"); Path excelPath = asyncResultManager.writeExcel(GisSurveySystemCheckResultHead.OVERLAP_LINES, buildExcel(data), FLAG, GisSurveySystemCheckKeys.OVERLAP_LINES + ".xlsx"); //构建结果 return new GisSurveySystemCheckResultDetail(true , FLAG + "/" + jsonPath.getFileName() , FLAG + "/" + excelPath.getFileName() , size); } /** * 构建excel数据 * * @param data 数据 * @return excel数据 */ private List> buildExcel(List> data) { return IntStream.range(0, data.size()) .boxed() .flatMap(i -> data.get(i).stream().map(element -> { Map map = new HashMap<>(); map.put(GisSurveySystemCheckResultHead.KEYS.GROUP_NAME, i + 1); map.put(GisSurveySystemCheckResultHead.KEYS.UP_NO, element.getUpNo()); map.put(GisSurveySystemCheckResultHead.KEYS.DOWN_NO, element.getDownNo()); map.put(GisSurveySystemCheckResultHead.KEYS.CODE, element.getCode()); map.put(GisSurveySystemCheckResultHead.KEYS.UP_NODE, element.getUpNode()); map.put(GisSurveySystemCheckResultHead.KEYS.DOWN_NODE, element.getDownNode()); map.put(GisSurveySystemCheckResultHead.KEYS.NAME, element.getName()); return map; })).collect(Collectors.toList()); } }