Parcourir la source

自动连线计算和布局

zhaoyk il y a 2 ans
Parent
commit
913e3339c2

+ 46 - 21
adm-business/adm-diagram/src/main/java/com/persagy/adm/diagram/core/DiagramBuilder.java

@@ -44,6 +44,11 @@ public class DiagramBuilder {
 	 */
 	private HashMap<String, Object> equipMap = new HashMap<>();
 
+	/**
+	 * 记录已经使用的关系id和对应的连线
+	 */
+	private HashMap<String, Line> lineMap = new HashMap<>();
+
 	public DiagramBuilder(Diagram diagram, DataStrategy dataStrategy) {
 		this.diagram = diagram;
 		this.template = diagram.getTemplate();
@@ -55,16 +60,24 @@ public class DiagramBuilder {
 	private void init(){
 		//记录节点中已使用的数据id
 		diagram.getNodes().forEach(node -> {
-			if (EquipmentNode.TYPE.equals(node.getCompType()))
+			if (EquipmentNode.TYPE.equals(node.getCompType())) {
 				equipMap.put(((EquipmentNode) node).getObjId(), node);
+			}
 		});
 		//记录干管中已使用的数据id
 		if(template.getMainPipes() != null) {
 			template.getMainPipes().forEach(mainPipe -> {
-				if(StrUtil.isNotBlank(mainPipe.getDataObjectId()))
+				if(StrUtil.isNotBlank(mainPipe.getDataObjectId())) {
 					equipMap.put(mainPipe.getDataObjectId(), mainPipe);
+				}
 			});
 		}
+
+		diagram.getLines().forEach(line -> {
+			if (StrUtil.isNotBlank(line.getDataObjectId())){
+				lineMap.put(line.getDataObjectId(), line);
+			}
+		});
 	}
 
 	//加载设备数据,并进行计算处理
@@ -282,21 +295,18 @@ public class DiagramBuilder {
 	
 	public void buildLines(List<ObjectNode> optionalRels){
 		for(ObjectNode rel : optionalRels) {
-			String from = rel.get(ObjectRelation.OBJ_FROM_HUM).asText();
-			String to = rel.get(ObjectRelation.OBJ_TO_HUM).asText();
-
-			Object fromObj = equipMap.get(from);
-			Object toObj = equipMap.get(to);
+			Object fromObj = equipMap.get(rel.get(ObjectRelation.OBJ_FROM_HUM).asText());
+			Object toObj = equipMap.get(rel.get(ObjectRelation.OBJ_TO_HUM).asText());
 
 			if(fromObj != null && toObj != null) {
 				String relType = rel.get(ObjectRelation.GRAPH_CODE_HUM).asText() + '/' + rel.get(ObjectRelation.REL_CODE_HUM).asText();
-				ConnectPoint p1 = getConnectPoint(fromObj, relType, toObj);
-				if(p1 != null) {
-					ConnectPoint p2 = getConnectPoint(toObj, relType, fromObj);
-					if(p2 != null) {
+				ConnectPoint from = getConnectPoint(fromObj, relType, toObj);
+				if(from != null) {
+					ConnectPoint to = getConnectPoint(toObj, relType, fromObj);
+					if(to != null) {
 						Line line = new Line();
-						line.setFrom(p1);
-						line.setTo(p2);
+						line.setFrom(from);
+						line.setTo(to);
 						line.setRelType(relType);
 						line.setDataObject(rel);
 
@@ -307,28 +317,39 @@ public class DiagramBuilder {
 		}
 
 		//test code TODO
+//		diagram.getLines().clear();
 		EquipmentNode n1 = null;
 		EquipmentNode n2 = null;
+		MainPipe mp2 = null;
 		for(Object o : equipMap.values()) {
 			if(o instanceof EquipmentNode){
-				String txt = ((EquipmentNode) o).getLabel().getContent();
+				String txt = DiagramBuilder.getName((ObjectNode) ((EquipmentNode) o).getDataObject());
 				if("1#进线柜".equals(txt))
 					n1 = (EquipmentNode) o;
-				else if("1#计量柜".equals(txt))
+				else if("2#变压器柜".equals(txt))
 					n2 = (EquipmentNode) o;
+			} else if(o instanceof MainPipe){
+				String txt = DiagramBuilder.getName((ObjectNode) ((MainPipe) o).getDataObject());
+				if("1#高压供电母线".equals(txt))
+					mp2 = (MainPipe) o;
 			}
 		}
-		if(n1 != null && n2 != null) {
+		if(n1 != null && n2 != null && mp2 != null) {
 			ConnectPoint p1 = new ConnectPoint();
 			p1.setHostType(EquipmentNode.TYPE);
 			p1.setHostId(n1.getId());
-			p1.setAnchorCode("T3");
+			p1.setAnchorCode("B3");
 			p1.setHostObj(n1);
+
 			ConnectPoint p2 = new ConnectPoint();
-			p2.setHostType(EquipmentNode.TYPE);
-			p2.setHostId(n2.getId());
-			p2.setAnchorCode("B3");
-			p2.setHostObj(n2);
+//			p2.setHostType(EquipmentNode.TYPE);
+//			p2.setHostId(n2.getId());
+//			p2.setAnchorCode("T3");
+//			p2.setHostObj(n2);
+			p2.setHostType(MainPipe.TYPE);
+			p2.setHostId(mp2.getId());
+			p2.setHostObj(mp2);
+
 			Line line = new Line();
 			line.setFrom(p1);
 			line.setTo(p2);
@@ -424,6 +445,10 @@ public class DiagramBuilder {
 		return equipMap;
 	}
 
+	public HashMap<String, Line> getLineMap() {
+		return lineMap;
+	}
+
 	public HashSet<String> getRefRelTypes() {
 		return refRelTypes;
 	}

+ 37 - 25
adm-business/adm-diagram/src/main/java/com/persagy/adm/diagram/core/DiagramDataLoader.java

@@ -1,6 +1,5 @@
 package com.persagy.adm.diagram.core;
 
-import cn.hutool.core.collection.CollUtil;
 import com.fasterxml.jackson.databind.node.ObjectNode;
 import com.persagy.adm.diagram.core.model.Diagram;
 import com.persagy.adm.diagram.core.model.DiagramNode;
@@ -9,7 +8,10 @@ import com.persagy.adm.diagram.core.model.Line;
 import com.persagy.adm.diagram.core.model.base.Container;
 import com.persagy.adm.diagram.core.model.template.MainPipe;
 
-import java.util.*;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
 import java.util.stream.Collectors;
 
 /**
@@ -22,8 +24,6 @@ public class DiagramDataLoader {
 
 	private DataStrategy dataStrategy;
 
-	private List<ObjectNode> rels;
-
 	private List<ObjectNode> optionalObjs;
 
 	private List<ObjectNode> optionalRels;
@@ -40,25 +40,21 @@ public class DiagramDataLoader {
 		List<String> objIds = diagram.getObjIds();
 		if(objIds.size() > 0) {
 			List<ObjectNode> objs = dataStrategy.loadObjectsById(objIds, diagram.getProjectId(), diagram.getGroupCode());
-			if(CollUtil.isNotEmpty(objs))
-				initNodes(objs);
-			else
-				throw new DiagramException("对象数据加载失败");
+			initNodes(objs);
 		}
 
 		List<String> relIds = diagram.getRelIds();
 		if(relIds.size() > 0) {
 			List<ObjectNode> rels = dataStrategy.loadRelationsById(relIds, diagram.getProjectId(), diagram.getGroupCode());
-			if(CollUtil.isNotEmpty(rels))
-				initLines(rels);
-			else
-				throw new DiagramException("关系数据加载失败");
+			initLines(rels);
 		}
 	}
 
 	private void initNodes(List<ObjectNode> objs) {
 		HashMap<String, ObjectNode> objMap = new HashMap<>();
-		objs.forEach(obj -> objMap.put(obj.get("id").asText(), obj));
+		if(objs != null) {
+			objs.forEach(obj -> objMap.put(obj.get("id").asText(), obj));
+		}
 
 		for (DiagramNode node : diagram.getNodes()) {
 			ObjectNode obj = null;
@@ -68,13 +64,14 @@ public class DiagramDataLoader {
 				obj = objMap.get(en.getObjId());
 
 				if(en.getLegendId() != null)
-					en.setLegend(dataStrategy.getLegend(en.getLegendId(), en.getObjSystemCode()));
+					en.setLegend(dataStrategy.getLegend(en.getLegendId(), en.getObjSystemCode())); //TODO 批量查询优化
 				else
 					en.setLegend(DiagramBuilder.emptyLegend());
 			}
 
-			if(obj != null)
+			if(obj != null) {
 				node.setDataObject(obj);
+			}
 		}
 
 		//设备类干管
@@ -82,8 +79,9 @@ public class DiagramDataLoader {
 			for(MainPipe mainPipe : diagram.getTemplate().getMainPipes()) {
 				if(mainPipe.isBindEquipment()){
 					ObjectNode obj = objMap.get(mainPipe.getDataObjectId());
-					if(obj != null)
+					if(obj != null) {
 						mainPipe.setDataObject(obj);
+					}
 				}
 			}
 		}
@@ -91,13 +89,16 @@ public class DiagramDataLoader {
 
 	private void initLines(List<ObjectNode> rels){
 		HashMap<String, ObjectNode> relMap = new HashMap<>();
-		rels.forEach(obj -> relMap.put(obj.get("id").asText(), obj));
+		if(rels != null) {
+			rels.forEach(obj -> relMap.put(obj.get("id").asText(), obj));
+		}
 
 		for(Line line : diagram.getLines()) {
 			if(line.getDataObjectId() != null){
 				ObjectNode rel = relMap.get(line.getDataObjectId());
-				if(rel != null)
+				if(rel != null) {
 					line.setDataObject(rel);
+				}
 			}
 		}
 	}
@@ -110,37 +111,48 @@ public class DiagramDataLoader {
 		HashSet<String> equipTypes = new HashSet<>();
 		List<Container> containers = diagram.getTemplate().getContainers();
 		for(Container con : containers) {
-			if(con.getEquipmentTypes() != null)
+			if(con.getEquipmentTypes() != null) {
 				equipTypes.addAll(con.getEquipmentTypes());
+			}
 		}
 		if(diagram.getTemplate().getMainPipes() != null) {
 			for(MainPipe mainPipe : diagram.getTemplate().getMainPipes()) {
-				if(mainPipe.getEquipmentTypes() != null) //TODO 过滤已经初始化加载数据的干管
+				if(mainPipe.getEquipmentTypes() != null) { //TODO 过滤已经初始化加载数据的干管
 					equipTypes.addAll(mainPipe.getEquipmentTypes());
+				}
 			}
 		}
 
 		//TODO 打包设备查询性能优化
-		if(equipTypes.size() > 0)
+		if(equipTypes.size() > 0) {
 			optionalObjs = dataStrategy.loadObjectsByType(new ArrayList<>(equipTypes), diagram.getProjectId(), diagram.getSystemId(), diagram.getGroupCode());
-		else
+		} else {
 			optionalObjs = new ArrayList<>();
+		}
 		//记录备选对象id列表
 		List<String> objIds = optionalObjs.stream().map(obj -> obj.get("id").asText()).collect(Collectors.toList());
 
 		//去掉已经使用的数据项
 		HashMap<String, Object> equipMap = builder.getEquipMap();
-		if(equipMap.size() > 0)
+		if(equipMap.size() > 0) {
 			optionalObjs = optionalObjs.stream().filter(obj -> !equipMap.containsKey(obj.get("id").asText())).collect(Collectors.toList());
+		}
 
 		builder.buildEquipNodeAndContainer(containers, optionalObjs);
 		List<String[]> relTypes = builder.getRefRelTypes().stream().map(type -> type.split("/")).collect(Collectors.toList());
 
 		//TODO 关系查询,需要区分打包设备
-		if(objIds.size() > 0 && relTypes.size() > 0)
+		if(objIds.size() > 0 && relTypes.size() > 0) {
 			optionalRels = dataStrategy.loadRelationsByType(relTypes, objIds, diagram.getProjectId(), diagram.getGroupCode());
-		else
+		} else {
 			optionalRels = new ArrayList<>();
+		}
+
+		//去掉已经使用的关系项
+		HashMap<String, Line> lineMap = builder.getLineMap();
+		if(lineMap.size() > 0) {
+			optionalRels = optionalRels.stream().filter(obj -> !lineMap.containsKey(obj.get("id").asText())).collect(Collectors.toList());
+		}
 
 		builder.buildLines(optionalRels);
 	}

+ 10 - 4
adm-business/adm-diagram/src/main/java/com/persagy/adm/diagram/core/line/Block.java

@@ -13,13 +13,19 @@ public class Block {
 
 	private XY size;
 
-	private Polygon polygon;
+	private Polygon rect;
 
 	public Block(DiagramNode node) {
 		this.node = node;
 		this.location = node.locationToRoot();
 		this.size = node.getSize();
-		this.polygon = GeomUtil.getPolygon(location, size);
+		this.rect = GeomUtil.getRect(location, size);
+	}
+
+	public Block(XY location, XY size) {
+		this.location = location;
+		this.size = size;
+		this.rect = GeomUtil.getRect(location, size);
 	}
 
 	public XY getLocation() {
@@ -30,8 +36,8 @@ public class Block {
 		return size;
 	}
 
-	public Polygon getPolygon() {
-		return polygon;
+	public Polygon getRect() {
+		return rect;
 	}
 
 }

+ 43 - 1
adm-business/adm-diagram/src/main/java/com/persagy/adm/diagram/core/line/LineEnd.java

@@ -10,7 +10,7 @@ public class LineEnd {
 	/**
 	 * 与真正连接点的偏移量
 	 */
-	public static int offset = 5;
+	public static int offset = 8;
 
 	/**
 	 * 模式1,点(锚点)
@@ -38,4 +38,46 @@ public class LineEnd {
 		this.line = line;
 	}
 
+	public boolean isLineHorizontal(){
+		return isHorizontal(line);
+	}
+
+	public int getLineTrackPos(){
+		return isHorizontal(line) ? line[0].y : line[0].x;
+	}
+
+	public int getLineFromPos(){
+		return isHorizontal(line) ? line[0].x : line[0].y;
+	}
+
+	public int getLineToPos(){
+		return isHorizontal(line) ? line[1].x : line[1].y;
+	}
+
+	public int[] getLineScope(){
+		return isHorizontal(line) ? new int[]{line[0].x, line[1].x} : new int[]{line[0].y, line[1].y};
+	}
+
+	public static void orderLine(XY[] line) {
+		boolean needOrder = false;
+		if(isHorizontal(line)) {
+			if(line[0].x > line[1].x) {
+				needOrder = true;
+			}
+		} else {
+			if(line[0].y > line[1].y) {
+				needOrder = true;
+			}
+		}
+		if(needOrder) {
+			XY tmp = line[0];
+			line[0] = line[1];
+			line[1] = tmp;
+		}
+	}
+
+	public static boolean isHorizontal(XY[] line){
+		return line[0].y == line[1].y;
+	}
+
 }

+ 31 - 11
adm-business/adm-diagram/src/main/java/com/persagy/adm/diagram/core/line/LineLayoutManager.java

@@ -7,6 +7,8 @@ import com.persagy.adm.diagram.core.model.EquipmentNode;
 import com.persagy.adm.diagram.core.model.Line;
 import com.persagy.adm.diagram.core.model.base.XY;
 import com.persagy.adm.diagram.core.model.template.MainPipe;
+import com.persagy.adm.diagram.core.util.GeomUtil;
+import org.locationtech.jts.geom.Point;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -32,7 +34,7 @@ public class LineLayoutManager {
 		}
 	}
 
-	public void layout(Line line){
+	public List<XY> layout(Line line){
 		ConnectPoint p1 = line.getFrom();
 		ConnectPoint p2 = line.getTo();
 
@@ -49,7 +51,8 @@ public class LineLayoutManager {
 		}
 
 		LineEnd[] arr = buildLineEnd(p1, p2);
-
+		PathBuilder pb = new PathBuilder(line, arr[0], arr[1], exchange);
+		return pb.calcPath(blocks);
 	}
 
 	private LineEnd[] buildLineEnd(ConnectPoint p1, ConnectPoint p2) {
@@ -62,6 +65,7 @@ public class LineLayoutManager {
 			arr[1] = new LineEnd();
 			arr[1].setPoint(anchorEmerge(p2));
 		} else if (p2.getHostObj() instanceof MainPipe) {
+			Point point1 = GeomUtil.getPoint(p1.getLocation());
 			MainPipe mp = (MainPipe) p2.getHostObj();
 			XY[] nearLine = null;
 			double min = Double.MAX_VALUE;
@@ -69,8 +73,23 @@ public class LineLayoutManager {
 				XY pre = null;
 				for(XY xy : list) {
 					if(pre != null) {
-						XY[] line = new XY[]{pre, xy};
-						double distance = distance(line, p1.getLocation());
+						XY[] line = new XY[]{pre.copy(), xy.copy()}; //复制坐标
+						LineEnd.orderLine(line); //起点终点排序
+
+						//线需要留头
+						if(LineEnd.isHorizontal(line)){
+							if(line[1].x - line[0].x > LineEnd.offset * 2){
+								line[0].x += LineEnd.offset;
+								line[1].x -= LineEnd.offset;
+							}
+						} else {
+							if(line[1].y - line[0].y > LineEnd.offset * 2){
+								line[0].y += LineEnd.offset;
+								line[1].y -= LineEnd.offset;
+							}
+						}
+
+						double distance = point1.distance(GeomUtil.getLine(line)); //计算点和线段的最短距离
 						if(distance < min){
 							min = distance;
 							nearLine = line;
@@ -87,13 +106,6 @@ public class LineLayoutManager {
 	}
 
 	/**
-	 * 就算点和线段的最短距离
-	 */
-	private double distance(XY[] line, XY xy){
-		return 0; //TODO
-	}
-
-	/**
 	 * 锚点出线
 	 */
 	private XY anchorEmerge(ConnectPoint point){
@@ -118,4 +130,12 @@ public class LineLayoutManager {
 		return xy;
 	}
 
+	public List<Block> getBlocks() {
+		return blocks;
+	}
+
+	public DiagramBuilder getDiagramBuilder() {
+		return diagramBuilder;
+	}
+
 }

+ 565 - 0
adm-business/adm-diagram/src/main/java/com/persagy/adm/diagram/core/line/PathBuilder.java

@@ -1,7 +1,572 @@
 package com.persagy.adm.diagram.core.line;
 
+import cn.hutool.core.util.ArrayUtil;
+import com.persagy.adm.diagram.core.model.Line;
+import com.persagy.adm.diagram.core.model.base.XY;
+
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * 连线路径计算
+ */
 public class PathBuilder {
 
+	private static final int D_RIGHT = 1;
+
+	private static final int D_DOWN = 2;
+
+	private static final int D_LEFT = 3;
+
+	private static final int D_UP = 4;
+
+	private static int edge = 10;
+
+	private Line line;
+
+	private LineEnd end1;
+
+	private LineEnd end2;
+
+	private boolean exchange;
+
+	private List<XY> points = new ArrayList<>();
+
+	private XY currentPoint;
+
+	private int fromDirection;
+
+	private Random random = new Random();
+
+	public PathBuilder(Line line, LineEnd end1, LineEnd end2, boolean exchange) {
+		this.line = line;
+		this.end1 = end1;
+		this.end2 = end2;
+		this.exchange = exchange;
+
+		setCurrentPoint(end1.getPoint());
+		String fromAnchor = (!exchange ? line.getFrom() : line.getTo()).getAnchorCode();
+		switch (fromAnchor.charAt(0)) {
+			case 'T':
+				fromDirection = D_DOWN;
+				break;
+			case 'B':
+				fromDirection = D_UP;
+				break;
+			case 'L':
+				fromDirection = D_RIGHT;
+				break;
+			case 'R':
+				fromDirection = D_LEFT;
+				break;
+		}
+	}
+
+	private void setCurrentPoint(XY point){
+		currentPoint = point;
+		points.add(currentPoint);
+	}
+
+	public List<XY> calcPath(List<Block> blocks) {
+		findPath(blocks);
+
+		if(exchange)
+			Collections.reverse(points);
+
+		if(line.getFrom().getLocation() != null) {
+			points.add(0, line.getFrom().getLocation());
+		}
+		if(line.getTo().getLocation() != null) {
+			points.add(line.getTo().getLocation());
+		}
+
+		return points;
+	}
+
+	private void findPath(List<Block> blocks) {
+		Block rectArea = buildRectArea();
+		List<Block> currentBlocks = blocks.stream().filter(block -> block.getRect().intersects(rectArea.getRect())).collect(Collectors.toList());
+
+		int[] directions = findDirection();
+
+		for(int direction : directions) {
+			if(direction == fromDirection)
+				continue;
+
+			boolean vDirection = isVerticalDirection(direction);
+			int trackPos = vDirection ? currentPoint.x : currentPoint.y;
+			int pos = vDirection ? currentPoint.y : currentPoint.x;
+			boolean reverse = direction == D_LEFT || direction == D_UP;
+
+			List<BlockLine> blockLines = buildBlockLines(currentBlocks, direction);
+
+			int stopPos = -1;
+			for(BlockLine bl : blockLines) {
+				boolean valid = !reverse ? bl.base > pos : bl.base < pos;
+				if(valid && bl.begin <= trackPos && bl.end >= trackPos) {
+					stopPos = bl.base;
+					break;
+				}
+			}
+
+			if(stopPos != -1) {
+				int move = stopPos - pos;
+				if(Math.abs(move) <= edge) //当前方向被阻挡
+					continue;
+			}
+
+			List<int[]> nonBlockRoads = getNonBlockRoads(blockLines, rectArea, vDirection);
+			int[] nbr = findNonBlockRoad(nonBlockRoads, trackPos);
+
+			if(nbr != null) {
+				//检查终点是否在同一个无阻挡通道中
+				if (checkEnd(nbr, vDirection)) {
+					connectEnd(direction, nbr, vDirection);
+					return;
+				}
+			}
+
+			//获取垂直参考方向
+			int crossDirection = 0;
+			for (int i = 0; i < directions.length; i++) {
+				if(isVerticalDirection(directions[i]) != vDirection) {
+					crossDirection = directions[i];
+					break;
+				}
+			}
+			boolean crossReverse = crossDirection == D_LEFT || crossDirection == D_UP;
+			List<BlockLine> crossBlockLines = buildBlockLines(currentBlocks, crossDirection);
+			List<int[]> crossNonBlockRoads = getNonBlockRoads(crossBlockLines, rectArea, !vDirection);
+
+			//位于垂直参考方向上的无阻挡通道中
+			int[] crossNbr = findNonBlockRoad(crossNonBlockRoads, pos);
+			if(crossNbr != null) {
+				if (checkEnd(crossNbr, !vDirection)) {
+					connectEnd(crossDirection, crossNbr, !vDirection);
+					return;
+				}
+			}
+
+			//折线直达
+			if(nbr != null) {
+				for(int[] crossRoad : crossNonBlockRoads) {
+					boolean valid = !reverse ? crossRoad[0] > pos : crossRoad[1] < pos;
+					if(valid && checkEnd(crossRoad, !vDirection)){
+						connectEnd(direction, crossRoad, !vDirection);
+						return;
+					}
+				}
+			}
+			if(crossNbr != null) {
+				for(int[] road : nonBlockRoads) {
+					boolean valid = !crossReverse ? road[0] > pos : road[1] < pos;
+					if(valid && checkEnd(road, vDirection)){
+						connectEnd(crossDirection, road, vDirection);
+						return;
+					}
+				}
+			}
+
+			//移动到垂直参考方向上的无阻挡通道位置
+			int targetPos = -1;
+			if(!reverse) {
+				for (int i = 0; i < crossNonBlockRoads.size(); i++) {
+					int[] crossRoad = crossNonBlockRoads.get(i);
+					if(crossRoad[0] > pos){
+						targetPos = crossRoad[0] + (crossRoad[1] - crossRoad[0]) / 2; //TODO 调整&避免重叠
+						break;
+					}
+				}
+			} else {
+				for (int i = crossNonBlockRoads.size() - 1; i >= 0; i--) {
+					int[] crossRoad = crossNonBlockRoads.get(i);
+					if(crossRoad[1] < pos){
+						targetPos = crossRoad[0] + (crossRoad[1] - crossRoad[0]) / 2; //TODO 调整&避免重叠
+						break;
+					}
+				}
+			}
+			//垂直参考方向方向上没有通道
+			if(targetPos == -1) {
+				if(stopPos != -1) {	// 移动到stopPos
+					if(stopPos > pos) {
+						targetPos = stopPos - edge;
+					} else {
+						targetPos = stopPos + edge;
+					}
+				} else { //移动到垂直参考方向上blockLine变化的位置
+					for(BlockLine crossBl : crossBlockLines) {
+						boolean valid = !crossReverse ? crossBl.base > trackPos : crossBl.base < targetPos;
+						if(valid && crossBl.begin <= pos && crossBl.end >= pos) {
+							if(!reverse) {
+								targetPos = crossBl.end;
+							} else {
+								targetPos = crossBl.begin;
+							}
+							break;
+						}
+					}
+					if(targetPos == -1)//计算失败,尝试下一方向
+						continue;
+				}
+			}
+			setCurrentPoint(vDirection ? new XY(trackPos, targetPos) : new XY(targetPos, trackPos));
+			fromDirection = direction > 2 ? direction - 2 : direction + 2; //取反向
+			findPath(blocks); //递归调用路径计算
+		}
+	}
+
+	private Block buildRectArea() {
+		int[] xs;
+		int[] ys;
+		if(end2.getPoint() != null) {
+			xs = new int[] {currentPoint.x, end2.getPoint().x};
+			ys = new int[] {currentPoint.y, end2.getPoint().y};
+		} else {
+			xs = new int[] {currentPoint.x, end2.getLine()[0].x, end2.getLine()[1].x};
+			ys = new int[] {currentPoint.y, end2.getLine()[0].y, end2.getLine()[1].y};
+		}
+
+		XY location = new XY(ArrayUtil.min(xs), ArrayUtil.min(ys));
+		location.x -= edge;
+		location.y -= edge;
+		XY size = new XY(ArrayUtil.max(xs) - location.x, ArrayUtil.max(ys) - location.y);
+		size.x += edge;
+		size.y += edge;
+
+		return new Block(location, size);
+	}
+
+	private int[] findDirection(){
+		int[] rtn = new int[4];
+
+		if(end2.getPoint() != null) {
+			XY endPoint = end2.getPoint();
+			if (endPoint.x == currentPoint.x) {
+				setDirection(rtn, null, endPoint.y > currentPoint.y);
+			} else if (endPoint.y == currentPoint.y) {
+				setDirection(rtn, endPoint.x > currentPoint.x, null);
+			} else {
+				setDirection(rtn, endPoint.x > currentPoint.x, endPoint.y > currentPoint.y);
+			}
+		} else {
+			int trackPos = end2.getLineTrackPos();
+			int[] scope = end2.getLineScope();
+			if(end2.isLineHorizontal()) {
+				boolean xMatch = scope[0] <= currentPoint.x && scope[1] >= currentPoint.x;
+				if (xMatch) {
+					setDirection(rtn, null, trackPos > currentPoint.y);
+				} else if (trackPos == currentPoint.y) {
+					//需要垂直交汇
+					rtn[0] = D_DOWN;
+					rtn[1] = D_UP;
+
+					boolean right = scope[0] > currentPoint.x;
+					rtn[2] = right ? D_RIGHT : D_LEFT;
+					rtn[3] = right ? D_LEFT : D_RIGHT;
+				}
+			} else {
+				boolean yMatch = scope[0] <= currentPoint.y && scope[0] >= currentPoint.y;
+				if (yMatch) {
+					setDirection(rtn, trackPos > currentPoint.x, null);
+				} else if (trackPos == currentPoint.x) {
+					//需要垂直交汇
+					rtn[0] = D_RIGHT;
+					rtn[1] = D_LEFT;
+
+					boolean down = scope[0] > currentPoint.y;
+					rtn[2] = down ? D_DOWN : D_UP;
+					rtn[3] = down ? D_UP : D_DOWN;
+				}
+			}
+			if(rtn[0] == 0) {
+				XY p0 = end2.getLine()[0];
+				setDirection(rtn, p0.x > currentPoint.x, p0.y > currentPoint.y);
+			}
+		}
+
+		return rtn;
+	}
+
+	private void setDirection(int[] directions, Boolean right, Boolean down){
+		if (right != null && down != null) {
+			directions[0] = right ? D_RIGHT : D_LEFT;
+			directions[1] = down ? D_DOWN : D_UP;
+			directions[2] = right ? D_LEFT : D_RIGHT;
+			directions[3] = down ? D_UP : D_DOWN;
+		} else if(right != null) {
+			directions[0] = right ? D_RIGHT : D_LEFT;
+			directions[1] = D_DOWN;
+			directions[2] = D_UP;
+			directions[3] = right ? D_LEFT : D_RIGHT;
+		} else if(down != null) {
+			directions[0] = down ? D_DOWN : D_UP;
+			directions[1] = D_RIGHT;
+			directions[2] = D_LEFT;
+			directions[3] = down ? D_UP : D_DOWN;
+		}
+	}
+
+	private List<BlockLine> buildBlockLines(List<Block> currentBlocks, int direction) {
+		List<BlockLine> blockLines = new ArrayList<>();
+		for(Block block : currentBlocks) {
+			addBlockLine(block, direction, blockLines);
+		}
+
+		//合并相连的blockLine
+		Iterator<BlockLine> iter = blockLines.iterator();
+		BlockLine pre = null;
+		while (iter.hasNext()) {
+			BlockLine bl = iter.next();
+			if(pre != null) {
+				if(pre.end == bl.begin && pre.base == bl.base) {
+					pre.end = bl.end;
+					iter.remove();
+					continue;
+				}
+			}
+			pre = bl;
+		}
+
+		return blockLines;
+	}
+
+	private void addBlockLine(Block block, int direction, List<BlockLine> blockLines) {
+		BlockLine toHandle = new BlockLine();
+		XY loc = block.getLocation();
+		XY size = block.getSize();
+		switch (direction) {
+			case D_UP:
+				if(currentPoint.y <= loc.y)
+					return;
+				toHandle.base = loc.y + size.y;
+				toHandle.begin = loc.x;
+				toHandle.end = loc.x + size.x;
+				break;
+			case D_DOWN:
+				if(currentPoint.y >= loc.y + size.y)
+					return;
+				toHandle.base = loc.y;
+				toHandle.begin = loc.x;
+				toHandle.end = loc.x + size.x;
+				break;
+			case D_LEFT:
+				if(currentPoint.x <= loc.x)
+					return;
+				toHandle.base = loc.x + size.x;
+				toHandle.begin = loc.y;
+				toHandle.end = loc.y + size.y;
+				break;
+			case D_RIGHT:
+				if(currentPoint.x >= loc.x + size.x)
+					return;
+				toHandle.base = loc.x;
+				toHandle.begin = loc.y;
+				toHandle.end = loc.y + size.y;
+				break;
+		}
+
+		//使用toHandle的端点对blockLines中线段进行切分
+		int pos = toHandle.begin;
+		for (int i = 0; i < blockLines.size(); i++) {
+			BlockLine item = blockLines.get(i);
+			if (pos > item.begin && pos < item.end) {
+				blockLines.add(i + 1, new BlockLine(pos, item.end, item.base));
+				item.end = pos;
+
+				if(pos == toHandle.begin)
+					pos = toHandle.end;
+				else
+					break;
+			}
+		}
+
+		//使用blockLines中线段端点对toHandle进行切分
+		LinkedList<BlockLine> nls = new LinkedList<>();
+		boolean finish = false;
+		for(BlockLine item : blockLines) {
+			int[] posArr = new int[] {item.begin, item.end};
+			for(int p : posArr) {
+				if(p > toHandle.begin && p < toHandle.end) {
+					nls.add(new BlockLine(toHandle.begin, p, toHandle.base));
+					toHandle.begin = p;
+				} else if(p > toHandle.end) {
+					finish = true;
+					break;
+				}
+			}
+
+			if(finish)
+				break;;
+		}
+		nls.add(toHandle);
+
+		//合并blockLines并设置base
+		for (int i = 0; i < blockLines.size(); i++) {
+			if(nls.size() == 0)
+				break;
+
+			BlockLine item = blockLines.get(i);
+			BlockLine nl = nls.getFirst();
+			if(nl.begin < item.begin){ //插入
+				blockLines.add(i, nl);
+
+				nls.removeFirst();
+			} else if(nl.begin == item.begin) { //重合
+				item.base = (direction == D_UP || direction == D_LEFT) ? Math.max(nl.base, item.base) : Math.min(nl.base, item.base);
+
+				nls.removeFirst();
+			}
+		}
+		if(nls.size() > 0) {
+			blockLines.addAll(nls);
+		}
+	}
+
+	private List<int[]> getNonBlockRoads(List<BlockLine> blockLines, Block rectArea, boolean vDirection){
+		int from, to;
+		if(vDirection) {
+			from = rectArea.getLocation().x;
+			to = rectArea.getLocation().x + rectArea.getSize().x;
+		} else {
+			from = rectArea.getLocation().y;
+			to = rectArea.getLocation().y + rectArea.getSize().y;
+		}
+
+		List<int[]> list = new ArrayList<>();
+		int tmp = from;
+		for(BlockLine bl : blockLines) {
+			if(tmp < bl.begin)
+				list.add(new int[]{tmp, bl.begin});
+			tmp = bl.end;
+		}
+		if(tmp < to)
+			list.add(new int[]{tmp, to});
+
+		return list;
+	}
+
+	private int[] findNonBlockRoad(List<int[]> nonBlockRoads, int pos) {
+		for(int[] road : nonBlockRoads) {
+			if(road[0] <= pos && road[1] >= pos) {
+				return road;
+			}
+		}
+		return null;
+	}
+
+	/**
+	 * 检查终点是否在某个无阻挡通道上
+	 */
+	private boolean checkEnd(int[] nonBlockRoad, boolean vDirection){
+		if(end2.getPoint() != null) {
+			int pos = vDirection ? end2.getPoint().x : end2.getPoint().y;
+			return nonBlockRoad[0] <= pos && nonBlockRoad[1] >= pos;
+		} else {
+			boolean hLine = end2.isLineHorizontal();
+			if(hLine == vDirection) { //垂直
+				int[] scope = end2.getLineScope();
+				boolean notJoin = scope[0] > nonBlockRoad[1] || nonBlockRoad[0] > scope[1];
+				return !notJoin;
+			} else { //平行
+				int trackPos = end2.getLineTrackPos();
+				return nonBlockRoad[0] <= trackPos && nonBlockRoad[1] >= trackPos;
+			}
+		}
+	}
+
+	private void connectEnd(int direction, int[] endNbr, boolean vEndNbr) {
+		boolean vDirection = isVerticalDirection(direction);
+		if(end2.getPoint() != null) {
+			XY endPoint = end2.getPoint();
+			if(currentPoint.x != endPoint.x && currentPoint.y != endPoint.y) {
+				moveTo(vDirection ? endPoint.y : endPoint.x, vDirection);
+			}
+			points.add(endPoint);
+		} else {
+			//XY[] line = end2.getLine();
+			boolean hLine = end2.isLineHorizontal();
+			int lineTrackPos = end2.getLineTrackPos();
+			int[] lineScope = end2.getLineScope();
+
+			if(vDirection == vEndNbr) { //当前方向与终点通道方向相同
+				if(vEndNbr == hLine) { //垂直相交
+					moveTo(lineTrackPos, hLine); //线横向时做纵向移动
+				} else {
+					//TODO 平行连接
+				}
+			} else { //当前方向与终点通道方向垂直,折线连接
+				if(vEndNbr == hLine) { //endLine与通道垂直
+					int[] targetScope = new int[] {Math.max(lineScope[0], endNbr[0]), Math.min(lineScope[1], endNbr[1])};
+					int safeOffset = 5;//临时避免和节点边线重合
+					int target0 = targetScope[0] + safeOffset + random.nextInt(targetScope[1] - targetScope[0] - safeOffset * 2); //TODO 调整&避免重叠
+					moveTo(target0, vDirection);
+					moveTo(lineTrackPos, hLine);
+				} else { //endLine与通道平行
+					//TODO 调整&避免重叠
+					int target0;
+					if(endNbr[1] - lineTrackPos > lineTrackPos - endNbr[0]) {
+						target0 = lineTrackPos + LineEnd.offset;
+					} else {
+						target0 = lineTrackPos - LineEnd.offset;
+					}
+					if(target0 > endNbr[1]){
+						target0 = endNbr[1];
+					} else if(target0 < endNbr[0]) {
+						target0 = endNbr[0];
+					}
+					XY point0 = moveTo(target0, vDirection);
+
+					int target1 = hLine ? point0.x : point0.y;
+					int offset = random.nextInt((lineScope[1] - lineScope[0]) / 2);
+					if(Math.abs(lineScope[0] - target1) < Math.abs((lineScope[1] - target1))){
+						target1 = lineScope[0] + offset;
+					} else {
+						target1 = lineScope[1] - offset;
+					}
+					moveTo(target1, !vDirection);
+
+					moveTo(lineTrackPos, vDirection);
+				}
+			}
+		}
+	}
+
+	private XY moveTo(int targetPos, boolean vDirection) {
+		XY point = new XY(currentPoint);
+		if(vDirection)
+			point.y = targetPos;
+		else
+			point.x = targetPos;
+
+		//TODO 连线检查,处理重叠等
+
+		setCurrentPoint(point);
+		return point;
+	}
+
+	private boolean isVerticalDirection(int direction) {
+		return direction == D_DOWN || direction == D_UP;
+	}
+
+	class BlockLine {
+
+		BlockLine(){
+
+		}
+
+		BlockLine(int begin, int end, int base) {
+			this.begin = begin;
+			this.end = end;
+			this.base = base;
+		}
+
+		int begin;
+
+		int end;
+
+		int base;
 
+	}
 
 }

+ 1 - 0
adm-business/adm-diagram/src/main/java/com/persagy/adm/diagram/core/model/Diagram.java

@@ -87,6 +87,7 @@ public class Diagram {
 	private List<Line> lines = new ArrayList();
 
 	//运行时
+	@Expose(serialize = false)
 	private DiagramTemplate template;
 
 

+ 1 - 10
adm-business/adm-diagram/src/main/java/com/persagy/adm/diagram/core/model/Line.java

@@ -1,11 +1,9 @@
 package com.persagy.adm.diagram.core.model;
 
 import com.google.gson.annotations.Expose;
-import com.persagy.adm.diagram.core.DiagramBuilder;
 import com.persagy.adm.diagram.core.line.LineLayoutManager;
 import com.persagy.adm.diagram.core.model.base.XY;
 
-import java.util.ArrayList;
 import java.util.List;
 
 /**
@@ -55,14 +53,7 @@ public class Line extends AbstractLine {
 	@Override
 	public void layout(Object layoutContext) {
 		LineLayoutManager layoutManager = (LineLayoutManager) layoutContext;
-		layoutManager.layout(this);
-
-		//test code TODO
-		locationPath = new ArrayList<>();
-		if(from.getLocation() != null && to.getLocation() != null) {
-			locationPath.add(from.getLocation());
-			locationPath.add(to.getLocation());
-		}
+		locationPath = layoutManager.layout(this);
 	}
 
 	public int getDirection() {

+ 4 - 0
adm-business/adm-diagram/src/main/java/com/persagy/adm/diagram/core/model/base/XY.java

@@ -24,4 +24,8 @@ public class XY {
 		this.y = xy.y;
 	}
 
+	public XY copy() {
+		return new XY(x, y);
+	}
+
 }

+ 10 - 182
adm-business/adm-diagram/src/main/java/com/persagy/adm/diagram/core/util/GeomUtil.java

@@ -1,23 +1,16 @@
 package com.persagy.adm.diagram.core.util;
 
-import cn.hutool.core.collection.CollUtil;
-import cn.hutool.core.map.CaseInsensitiveMap;
-import com.fasterxml.jackson.databind.JsonNode;
-import com.fasterxml.jackson.databind.node.ArrayNode;
 import com.persagy.adm.diagram.core.model.base.XY;
 import org.locationtech.jts.geom.*;
-import org.locationtech.jts.io.ParseException;
-import org.locationtech.jts.io.WKTReader;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
 
+/**
+ * 几何计算工具类
+ */
 public class GeomUtil {
 
 	private static GeometryFactory geometryFactory = new GeometryFactory();
 
-	public static Polygon getPolygon(XY location, XY size) {
+	public static Polygon getRect(XY location, XY size) {
 		Coordinate[] cs = new Coordinate[5];
 		cs[0] = new Coordinate(location.x, location.y);
 		cs[1] = new Coordinate(location.x + size.x, location.y);
@@ -35,180 +28,15 @@ public class GeomUtil {
 		return geometryFactory.createLineString(cs);
 	}
 
-	public Point getPoint(XY xy){
+	public static Point getPoint(XY xy){
 		return geometryFactory.createPoint(new Coordinate(xy.x, xy.y));
 	}
 
-	public static Polygon[] getPolygon(ArrayNode arrayNode){
-		Polygon[] ps = new Polygon[arrayNode.size()];
-		for (int i = 0; i < ps.length; i++) {
-			ArrayNode arr = (ArrayNode) arrayNode.get(i);
-			ArrayNode shellArrNode = (ArrayNode) arr.get(0);
-			LinearRing shell = geometryFactory.createLinearRing(buildCoords(shellArrNode));
-			LinearRing[] holes = null;
-			if(arr.size() > 1) {
-				holes = new LinearRing[arr.size() - 1];
-				for (int n = 1; n < arr.size(); n++) {
-					holes[n - 1] = geometryFactory.createLinearRing(buildCoords((ArrayNode) arr.get(n)));
-				}
-			}
-			ps[i] = geometryFactory.createPolygon(shell, holes);
-		}
-		return ps;
-	}
-
-	private static Coordinate[] buildCoords(ArrayNode arrayNode){
-		Coordinate[] coords = new Coordinate[arrayNode.size()];
-		for (int i = 0; i < arrayNode.size(); i++) {
-			JsonNode pointNode = arrayNode.get(i);
-			coords[i] = new Coordinate(pointNode.get("x").asDouble(), pointNode.get("y").asDouble());
-		}
-		return coords;
-	}
-
-	public static boolean checkPolygon(ArrayList<ArrayList<ArrayList<CaseInsensitiveMap>>> outline){
-		if(CollUtil.isEmpty(outline))
-			return false;
-
-		try {
-			Polygon[] ps = new Polygon[outline.size()];
-			for (int i = 0; i < ps.length; i++) {
-				List space = (List) outline.get(i);
-				if(CollUtil.isEmpty(space))
-					return false;
-
-				List<Map> shellList = (List) space.get(0);
-				LinearRing shell = geometryFactory.createLinearRing(buildCoords(shellList));
-				LinearRing[] holes = null;
-				if(space.size() > 1) {
-					holes = new LinearRing[space.size() - 1];
-					for (int n = 1; n < space.size(); n++) {
-						holes[n - 1] = geometryFactory.createLinearRing(buildCoords((List<Map>) space.get(n)));
-					}
-				}
-				ps[i] = geometryFactory.createPolygon(shell, holes);
-			}
-		} catch (Exception e) {
-			return false;
-		}
-		return true;
-	}
-
-	private static Coordinate[] buildCoords(List<Map> list){
-		Coordinate[] coords = new Coordinate[list.size()];
-		for (int i = 0; i < list.size(); i++) {
-			Map point = list.get(i);
-			coords[i] = new Coordinate((Double) point.get("x"), (Double)point.get("y"));
-		}
-		return coords;
-	}
-
-	public static boolean checkPolygon(ArrayNode outline){
-		if(outline == null || outline.size() == 0)
-			return false;
-
-		try {
-			Polygon[] ps = new Polygon[outline.size()];
-			for (int i = 0; i < ps.length; i++) {
-				ArrayNode space = (ArrayNode) outline.get(i);
-				if(space == null || space.size() == 0)
-					return false;
-
-				ArrayNode shellList = (ArrayNode) space.get(0);
-				LinearRing shell = geometryFactory.createLinearRing(buildCoords(shellList));
-				LinearRing[] holes = null;
-				if(space.size() > 1) {
-					holes = new LinearRing[space.size() - 1];
-					for (int n = 1; n < space.size(); n++) {
-						holes[n - 1] = geometryFactory.createLinearRing(buildCoords((ArrayNode) space.get(n)));
-					}
-				}
-				ps[i] = geometryFactory.createPolygon(shell, holes);
-			}
-		} catch (Exception e) {
-			return false;
-		}
-		return true;
-	}
-
-
-	/**
-	 *  两个几何对象是否是重叠的
-	 * @return
-	 * @throws ParseException
-	 */
-	public boolean equalsGeo() throws ParseException {
-		WKTReader reader = new WKTReader( geometryFactory );
-		LineString geometry1 = (LineString) reader.read("LINESTRING(0 0, 2 0, 5 0)");
-		LineString geometry2 = (LineString) reader.read("LINESTRING(5 0, 0 0)");
-		return geometry1.equals(geometry2);//true
-	}
-
-	/**
-	 * 几何对象没有交点(相邻)
-	 * @return
-	 * @throws ParseException
-	 */
-	public boolean disjointGeo() throws ParseException{
-		WKTReader reader = new WKTReader( geometryFactory );
-		LineString geometry1 = (LineString) reader.read("LINESTRING(0 0, 2 0, 5 0)");
-		LineString geometry2 = (LineString) reader.read("LINESTRING(0 1, 0 2)");
-		return geometry1.disjoint(geometry2);
-	}
-
-	/**
-	 * 至少一个公共点(相交)
-	 * @return
-	 * @throws ParseException
-	 */
-	public boolean intersectsGeo() throws ParseException{
-		WKTReader reader = new WKTReader( geometryFactory );
-		LineString geometry1 = (LineString) reader.read("LINESTRING(0 0, 2 0, 5 0)");
-		LineString geometry2 = (LineString) reader.read("LINESTRING(0 0, 0 2)");
-		Geometry interPoint = geometry1.intersection(geometry2);//相交点
-		System.out.println(interPoint.toText());//输出 POINT (0 0)
-		return geometry1.intersects(geometry2);
-	}
-
-	/**
-	 * 判断以x,y为坐标的点point(x,y)是否在geometry表示的Polygon中
-	 * @param x
-	 * @param y
-	 * @param geometry wkt格式
-	 * @return
-	 */
-	public boolean withinGeo(double x,double y,String geometry) throws ParseException {
-
-		Coordinate coord = new Coordinate(x,y);
-		Point point = geometryFactory.createPoint( coord );
-
-		WKTReader reader = new WKTReader( geometryFactory );
-		Polygon polygon = (Polygon) reader.read(geometry);
-		return point.within(polygon);
-	}
-
-	public LinearRing createLinearRing(double[]... xys){
-		Coordinate[] coords = new Coordinate[xys.length];
-		for(int i=0;i<xys.length;i++){
-			coords[i] = new Coordinate(xys[i][0], xys[i][1]);
-		}
-		return geometryFactory.createLinearRing(coords);
-	}
-
-	public Polygon createPolygon(double[]... xys){
-		Coordinate[] coords = new Coordinate[xys.length];
-		for(int i=0;i<xys.length;i++){
-			coords[i] = new Coordinate(xys[i][0], xys[i][1]);
-		}
-		return geometryFactory.createPolygon(coords);
-	}
-
-	public LineString createLine(double[]... xys){
-		Coordinate[] coords = new Coordinate[xys.length];
-		for(int i=0;i<xys.length;i++){
-			coords[i] = new Coordinate(xys[i][0], xys[i][1]);
-		}
-		return geometryFactory.createLineString(coords);
+	public static void main(String[] args) {
+		Point p1 = getPoint(new XY(1, 1));
+		LineString line = getLine(new XY[]{new XY(2, 0), new XY(2, 4)});
+		double d = p1.distance(line);
+		System.out.println(d);
 	}
 
 }

+ 4 - 0
adm-business/adm-diagram/src/main/java/com/persagy/adm/diagram/manage/DemoDiagramManager.java

@@ -121,6 +121,10 @@ public class DemoDiagramManager {
 		Diagram diagram = dataStrategy.getDiagram(diagramId);
 		diagram.setTemplateId(templateId);
 
+		//重设模板时,清空节点和连线
+		diagram.getNodes().clear();
+		diagram.getLines().clear();
+
 		dataStrategy.saveDiagram(diagram);
 		buildDiagram(diagram, true);