IsolatedLinesFinder.java 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276
  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.springframework.scheduling.annotation.Async;
  14. import org.springframework.scheduling.annotation.AsyncResult;
  15. import org.springframework.stereotype.Component;
  16. import org.springframework.util.concurrent.ListenableFuture;
  17. import java.nio.file.Path;
  18. import java.util.ArrayList;
  19. import java.util.Collection;
  20. import java.util.Collections;
  21. import java.util.HashMap;
  22. import java.util.List;
  23. import java.util.Map;
  24. import java.util.stream.Collectors;
  25. import java.util.stream.IntStream;
  26. /**
  27. * 孤立线查找
  28. *
  29. * @author 欧阳劲驰
  30. * @since 1.0.0
  31. */
  32. @Component
  33. public class IsolatedLinesFinder {
  34. /**
  35. * log
  36. */
  37. private final String mStrClassName;
  38. private final String mBizType;
  39. private final AsyncResultManager asyncResultManager;
  40. public IsolatedLinesFinder(AsyncResultManager asyncResultManager) {
  41. mStrClassName = "IsolatedLinesFinder";
  42. mBizType = LogFlagBusiType.BUSI_GIS_SURVEY.toStrValue();
  43. this.asyncResultManager = asyncResultManager;
  44. }
  45. /**
  46. * 寻找孤立线
  47. *
  48. * @param lines 线集合
  49. * @return 线分组
  50. */
  51. @Async
  52. public ListenableFuture<GisSurveySystemCheckResultDetail> findIsolatedLines(List<GisSurveyLayerApplyLine> lines
  53. , GisSurveySystemCheckId systemCheckId) throws InterruptedException {
  54. LogPrintMgr.getInstance().printLogMsg(LogLevelFlag.LOG_INFO, mBizType, mStrClassName, "开始执行寻找孤立线========>");
  55. long begin = System.currentTimeMillis();
  56. //创建并查集
  57. UnionFind unionFind = new UnionFind();
  58. //分组线
  59. List<List<GisSurveySystemCheckElement>> groupElements = unionFind.groupLines(lines);
  60. return new AsyncResult<>(createResult(groupElements, systemCheckId, begin));
  61. }
  62. /**
  63. * 创建结果
  64. *
  65. * @param data 数据
  66. * @param systemCheckId 系统检查id
  67. * @param begin 开始时间
  68. * @return 结果
  69. */
  70. private GisSurveySystemCheckResultDetail createResult(List<List<GisSurveySystemCheckElement>> data
  71. , GisSurveySystemCheckId systemCheckId, long begin) {
  72. long end = System.currentTimeMillis();
  73. LogPrintMgr.getInstance().printLogMsg(LogLevelFlag.LOG_INFO, mBizType, mStrClassName
  74. , String.format(
  75. "结束执行寻找孤立线,用时(毫秒):%d"
  76. , (end - begin)
  77. )
  78. );
  79. //数据大小
  80. final int size = data.size();
  81. //结果flag
  82. final String FLAG = systemCheckId.getFlag();
  83. //写入json和excel结果
  84. Path jsonPath = asyncResultManager.writeJson(data, FLAG, GisSurveySystemCheckKeys.ISOLATED_LINES + ".json");
  85. Path excelPath = asyncResultManager.writeExcel(GisSurveySystemCheckResultHead.ISOLATED_LINES,
  86. buildExcel(data), FLAG, GisSurveySystemCheckKeys.ISOLATED_LINES + ".xlsx");
  87. //构建结果
  88. return new GisSurveySystemCheckResultDetail(true
  89. , FLAG + "/" + jsonPath.getFileName()
  90. , FLAG + "/" + excelPath.getFileName()
  91. , size);
  92. }
  93. /**
  94. * 构建excel数据
  95. *
  96. * @param data 数据
  97. * @return excel数据
  98. */
  99. private List<Map<String, Object>> buildExcel(List<List<GisSurveySystemCheckElement>> data) {
  100. return IntStream.range(0, data.size())
  101. .boxed()
  102. .flatMap(i -> data.get(i).stream().map(element -> {
  103. Map<String, Object> map = new HashMap<>();
  104. map.put(GisSurveySystemCheckResultHead.KEYS.GROUP_NAME, i + 1);
  105. map.put(GisSurveySystemCheckResultHead.KEYS.UP_NO, element.getUpNo());
  106. map.put(GisSurveySystemCheckResultHead.KEYS.DOWN_NO, element.getDownNo());
  107. map.put(GisSurveySystemCheckResultHead.KEYS.CODE, element.getCode());
  108. map.put(GisSurveySystemCheckResultHead.KEYS.UP_NODE, element.getUpNode());
  109. map.put(GisSurveySystemCheckResultHead.KEYS.DOWN_NODE, element.getDownNode());
  110. map.put(GisSurveySystemCheckResultHead.KEYS.NAME, element.getName());
  111. return map;
  112. })).collect(Collectors.toList());
  113. }
  114. /**
  115. * 并查集
  116. */
  117. public static class UnionFind {
  118. //线的子父关系,key:子,value:父(可能为根)
  119. private final Map<String, String> parent = new HashMap<>();
  120. //点根线关系,{线的节点编码}->{根线编码}
  121. private final Map<String, String> pointRoot = new HashMap<>();
  122. //秩,通常树的根节点为1
  123. private final Map<String, Integer> rank = new HashMap<>();
  124. /**
  125. * 线分组
  126. *
  127. * @param lines 线集合
  128. * @return 分组集合
  129. */
  130. public List<List<GisSurveySystemCheckElement>> groupLines(List<GisSurveyLayerApplyLine> lines) throws InterruptedException {
  131. //处理所有线
  132. for (GisSurveyLayerApplyLine line : lines) {
  133. //响应中断
  134. if (Thread.interrupted()) throw new InterruptedException();
  135. //初始化节点
  136. initNode(line.getCode());
  137. //当前code
  138. String currentRoot = line.getCode();
  139. //处理起点,合并当前线与以upNode为终点的根线
  140. if (pointRoot.containsKey(line.getUpNode())) {
  141. String existingRoot = pointRoot.get(line.getUpNode());
  142. currentRoot = unionAndGetRoot(currentRoot, existingRoot);
  143. }
  144. //处理终点,合并当前线与以downNode为终点的根线
  145. if (pointRoot.containsKey(line.getDownNode())) {
  146. String existingRoot = pointRoot.get(line.getDownNode());
  147. currentRoot = unionAndGetRoot(currentRoot, existingRoot);
  148. }
  149. //更新当前节点的根线映射
  150. updateRootMappings(line.getUpNode(), currentRoot);
  151. updateRootMappings(line.getDownNode(), currentRoot);
  152. }
  153. //清理关系表
  154. pointRoot.clear();
  155. //构建分组结果
  156. Map<String, List<GisSurveySystemCheckElement>> groups = new HashMap<>();
  157. for (GisSurveyLayerApplyLine line : lines) {
  158. //响应中断
  159. if (Thread.interrupted()) throw new InterruptedException();
  160. //当前线code
  161. String lineCode = line.getCode();
  162. //根线code
  163. String rootCode = findRoot(lineCode);
  164. //当前线存入根线组
  165. groups.computeIfAbsent(rootCode, k -> new ArrayList<>())
  166. //转为返回元素
  167. .add(BeanUtil.copy(line, GisSurveySystemCheckElement.class));
  168. }
  169. //分组结果值集合
  170. Collection<List<GisSurveySystemCheckElement>> groupValues = groups.values();
  171. //排除1组数据
  172. if (groupValues.size() == 1) groupValues = Collections.emptyList();
  173. return new ArrayList<>(groupValues);
  174. }
  175. /**
  176. * 更新节点对应的根线映射
  177. *
  178. * @param node 节点
  179. * @param currentRoot 当前节点的根线
  180. */
  181. private void updateRootMappings(String node, String currentRoot) throws InterruptedException {
  182. //获取点根线关系,如存在该关系,则再寻找一次根线,保证多点根一致
  183. String existingRoot = pointRoot.get(node);
  184. if (existingRoot != null) currentRoot = unionAndGetRoot(currentRoot, existingRoot);
  185. //更新点根线关系
  186. pointRoot.put(node, currentRoot);
  187. }
  188. /**
  189. * 初始化节线
  190. *
  191. * @param code 节点code
  192. */
  193. public void initNode(String code) {
  194. parent.computeIfAbsent(code, k -> {
  195. rank.put(k, 0);
  196. return k;
  197. });
  198. }
  199. /**
  200. * 查找根节点
  201. *
  202. * @param code 节点code
  203. * @return 根节点code
  204. */
  205. public String findRoot(String code) throws InterruptedException {
  206. //当前code
  207. String current = code;
  208. //最大迭代深度(该算法时间为常数级,如查找深度到64则为程序异常)
  209. int maxIterationsDepth = 64;
  210. //迭代记录,防止无限递归
  211. int iterations = 0;
  212. while (iterations++ < maxIterationsDepth) {
  213. //响应中断
  214. if (Thread.interrupted()) throw new InterruptedException();
  215. //提取父节点
  216. String parentNode = parent.get(current);
  217. // 找到根节点立即返回
  218. if (parentNode.equals(current)) return current;
  219. //当前节点指向祖父节点,路径减半(已知子父关系,故跳过)
  220. String grandparent = parent.get(parentNode);
  221. parent.put(current, grandparent);
  222. current = grandparent;
  223. }
  224. //递归保险,最终强制验证是否为根节点,否则结束查找
  225. if (parent.get(current).equals(current)) return current;
  226. else throw new IllegalStateException("该code无法查询到根节点: " + code);
  227. }
  228. /**
  229. * 合并并返回根线
  230. *
  231. * @param current 当前线
  232. * @param target 目标线
  233. * @return 根线
  234. */
  235. public String unionAndGetRoot(String current, String target) throws InterruptedException {
  236. //获取两线的根线
  237. String rootCurrent = findRoot(current);
  238. String rootTarget = findRoot(target);
  239. //排除根线相同
  240. if (rootCurrent.equals(rootTarget)) return rootCurrent;
  241. //按秩合并,如 当前线根节点秩 大于 目标线根节点秩,则 目标线根线 为 当前线根线 的下游线
  242. if (rank.get(rootCurrent) > rank.get(rootTarget)) {
  243. parent.put(rootTarget, rootCurrent);
  244. return rootCurrent;
  245. } else {
  246. //否则 当前线根线 为 目标线根线 的下游线
  247. parent.put(rootCurrent, rootTarget);
  248. //如线秩相同,则对根线升秩
  249. if (rank.get(rootCurrent).equals(rank.get(rootTarget))) {
  250. rank.put(rootTarget, rank.get(rootTarget) + 1);
  251. }
  252. return rootTarget;
  253. }
  254. }
  255. }
  256. }