OverlapLinesFinder.java 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254
  1. package com.shkpr.service.alambizplugin.components.checker;
  2. import com.global.base.log.LogLevelFlag;
  3. import com.global.base.log.LogPrintMgr;
  4. import com.shkpr.service.alambizplugin.commtools.BeanUtil;
  5. import com.shkpr.service.alambizplugin.components.AsyncResultManager;
  6. import com.shkpr.service.alambizplugin.constants.GisSurveySystemCheckKeys;
  7. import com.shkpr.service.alambizplugin.constants.GisSurveySystemCheckResultHead;
  8. import com.shkpr.service.alambizplugin.constants.LogFlagBusiType;
  9. import com.shkpr.service.alambizplugin.dto.GisSurveyLayerApplyLine;
  10. import com.shkpr.service.alambizplugin.dto.GisSurveySystemCheckElement;
  11. import com.shkpr.service.alambizplugin.dto.GisSurveySystemCheckId;
  12. import com.shkpr.service.alambizplugin.dto.GisSurveySystemCheckResultDetail;
  13. import org.apache.commons.collections4.CollectionUtils;
  14. import org.locationtech.jts.algorithm.Orientation;
  15. import org.locationtech.jts.geom.Coordinate;
  16. import org.locationtech.jts.geom.LineSegment;
  17. import org.locationtech.jts.geom.LineString;
  18. import org.locationtech.jts.index.strtree.STRtree;
  19. import org.springframework.scheduling.annotation.Async;
  20. import org.springframework.scheduling.annotation.AsyncResult;
  21. import org.springframework.stereotype.Component;
  22. import org.springframework.util.concurrent.ListenableFuture;
  23. import java.nio.file.Path;
  24. import java.util.ArrayList;
  25. import java.util.Arrays;
  26. import java.util.HashMap;
  27. import java.util.HashSet;
  28. import java.util.List;
  29. import java.util.Map;
  30. import java.util.Objects;
  31. import java.util.Set;
  32. import java.util.stream.Collectors;
  33. import java.util.stream.IntStream;
  34. /**
  35. * 寻找重叠线
  36. *
  37. * @author 欧阳劲驰
  38. * @since 0.0.1
  39. */
  40. @Component
  41. public class OverlapLinesFinder {
  42. //双精度误差
  43. private static final double EPSILON = Double.longBitsToDouble(0x3ca0000000000000L);
  44. /**
  45. * log
  46. */
  47. private final String mStrClassName;
  48. private final String mBizType;
  49. private final AsyncResultManager asyncResultManager;
  50. public OverlapLinesFinder(AsyncResultManager asyncResultManager) {
  51. mStrClassName = "OverlapLinesFinder";
  52. mBizType = LogFlagBusiType.BUSI_GIS_SURVEY.toStrValue();
  53. this.asyncResultManager = asyncResultManager;
  54. }
  55. /**
  56. * 寻找重叠线
  57. * <p>返回的为重叠线组,未重叠的线不会出现在返回结果</p>
  58. * <p>一组线,仅为两条重叠的线</p>
  59. * <p>如完全重叠,则全分为一组,会出现多条线一组的情况</p>
  60. *
  61. * @param lines 线集合
  62. * @return 重叠线分组
  63. */
  64. @Async
  65. public ListenableFuture<GisSurveySystemCheckResultDetail> findOverlapLines(List<GisSurveyLayerApplyLine> lines
  66. , GisSurveySystemCheckId systemCheckId) throws InterruptedException {
  67. LogPrintMgr.getInstance().printLogMsg(LogLevelFlag.LOG_INFO, mBizType, mStrClassName, "开始执行寻找重叠线========>");
  68. long begin = System.currentTimeMillis();
  69. //创建空间索引
  70. STRtree tree = new STRtree();
  71. //元素存入索引
  72. for (GisSurveyLayerApplyLine layerApplyLine : lines) {
  73. if (layerApplyLine.getGis() == null) continue;
  74. LineString line = layerApplyLine.getGis();
  75. tree.insert(line.getEnvelopeInternal(), layerApplyLine);
  76. }
  77. tree.build();
  78. //结果
  79. List<List<GisSurveySystemCheckElement>> groupElements = new ArrayList<>();
  80. //处理过的code
  81. Set<String> processedCodes = new HashSet<>();
  82. //完全重叠过的code
  83. Set<String> fullOverlapCodes = new HashSet<>();
  84. //遍历所有线
  85. for (GisSurveyLayerApplyLine line1 : lines) {
  86. if (line1.getGis() == null) continue;
  87. //响应中断
  88. if (Thread.interrupted()) throw new InterruptedException();
  89. //完全重叠组
  90. List<GisSurveySystemCheckElement> fullOverlaps = new ArrayList<>();
  91. //查询索引内元素
  92. List<?> candidates = tree.query(line1.getGis().getEnvelopeInternal());
  93. for (Object candidate : candidates) {
  94. GisSurveyLayerApplyLine line2 = (GisSurveyLayerApplyLine) candidate;
  95. //排除code相同或已经处理的元素,
  96. if (Objects.equals(line1.getCode(), line2.getCode())
  97. || processedCodes.contains(line2.getCode())
  98. || line2.getGis() == null
  99. ) continue;
  100. //响应中断
  101. if (Thread.interrupted()) throw new InterruptedException();
  102. //判断是否完全重叠(两线都未被标记过)
  103. if (!fullOverlapCodes.contains(line1.getCode())
  104. && !fullOverlapCodes.contains(line2.getCode())
  105. && calcFullOverlap(line1, line2)) {
  106. fullOverlaps.add(BeanUtil.copy(line2, GisSurveySystemCheckElement.class));
  107. }
  108. //判断是否部分重叠(排除完全重叠)
  109. if (!calcFullOverlap(line1, line2) && calcPartialOverlap(line1, line2)) {
  110. // 创建两两一组的重叠线对
  111. groupElements.add(Arrays.asList(
  112. BeanUtil.copy(line1, GisSurveySystemCheckElement.class),
  113. BeanUtil.copy(line2, GisSurveySystemCheckElement.class)
  114. ));
  115. }
  116. }
  117. //如完全重叠组不为空,则存入结果
  118. if (CollectionUtils.isNotEmpty(fullOverlaps)) {
  119. //存入自身
  120. fullOverlaps.add(BeanUtil.copy(line1, GisSurveySystemCheckElement.class));
  121. //填入结果
  122. groupElements.add(fullOverlaps);
  123. //标记完全重叠
  124. fullOverlapCodes.addAll(fullOverlaps.stream()
  125. .map(GisSurveySystemCheckElement::getCode)
  126. .collect(Collectors.toSet())
  127. );
  128. }
  129. //存入code,避免重复处理
  130. processedCodes.add(line1.getCode());
  131. }
  132. return new AsyncResult<>(createResult(groupElements, systemCheckId, begin));
  133. }
  134. /**
  135. * 计算完全重叠
  136. *
  137. * @param line1 线段1
  138. * @param line2 线段2
  139. * @return 重叠状态
  140. */
  141. public boolean calcFullOverlap(GisSurveyLayerApplyLine line1, GisSurveyLayerApplyLine line2) {
  142. //取出四个点
  143. Coordinate a = line1.getGis().getCoordinateN(0);
  144. Coordinate b = line1.getGis().getCoordinateN(1);
  145. Coordinate c = line2.getGis().getCoordinateN(0);
  146. Coordinate d = line2.getGis().getCoordinateN(1);
  147. if (a == null || b == null || c == null || d == null) return false;
  148. return (a.equals2D(c) && b.equals2D(d)) || (a.equals2D(d) && b.equals2D(c));
  149. }
  150. /**
  151. * 计算部分重叠线
  152. *
  153. * @param line1 线段1
  154. * @param line2 线段2
  155. * @return 重叠状态
  156. */
  157. public boolean calcPartialOverlap(GisSurveyLayerApplyLine line1, GisSurveyLayerApplyLine line2) {
  158. //取出四个点
  159. Coordinate a = line1.getGis().getCoordinateN(0);
  160. Coordinate b = line1.getGis().getCoordinateN(1);
  161. Coordinate c = line2.getGis().getCoordinateN(0);
  162. Coordinate d = line2.getGis().getCoordinateN(1);
  163. //点数量判断
  164. if (a == null || b == null || c == null || d == null) return false;
  165. //检查C和D是否在AB的直线上(叉积为0,则方向一致)
  166. if (Orientation.index(a, b, c) != 0 || Orientation.index(a, b, d) != 0) return false;
  167. LineSegment seg1 = new LineSegment(a, b);
  168. LineSegment seg2 = new LineSegment(c, d);
  169. //四点共点判断
  170. if (seg1.equals(seg2)) return true;
  171. //获取C和D对于线段1的投影因子
  172. double tC = seg1.projectionFactor(c);
  173. double tD = seg1.projectionFactor(d);
  174. //获取最小和最大投影因子
  175. double cdMin = Math.min(tC, tD);
  176. double cdMax = Math.max(tC, tD);
  177. //排除前后延伸联通线
  178. if (cdMin == 1 && cdMax >= 1) return false;
  179. if (cdMax == 0 && cdMin <= 0) return false;
  180. //判断最大因子是否向前延伸或处于线段内,并且最小因子是否向后延伸或处于线段内
  181. return (cdMax + EPSILON >= 0) && (cdMin - EPSILON <= 1);
  182. }
  183. /**
  184. * 创建结果
  185. *
  186. * @param data 数据
  187. * @param systemCheckId 系统检查id
  188. * @param begin 开始时间
  189. * @return 结果
  190. */
  191. private GisSurveySystemCheckResultDetail createResult(List<List<GisSurveySystemCheckElement>> data
  192. , GisSurveySystemCheckId systemCheckId, long begin) {
  193. long end = System.currentTimeMillis();
  194. LogPrintMgr.getInstance().printLogMsg(LogLevelFlag.LOG_INFO, mBizType, mStrClassName
  195. , String.format(
  196. "结束执行寻找重叠线,用时(毫秒):%d"
  197. , (end - begin)
  198. )
  199. );
  200. //数据大小
  201. final int size = data.size();
  202. //结果flag
  203. final String FLAG = systemCheckId.getFlag();
  204. //写入json和excel结果
  205. Path jsonPath = asyncResultManager.writeJson(data, FLAG, GisSurveySystemCheckKeys.OVERLAP_LINES + ".json");
  206. Path excelPath = asyncResultManager.writeExcel(GisSurveySystemCheckResultHead.OVERLAP_LINES,
  207. buildExcel(data), FLAG, GisSurveySystemCheckKeys.OVERLAP_LINES + ".xlsx");
  208. //构建结果
  209. return new GisSurveySystemCheckResultDetail(true
  210. , FLAG + "/" + jsonPath.getFileName()
  211. , FLAG + "/" + excelPath.getFileName()
  212. , size);
  213. }
  214. /**
  215. * 构建excel数据
  216. *
  217. * @param data 数据
  218. * @return excel数据
  219. */
  220. private List<Map<String, Object>> buildExcel(List<List<GisSurveySystemCheckElement>> data) {
  221. return IntStream.range(0, data.size())
  222. .boxed()
  223. .flatMap(i -> data.get(i).stream().map(element -> {
  224. Map<String, Object> map = new HashMap<>();
  225. map.put(GisSurveySystemCheckResultHead.KEYS.GROUP_NAME, i + 1);
  226. map.put(GisSurveySystemCheckResultHead.KEYS.UP_NO, element.getUpNo());
  227. map.put(GisSurveySystemCheckResultHead.KEYS.DOWN_NO, element.getDownNo());
  228. map.put(GisSurveySystemCheckResultHead.KEYS.CODE, element.getCode());
  229. map.put(GisSurveySystemCheckResultHead.KEYS.UP_NODE, element.getUpNode());
  230. map.put(GisSurveySystemCheckResultHead.KEYS.DOWN_NODE, element.getDownNode());
  231. map.put(GisSurveySystemCheckResultHead.KEYS.NAME, element.getName());
  232. return map;
  233. })).collect(Collectors.toList());
  234. }
  235. }