1 /*******************************************************************************
2 * Copyright (c) 2011 Association for Decentralized Information Management in
4 * All rights reserved. This program and the accompanying materials
5 * are made available under the terms of the Eclipse Public License v1.0
6 * which accompanies this distribution, and is available at
7 * http://www.eclipse.org/legal/epl-v10.html
10 * VTT Technical Research Centre of Finland - initial API and implementation
11 *******************************************************************************/
12 package org.simantics.diagram.connection;
14 import java.awt.geom.Line2D;
15 import java.awt.geom.Path2D;
16 import java.awt.geom.Point2D;
17 import java.awt.geom.Rectangle2D;
18 import java.io.PrintStream;
19 import java.io.Serializable;
20 import java.util.ArrayList;
21 import java.util.Collection;
22 import java.util.Collections;
23 import java.util.Comparator;
24 import java.util.HashMap;
25 import java.util.Iterator;
27 import java.util.stream.Collectors;
29 import org.simantics.diagram.connection.rendering.arrows.ILineEndStyle;
30 import org.simantics.diagram.connection.rendering.arrows.PlainLineEndStyle;
31 import org.simantics.diagram.connection.segments.Segment;
32 import org.simantics.diagram.connection.splitting.SplittedRouteGraph;
34 import gnu.trove.list.array.TDoubleArrayList;
35 import gnu.trove.map.hash.THashMap;
36 import gnu.trove.map.hash.TObjectIntHashMap;
37 import gnu.trove.set.hash.THashSet;
39 public class RouteGraph implements Serializable {
41 private static final long serialVersionUID = 2004022454972623908L;
43 public static final boolean RETURN_UNMODIFIABLE_COLLECTIONS = false;
44 public static final boolean CHECK_PARAMERS = true;
46 protected ArrayList<RouteLine> lines = new ArrayList<RouteLine>(4);
47 protected ArrayList<RouteTerminal> terminals = new ArrayList<RouteTerminal>(4);
48 protected ArrayList<RouteLine> transientLines = new ArrayList<RouteLine>(4);
50 protected boolean isSimpleConnection;
51 protected boolean needsUpdate = false;
53 public void updateTerminals() {
54 boolean changed = false;
55 for(RouteTerminal terminal : terminals)
56 changed |= terminal.updateDynamicPosition();
61 * Adds a route line to the graph.
62 * @param isHorizontal true, if the line is horizontal
63 * @param position y coordinate of the line if horizontal, x coordinate if vertical
64 * @return The new line.
66 public RouteLine addLine(boolean isHorizontal, double position) {
67 RouteLine line = new RouteLine(isHorizontal, position);
73 * Adds a route terminal to the graph.
74 * @param x The location of the terminal.
76 * @param minX The avoidance area of the terminal.
80 * @param allowedDirections Allowed directions.
81 * First four bits tell with which directions
82 * a connection can leave the terminal. The
83 * Fifth bit indicates whether direct lines
84 * can be drawn to the terminal. Directions are
91 * @param style Tells what kind of line end is drawn
93 * @param position a provider for a dynamic position for the terminal or
94 * <code>null</code> if terminal position is not dynamic
95 * @return The new terminal.
97 public RouteTerminal addTerminal(double x, double y,
98 double minX, double minY,
99 double maxX, double maxY,
100 int allowedDirections,
101 ILineEndStyle style, RouteTerminalPosition position) {
102 return addTerminal(x, y, minX, minY, maxX, maxY, allowedDirections, style, null, position);
105 public RouteTerminal addTerminal(double x, double y,
106 double minX, double minY,
107 double maxX, double maxY,
108 int allowedDirections,
109 ILineEndStyle style) {
110 return addTerminal(x, y, minX, minY, maxX, maxY, allowedDirections, style, null, null);
113 public RouteTerminal addTerminal(double x, double y,
114 double minX, double minY,
115 double maxX, double maxY,
116 int allowedDirections,
117 ILineEndStyle style, ILineEndStyle dynamicStyle, RouteTerminalPosition position) {
119 if(allowedDirections > 0x1f)
120 throw new IllegalArgumentException("Illegal allowedDirection flags.");
121 if(minX > x || x > maxX || minY > y || y > maxY)
122 throw new IllegalArgumentException("Illegal position attributes for a terminal, (" + x + ", " + y + ") is outside of (" + minX + ", " + minY + ")x(" + maxX + ", " + maxY + ").");
125 style = PlainLineEndStyle.INSTANCE;
126 RouteTerminal terminal = new RouteTerminal(x, y, minX, minY,
127 maxX, maxY, allowedDirections, false, style, position);
128 terminal.setDynamicStyle(dynamicStyle);
129 terminals.add(terminal);
133 public RouteTerminal addBigTerminal(
134 double minX, double minY,
135 double maxX, double maxY,
136 ILineEndStyle style) {
137 return addBigTerminal(minX, minY, maxX, maxY, style, null);
139 public RouteTerminal addBigTerminal(
140 double minX, double minY,
141 double maxX, double maxY,
142 ILineEndStyle style, ILineEndStyle dynamicStyle) {
144 style = PlainLineEndStyle.INSTANCE;
145 RouteTerminal terminal = new RouteTerminal(
146 0.5*(minX+maxX), 0.5*(minY+maxY),
149 0xf, true, style, null);
150 terminal.setDynamicStyle(dynamicStyle);
152 terminals.add(terminal);
157 * Adds a route terminal to the graph. The avoidance
158 * area is given as a rectangle.
159 * @see RouteGraph#addTerminal(double, double, double, double, double, double, int, ILineEndStyle)
161 public RouteTerminal addTerminal(double x, double y,
163 int allowedDirections,
164 ILineEndStyle style) {
165 return addTerminal(x, y,
166 bounds.getMinX(), bounds.getMinY(), bounds.getMaxX(), bounds.getMaxY(),
167 allowedDirections, style, null);
171 * Adds a copy of the given terminal to the graph.
173 public RouteTerminal addTerminal(RouteTerminal terminal) {
174 RouteTerminal newTerminal = addTerminal(terminal.x, terminal.y,
175 terminal.getMinX(), terminal.getMinY(),
176 terminal.getMaxX(), terminal.getMaxY(),
177 terminal.getAllowedDirections(), terminal.getStyle(), terminal.getDynamicStyle(), terminal.getDynamicPosition());
178 newTerminal.setData(terminal.getData());
183 * Adds a route terminal to the graph. A default line end style is used.
184 * @see RouteGraph#addTerminal(double, double, double, double, double, double, int, ILineEndStyle)
186 public RouteTerminal addTerminal(double x, double y,
187 double minX, double minY,
188 double maxX, double maxY,
189 int allowedDirections) {
190 return addTerminal(x, y, minX, minY, maxX, maxY, allowedDirections,
191 PlainLineEndStyle.INSTANCE, null);
197 public void link(RouteNode node1, RouteNode node2) {
198 if(node1 instanceof RouteLine) {
199 if(node2 instanceof RouteLine) {
200 link((RouteLine)node1, (RouteLine)node2);
203 link((RouteLine)node1, (RouteTerminal)node2);
207 if(node2 instanceof RouteLine) {
208 link((RouteTerminal)node1, (RouteLine)node2);
211 link((RouteTerminal)node1, (RouteTerminal)node2);
219 public void link(RouteNode ... nodes) {
220 for(int i=1;i<nodes.length;++i)
221 link(nodes[i-1], nodes[i]);
227 public void link(RouteLine node1, RouteLine node2) {
228 new RouteLink(node1, node2);
233 * Links a terminal and a line.
235 public void link(RouteTerminal node1, RouteLine node2) {
238 throw new NullPointerException();
239 if(node1.line != null)
240 throw new IllegalStateException("Terminal is already connected.");
247 * Links a line and a terminal.
249 public void link(RouteLine node1, RouteTerminal node2) {
252 throw new NullPointerException();
253 if(node2.line != null)
254 throw new IllegalStateException("Terminal is already connected.");
261 * Links two terminals.
263 public void link(RouteTerminal node1, RouteTerminal node2) {
266 throw new NullPointerException();
268 throw new NullPointerException();
270 isSimpleConnection = true;
274 protected void removeTransientRouteLines() {
275 for(RouteLine line : transientLines)
277 transientLines.clear();
280 protected void removeRouteTerminalsFromRouteLines() {
281 for(RouteLine line : lines) {
282 line.removeRouteTerminals();
287 * Rotates given terminal clockwise by given amount
288 * (also negative numbers are allowed).
290 public void rotate(RouteTerminal terminal, int amount) {
291 terminal.rotate(amount);
296 * Moves the given route line so that its position correspond
297 * to given x or y depending on the orientation of the line.
299 public void setLocation(RouteLine line, double x, double y) {
300 makePersistent(line);
301 line.setLocation(x, y);
306 * Moves the terminal to given location.
308 public void setLocation(RouteTerminal terminal, double x, double y) {
309 terminal.setLocation(x, y);
314 * Updates transient route lines, route link positions
315 * and sorts route points of each route line.
317 public void update() {
319 removeTransientRouteLines();
320 removeRouteTerminalsFromRouteLines();
324 for(RouteLine line : lines)
326 for(RouteTerminal terminal : terminals)
327 if(terminal.hasDirectConnection() && terminal.line != null)
328 terminal.line.hidden = true;
331 if(isSimpleConnection) {
332 RouteTerminal a = terminals.get(0);
333 RouteTerminal b = terminals.get(1);
334 if(a.hasDirectConnection() || b.hasDirectConnection())
336 caseId = SimpleConnectionUtility.simpleConnectionCase(a, b);
337 //System.out.println("caseId = " + caseId);
339 case SimpleConnectionUtility.DIRECT_HORIZONTAL_CONNECTION:
340 case SimpleConnectionUtility.DIRECT_VERTICAL_CONNECTION: {
341 boolean horiz = caseId==SimpleConnectionUtility.DIRECT_HORIZONTAL_CONNECTION;
342 RouteLine line = new RouteLine(horiz, horiz ? a.y : a.x);
346 transientLines.add(line);
354 case SimpleConnectionUtility.ONE_BEND_HORIZONTAL_VERTICAL: {
355 RouteLine line1 = new RouteLine(true, a.y);
356 RouteLine line2 = new RouteLine(false, b.x);
357 new RouteLink(line1, line2);
362 transientLines.add(line1);
363 transientLines.add(line2);
370 case SimpleConnectionUtility.ONE_BEND_VERTICAL_HORIZONTAL: {
371 RouteLine line1 = new RouteLine(false, a.x);
372 RouteLine line2 = new RouteLine(true, b.y);
373 new RouteLink(line1, line2);
378 transientLines.add(line1);
379 transientLines.add(line2);
386 case SimpleConnectionUtility.MORE_BENDS_BBS_DONT_INTERSECT:
387 case SimpleConnectionUtility.MORE_BENDS_BBS_INTERSECT: {
389 SimpleConnectionUtility.findRouteLine(terminals.get(0), terminals.get(1),
390 caseId == SimpleConnectionUtility.MORE_BENDS_BBS_INTERSECT )
392 terminals.get(0).line = line;
393 terminals.get(1).line = line;
394 transientLines.add(line);
395 routeFromTerminals(caseId==SimpleConnectionUtility.MORE_BENDS_BBS_INTERSECT);
399 //routeFromTerminals(caseId==SimpleConnectionUtility.MORE_BENDS_BBS_INTERSECT);
402 caseId = SimpleConnectionUtility.COMPLEX_CONNECTION;
403 routeFromTerminals(false);
406 // Find positions of route points
407 for(RouteLine line : lines)
408 line.setPointPositions();
409 for(RouteLine line : transientLines)
410 line.setPointPositions();
412 // Sort route points in route lines
413 for(RouteLine line : lines) {
416 for(RouteLine line : transientLines) {
421 public static class Interval {
422 public final double min;
423 public final double max;
424 public Interval(double min, double max) {
430 public class IntervalCache {
431 THashMap<RouteLine, Interval> cache =
432 new THashMap<RouteLine, Interval>();
433 public Interval get(RouteLine line) {
434 Interval result = cache.get(line);
438 result = create(line);
439 cache.put(line, result);
443 private Interval create(RouteLine line) {
444 double min = Double.POSITIVE_INFINITY;
445 double max = Double.NEGATIVE_INFINITY;
447 for(RoutePoint point : line.points) {
449 if(point instanceof RouteLink) {
450 RouteLink link = (RouteLink)point;
452 temp = link.b.position;
454 temp = link.a.position;
457 RouteTerminal terminal = (RouteTerminal)point;
458 if(line.isHorizontal)
468 for(RouteTerminal terminal : terminals) {
469 if(terminal.line == line) {
470 double temp = terminal.approximatePositionToLine();
478 return new Interval(min, max);
482 protected void routeFromTerminals(boolean boundingBoxesIntersect) {
483 IntervalCache cache = new IntervalCache();
484 for(RouteTerminal terminal : terminals)
485 if(terminal.line != null) {
486 if(!terminal.hasDirectConnection())
487 terminal.route(transientLines, cache, boundingBoxesIntersect);
492 * Returns a RouteLine near the given point (x,y) (within tolerance).
494 public RouteLine pickLine(double x, double y, double tolerance, int mask) {
497 if(isSimpleConnection && transientLines.isEmpty()) {
498 if(terminals.size() == 2) {
499 if((mask & PICK_TRANSIENT_LINES) == 0)
501 RouteTerminal a = terminals.get(0);
502 RouteTerminal b = terminals.get(1);
503 if(a.hasDirectConnection() || b.hasDirectConnection()) {
504 if(Line2D.ptSegDistSq(a.x, a.y, b.x, b.y, x, y) <= tolerance*tolerance) {
505 RouteLine dummy = new RouteLine(false, y);
506 return dummy; // There are no lines to return
513 if((mask & PICK_PERSISTENT_LINES) != 0)
514 for(RouteLine line : lines)
515 if(line.isNear(x, y, tolerance))
517 if((mask & PICK_TRANSIENT_LINES) != 0)
518 for(RouteLine line : transientLines)
519 if(line.isNear(x, y, tolerance))
521 if((mask & PICK_DIRECT_LINES) != 0) {
522 RouteTerminal terminal = pickDirectLine(x, y, tolerance);
524 return terminal.line;
529 public RouteLine pickLine(double x, double y, double tolerance) {
530 return pickLine(x, y, tolerance, PICK_LINES);
533 private RouteTerminal pickDirectLine(double x, double y, double tolerance) {
534 double toleranceSq = tolerance * tolerance;
535 for(RouteTerminal terminal : terminals)
536 if((terminal.getAllowedDirections()&0x10) != 0) {
538 RouteLine line = terminal.getLine();
541 RoutePoint b = line.getBegin();
542 double distSq = Line2D.ptSegDistSq(terminal.x, terminal.y, b.x, b.y, x, y);
543 if(distSq <= toleranceSq) {
544 //System.out.println("distSq = " + distSq + ", toleranceSq = " + toleranceSq);
547 } catch(NullPointerException e) {
548 //if terminal does not have a line
550 } catch(IndexOutOfBoundsException e) {
551 // if line does not contain points
558 public RouteLineHalf pickLineHalf(double x, double y, double tolerance) {
559 if(isSimpleConnection)
563 RouteLine line = pickLine(x, y, tolerance);
566 RouteLink link = null;
567 RoutePoint begin = line.getBegin();
568 RoutePoint end = line.getEnd();
569 if(line.isHorizontal) {
570 double mx = 0.5*(begin.getX() + end.getX());
572 if(begin instanceof RouteLink)
573 link = (RouteLink)begin;
576 if(end instanceof RouteLink)
577 link = (RouteLink)line.getEnd();
581 double my = 0.5*(begin.getY() + end.getY());
583 if(begin instanceof RouteLink)
584 link = (RouteLink)begin;
587 if(end instanceof RouteLink)
588 link = (RouteLink)end;
593 if(link.getOther(line).isTransient())
595 return new RouteLineHalf(line, link);
598 public Collection<RouteLineHalf> getLineHalves() {
599 return getLineHalves(new ArrayList<RouteLineHalf>());
602 public Collection<RouteLineHalf> getLineHalves(Collection<RouteLineHalf> result) {
603 for(RouteLine line : getAllLines()) {
605 RoutePoint p = line.getBegin();
606 if(p instanceof RouteLink) {
607 RouteLink link = (RouteLink)p;
608 if(!link.getOther(line).isTransient())
609 result.add(new RouteLineHalf(line, link));
613 RoutePoint p = line.getEnd();
614 if(p instanceof RouteLink) {
615 RouteLink link = (RouteLink)p;
616 if(!link.getOther(line).isTransient())
617 result.add(new RouteLineHalf(line, link));
625 * Returns a RoutePoint near the given point (x,y) (within tolerance).
627 public RoutePoint pickPoint(double x, double y, double tolerance, int mask) {
630 if((mask&PICK_TERMINALS) != 0)
631 for(RouteTerminal terminal : terminals)
632 if(terminal.isNear(x, y))
634 if((mask&PICK_INTERIOR_POINTS) != 0) {
635 for(RouteLine line : lines) {
636 for(RoutePoint point : line.points)
637 if(point.isNear(x, y, tolerance))
640 for(RouteLine line : transientLines) {
641 for(RoutePoint point : line.points)
642 if(point.isNear(x, y, tolerance))
649 public RoutePoint pickPoint(double x, double y, double tolerance) {
650 return pickPoint(x, y, tolerance, PICK_POINTS);
653 public static final int PICK_INTERIOR_POINTS = 1;
654 public static final int PICK_TERMINALS = 2;
655 public static final int PICK_POINTS = PICK_INTERIOR_POINTS | PICK_TERMINALS;
657 public static final int PICK_PERSISTENT_LINES = 4;
658 public static final int PICK_TRANSIENT_LINES = 8;
659 public static final int PICK_DIRECT_LINES = 16;
660 public static final int PICK_LINES = PICK_PERSISTENT_LINES | PICK_TRANSIENT_LINES | PICK_DIRECT_LINES;
662 public static final int PICK_ALL = PICK_POINTS | PICK_LINES;
665 * Returns RoutePoint or RouteLine near the given point (x,y) (within tolerance).
667 public Object pick(double x, double y, double tolerance, int mask) {
668 if((mask & PICK_POINTS) != 0) {
669 Object point = pickPoint(x, y, tolerance, mask);
673 /*if((mask & PICK_DIRECT_LINES) != 0) {
674 RouteTerminal terminal = pickDirectLine(x, y, tolerance);
676 return terminal.line.getBegin();
678 if((mask & PICK_LINES) != 0)
679 return pickLine(x, y, tolerance, mask);
684 public Object pick(double x, double y, double tolerance) {
685 return pick(x, y, tolerance, PICK_ALL);
689 * Returns RoutePoint or RouteLine exactly under the given point (x,y).
691 public Object pick(double x, double y) {
692 return pick(x, y, 0.0);
696 * Prints the contents of the route graph to stdout.
698 public void print() {
703 * Prints the contents of the route graph to given print stream.
705 public void print(PrintStream out) {
708 if(isSimpleConnection)
709 out.println("=== SIMPLE CONNECTION ===");
711 out.println("=== COMPLEX CONNECTION ===");
712 for(RouteLine line : lines) {
716 for(RouteLine line : transientLines) {
720 for(RouteTerminal terminal : terminals) {
726 public void makePersistent(RouteLine line) {
727 prepareForModification();
728 if(isSimpleConnection || line.isTransient()) {
729 if(isSimpleConnection) {
730 isSimpleConnection = false;
731 for(RouteTerminal terminal : terminals)
732 terminal.line = line;
733 transientLines.remove(line);
735 Iterator<RoutePoint> it = line.points.iterator();
736 while(it.hasNext()) {
737 RoutePoint point = it.next();
738 if(point instanceof RouteTerminal)
741 line.terminal = null;
742 line.nextTransient = null;
745 line.terminal.line = line;
747 transientLines.remove(line);
749 Iterator<RoutePoint> it = line.points.iterator();
750 while(it.hasNext()) {
751 RoutePoint point = it.next();
752 if(point instanceof RouteTerminal)
755 line.terminal = null;
756 RouteLine temp = line.nextTransient;
757 line.nextTransient = null;
759 } while(line != null);
765 void prepareForModification() {
767 RouteLine line = transientLines.remove(0);
768 line.terminal = null;
770 for(RouteTerminal terminal : terminals)
771 terminal.line = line;
772 isSimpleConnection = false;
777 public double[] getLineLengths(RouteTerminal terminal) {
780 if(lines.size()==0 && transientLines.size()==1)
781 return new double[] { transientLines.get(0).getLength() };
783 RouteLine line = null;
785 for(RouteLine l : transientLines)
786 if(l.isTransient()) {
787 for(RoutePoint p : l.points)
788 if(!(p instanceof RouteLink)) {
793 TDoubleArrayList result = new TDoubleArrayList();
794 THashSet<RouteLine> set = new THashSet<RouteLine>();
798 result.add(line.getLength());
799 for(RoutePoint point : line.points)
800 if(point instanceof RouteLink) {
801 RouteLink link = (RouteLink)point;
802 if(set.add(link.a)) {
806 if(set.add(link.b)) {
813 return result.toArray();
816 public void split(RouteLine rl1, double splitPosition) {
819 boolean isHorizontal = rl1.isHorizontal();
820 if(isSimpleConnection) {
821 isSimpleConnection = false;
823 RouteLine sp = addLine(!isHorizontal, splitPosition);
824 for(RouteTerminal terminal : terminals)
829 else if(caseId < 4) {
830 RouteLine sp = addLine(!isHorizontal, splitPosition);
831 RouteLine l = addLine(isHorizontal, rl1.position);
833 rl1.terminal.line = sp;
834 for(RouteTerminal terminal : terminals)
835 if(terminal != rl1.terminal)
841 prepareForModification();
844 RouteLine rl2 = new RouteLine(isHorizontal, rl1.getPosition());
845 RouteLine sp = new RouteLine(!isHorizontal, splitPosition);
847 ArrayList<RoutePoint> points = rl1.points;
852 int maxPos = points.size();
853 while(minPos != maxPos) {
854 int c = (minPos + maxPos)/2;
856 ? points.get(c).getX() > splitPosition
857 : points.get(c).getY() > splitPosition)
865 for(int i=points.size()-1;i>=splitPos;--i) {
866 RoutePoint point = points.remove(i);
867 if(point instanceof RouteLink) {
868 RouteLink link = (RouteLink)point;
869 link.replace(rl1, rl2);
873 if(rl1.isTransient()) {
874 boolean p1 = rl1.isConnectedToPeristentLine();
875 boolean p2 = rl2.isConnectedToPeristentLine();
877 RouteTerminal terminal = rl1.terminal;
880 transientLines.add(rl2);
886 transientLines.add(rl2);
888 for(RouteTerminal t : terminals)
896 new RouteLink(rl1, sp);
897 new RouteLink(sp, rl2);
902 public boolean merge(RouteLine line) {
905 for(RoutePoint point : line.points) {
906 if(point instanceof RouteLink) {
907 RouteLink link = (RouteLink)point;
908 RouteLine other = link.getOther(line);
909 if(!other.isTransient()) {
910 sum += other.position;
915 return merge(line, sum / count);
919 * Removes given route line and collapses all its neighbor
920 * route lines into one line with given position.
922 public boolean merge(RouteLine line, double position) {
925 if(isSimpleConnection || line.isTransient())
928 if(lines.size() == 1) {
929 if(terminals.size() != 2)
932 isSimpleConnection = true;
933 for(RouteTerminal terminal : terminals)
934 terminal.line = null;
939 ArrayList<RouteLine> nLines = new ArrayList<RouteLine>();
940 for(RoutePoint point : line.points) {
941 /* Because line.terminal == null, there should be
944 * Update 14.2.2014: With Sulca, there is a constant issue when line.terminal == null but point is RouteTerminal.
946 if (point instanceof RouteLink) {
947 RouteLine l = ((RouteLink)point).getOther(line);
948 l.points.remove(point);
956 RouteLine merged = nLines.remove(nLines.size()-1);
957 merged.position = position;
958 for(RouteLine l : nLines) {
959 for(RoutePoint point : l.points) {
960 /* Because l.terminal == null, there should be
963 * Update 16.10.2014: With Sulca, there is a constant issue when line.terminal == null but point is RouteTerminal.
965 if (point instanceof RouteLink) {
966 RouteLink link = (RouteLink)point;
967 link.replace(l, merged);
971 THashSet<RouteLine> removedLines = new THashSet<RouteLine>();
972 removedLines.addAll(nLines);
973 lines.removeAll(removedLines);
974 removedLines.add(line);
976 for(RouteTerminal terminal : terminals)
977 if(removedLines.contains(terminal.line))
978 terminal.line = merged;
983 public void deleteCorner(RouteLink link) {
986 RouteLine a = link.getA();
987 RouteLine b = link.getB();
988 if(a.isTransient() || b.isTransient()
989 || a.points.size() != 2 || b.points.size() != 2)
991 RouteLine na=null, nb=null;
992 for(RoutePoint p : a.points) {
993 RouteLink l = (RouteLink)p;
994 if(l.a == a && l.b != b) {
998 else if(l.b == a && l.a != b) {
1003 for(RoutePoint p : b.points) {
1004 RouteLink l = (RouteLink)p;
1005 if(l.a == b && l.b != a) {
1009 else if(l.b == b && l.a != a) {
1014 if(na == null || nb == null) {
1015 System.err.println("Internal error in router.");
1023 if(na.terminal != null)
1024 na.terminal.line = nb;
1025 if(nb.terminal != null)
1026 nb.terminal.line = na;
1030 public boolean connectTerminal(RouteTerminal terminal, double x, double y, double tolerance) {
1031 Object target = pick(x, y, tolerance);
1032 if(target instanceof RouteLine) {
1033 RouteLine line = (RouteLine)target;
1034 RouteTerminal involvedTerminal =
1035 line.isTransient() ? line.terminal : null;
1036 makePersistent(line);
1037 terminal.line = null; // TODO this is a workaround
1039 int lineDir = line.isHorizontal
1040 ? (line.position < terminal.y ? 3 : 1)
1041 : (line.position < terminal.x ? 2 : 0)
1044 if(line.isHorizontal && Math.abs(x - terminal.x) > 30) {
1046 if(Directions.isAllowed(terminal.getAllowedDirections(), lineDir)) {
1047 RouteLine l1 = addLine(true, 0.5*(y+terminal.y));
1048 l2 = addLine(false, x);
1049 link(terminal, l1, l2, line);
1052 l2 = addLine(false, x);
1053 link(terminal, l2, line);
1055 if(involvedTerminal != null)
1056 involvedTerminal.line = l2;
1058 else if(!line.isHorizontal && Math.abs(y - terminal.y) > 30) {
1060 if(Directions.isAllowed(terminal.getAllowedDirections(), lineDir)) {
1061 RouteLine l1 = addLine(false, 0.5*(x+terminal.x));
1062 l2 = addLine(true, y);
1063 link(terminal, l1, l2, line);
1066 l2 = addLine(true, y);
1067 link(terminal, l2, line);
1069 if(involvedTerminal != null)
1070 involvedTerminal.line = l2;
1073 link(terminal, line);
1082 public boolean connectLine(RouteLine sLine, double x, double y, double tolerance) {
1083 Object target = pick(x, y, tolerance);
1084 if(target instanceof RouteLine) {
1085 RouteLine line = (RouteLine)target;
1086 RouteTerminal involvedTerminal =
1087 line.isTransient() ? line.terminal : null;
1088 makePersistent(line);
1089 if(line.isHorizontal == sLine.isHorizontal) {
1090 RouteLine l = addLine(!sLine.isHorizontal,
1091 sLine.isHorizontal ? x : y);
1092 link(sLine, l, line);
1093 if(involvedTerminal != null)
1094 involvedTerminal.line = l;
1098 if(involvedTerminal != null)
1099 involvedTerminal.line = sLine;
1109 * Makes a deep copy of the route graph and stores
1110 * the correspondences between original and copied
1111 * objects into the given map.
1113 public RouteGraph copy(THashMap<Object, Object> map) {
1114 RouteGraph copy = new RouteGraph();
1115 copy.isSimpleConnection = isSimpleConnection;
1116 copy.caseId = caseId;
1117 copy.needsUpdate = needsUpdate;
1118 for(RouteLine line : lines)
1119 copy.lines.add(line.copy(map));
1120 for(RouteLine line : transientLines)
1121 copy.transientLines.add(line.copy(map));
1122 for(RouteTerminal terminal : terminals)
1123 copy.terminals.add(terminal.copy(map));
1128 * Makes a deep copy of the route graph.
1130 public RouteGraph copy() {
1131 THashMap<Object, Object> map = new THashMap<Object, Object>();
1136 * Removes route lines that are conneted to at most
1137 * one other route line or terminal.
1139 public void removeExtraConnections() {
1140 TObjectIntHashMap<RouteLine> counts =
1141 new TObjectIntHashMap<RouteLine>();
1142 removeTransientRouteLines();
1143 for(RouteLine line : lines) {
1145 for(RoutePoint point : line.points)
1146 if(point instanceof RouteLink)
1148 counts.put(line, count);
1150 for(RouteTerminal terminal : terminals)
1151 counts.adjustOrPutValue(terminal.line, 1, 1);
1155 Iterator<RouteLine> it = lines.iterator();
1156 while(it.hasNext()) {
1157 RouteLine line = it.next();
1158 if(counts.get(line) <= 1) {
1159 for(RoutePoint point : line.points)
1160 if(point instanceof RouteLink)
1161 counts.adjustValue(((RouteLink)point).getOther(line), -1);
1172 * Removes a terminal and all route lines that become degenerated by the removal.
1174 public void remove(RouteTerminal terminal) {
1175 terminals.remove(terminal);
1176 removeExtraConnections();
1179 public void disconnect(RouteTerminal terminal) {
1180 terminal.line = null;
1181 removeExtraConnections();
1184 public void remove(RouteLink link) {
1185 link.a.points.remove(link);
1186 link.b.points.remove(link);
1189 public void toggleDirectLines(RouteTerminal terminal) {
1190 terminal.toggleDirectLines();
1195 * Returns all persistent route lines of the route graph.
1197 public Collection<RouteLine> getLines() {
1200 if(RETURN_UNMODIFIABLE_COLLECTIONS)
1201 return Collections.unmodifiableList(lines);
1207 * Returns all transient route lines of the route graph.
1209 public Collection<RouteLine> getTransientLines() {
1212 if(RETURN_UNMODIFIABLE_COLLECTIONS)
1213 return Collections.unmodifiableList(transientLines);
1215 return transientLines;
1219 * Returns all terminals of the route graph.
1221 public Collection<RouteTerminal> getTerminals() {
1222 if(RETURN_UNMODIFIABLE_COLLECTIONS)
1223 return Collections.unmodifiableList(terminals);
1229 * Returns all route lines, both persistent and transient,
1230 * of the route graph.
1232 public Collection<RouteLine> getAllLines() {
1235 ArrayList<RouteLine> allLines = new ArrayList<RouteLine>(lines.size() + transientLines.size());
1236 allLines.addAll(lines);
1237 allLines.addAll(transientLines);
1242 * Returns all route lines, both persistent and transient, of the route
1243 * graph, added to the specified collection. Avoids allocation of new
1244 * {@link ArrayList} compared to {@link #getAllLines()}.
1246 public Collection<RouteLine> getAllLines(Collection<RouteLine> result) {
1248 throw new NullPointerException("null result collection");
1251 result.addAll(lines);
1252 result.addAll(transientLines);
1257 * Returns true, if the route graph is connected and contains no cycles.
1259 public boolean isTree() {
1260 if(isSimpleConnection)
1262 for(RouteTerminal terminal : terminals)
1263 if(terminal.line == null)
1265 THashSet<RouteLine> visited = new THashSet<RouteLine>();
1266 ArrayList<RouteLine> stack = new ArrayList<RouteLine>();
1268 visited.add(lines.get(0));
1269 stack.add(lines.get(0));
1270 while(!stack.isEmpty()) {
1271 RouteLine cur = stack.remove(stack.size()-1);
1272 for(RouteLine n : cur.getPersistentNeighbors()) {
1278 return visited.size() == lines.size() && linkCount == 2*(lines.size()-1);
1282 * Returns if the connection is simple. A connection is simple, if it has just
1283 * two terminals connected together directly without (persistent) route lines.
1285 public boolean isSimpleConnection() {
1286 return isSimpleConnection;
1289 public void replaceBy(RouteGraph rg) {
1290 this.lines = rg.lines;
1291 this.terminals = rg.terminals;
1292 this.transientLines = rg.transientLines;
1293 this.caseId = rg.caseId;
1294 this.isSimpleConnection = rg.isSimpleConnection;
1295 this.needsUpdate = rg.needsUpdate;
1299 private void reset() {
1300 lines = new ArrayList<RouteLine>();
1301 terminals = new ArrayList<RouteTerminal>();
1302 transientLines = new ArrayList<RouteLine>();
1304 isSimpleConnection = false;
1305 needsUpdate = false;
1308 public Rectangle2D getBounds() {
1309 Rectangle2D bounds = new Rectangle2D.Double();
1314 public void getBounds(Rectangle2D bounds) {
1317 double minX = Double.POSITIVE_INFINITY;
1318 double maxX = Double.NEGATIVE_INFINITY;
1319 double minY = Double.POSITIVE_INFINITY;
1320 double maxY = Double.NEGATIVE_INFINITY;
1321 for(RouteLine line : lines) {
1322 double position = line.position;
1323 if(line.isHorizontal) {
1324 minY = Math.min(minY, position);
1325 maxY = Math.max(maxY, position);
1328 minX = Math.min(minX, position);
1329 maxX = Math.max(maxX, position);
1332 for(RouteLine line : transientLines) {
1333 double position = line.position;
1334 if(line.isHorizontal) {
1335 minY = Math.min(minY, position);
1336 maxY = Math.max(maxY, position);
1339 minX = Math.min(minX, position);
1340 maxX = Math.max(maxX, position);
1343 for(RouteTerminal terminal : terminals) {
1344 double x = terminal.x;
1345 double y = terminal.y;
1346 minX = Math.min(minX, x);
1347 maxX = Math.max(maxX, x);
1348 minY = Math.min(minY, y);
1349 maxY = Math.max(maxY, y);
1351 bounds.setFrame(minX, minY, maxX-minX, maxY-minY);
1354 private static void addPathBegin(Path2D path, RoutePoint cur, RouteLine line) {
1355 double x = cur.x, y = cur.y;
1356 if(cur instanceof RouteTerminal) {
1357 ILineEndStyle style = ((RouteTerminal)cur).getRenderStyle();
1358 if(line.isHorizontal()) {
1359 if(cur == line.getBegin())
1360 x += style.getLineEndLength(0);
1362 x -= style.getLineEndLength(2);
1365 if(cur == line.getBegin())
1366 y += style.getLineEndLength(1);
1368 y -= style.getLineEndLength(3);
1374 private static final Comparator<RoutePoint> RG_COMP = (o1, o2) -> {
1375 if (o1.getX() < o2.getX())
1377 else if (o1.getX() > o2.getX())
1379 if (o1.getY() < o2.getY())
1381 else if (o1.getY() > o2.getY())
1386 private static void addPathEnd(Path2D path, RoutePoint cur, RouteLine line) {
1387 double x = cur.x, y = cur.y;
1388 if(cur instanceof RouteTerminal) {
1389 ILineEndStyle style = ((RouteTerminal)cur).getRenderStyle();
1390 if(line.isHorizontal()) {
1391 if(cur == line.getBegin())
1392 x += style.getLineEndLength(0);
1394 x -= style.getLineEndLength(2);
1397 if(cur == line.getBegin())
1398 y += style.getLineEndLength(1);
1400 y -= style.getLineEndLength(3);
1406 public void getPath2D(Path2D path) {
1410 if(isSimpleConnection && transientLines.isEmpty()) {
1411 if(terminals.size() == 2) {
1412 RouteTerminal a = terminals.get(0);
1413 RouteTerminal b = terminals.get(1);
1414 if(a.hasDirectConnection() || b.hasDirectConnection()) {
1415 path.moveTo(a.x, a.y);
1416 path.lineTo(b.x, b.y);
1423 Map<RoutePoint, RouteLine> begins = new HashMap<RoutePoint, RouteLine>();
1424 for(RouteLine line : lines) {
1427 for(RouteLine line : transientLines) {
1430 for(RouteTerminal terminal : terminals)
1431 if((terminal.getAllowedDirections() & 0x10)!=0 && terminal.line != null) {
1432 begins.remove(terminal.line.getBegin());
1433 drawContinuousPath(path, begins, terminal, terminal.line);
1437 // Sort the begins so that the order of segments is deterministic. This is required when comparing e.g. SVG diagrams.
1438 // In case of overlapping begins the order may vary.
1439 for(RoutePoint begin : begins.keySet().stream().sorted(RG_COMP).collect(Collectors.toList())) {
1440 RouteLine curLine = begins.remove(begin);
1441 drawContinuousPath(path, begins, begin, curLine);
1445 private void drawContinuousPath(Path2D path, Map<RoutePoint, RouteLine> begins,
1446 RoutePoint cur, RouteLine curLine) {
1449 addPathBegin(path, cur, curLine);
1452 if(cur != curLine.getEnd())
1453 cur = curLine.getEnd();
1455 cur = curLine.getBegin();
1456 if(begins.remove(cur) != null || !(cur instanceof RouteLink)) {
1457 addPathEnd(path, cur, curLine);
1460 if(cur instanceof RouteLink) {
1461 if(!curLine.isDegenerated() ||
1462 path.getCurrentPoint().getX() != cur.x ||
1463 path.getCurrentPoint().getY() != cur.y)
1464 path.lineTo(cur.x, cur.y);
1465 RouteLink link = (RouteLink)cur;
1466 if(link.a != curLine)
1474 private static void add(Map<RoutePoint, RouteLine> begins,
1476 if(line.points.size() > 1) {
1478 RoutePoint p = line.getBegin();
1479 if(begins.remove(p) == null)
1480 begins.put(p, line);
1483 RoutePoint p = line.getEnd();
1484 if(begins.remove(p) == null)
1485 begins.put(p, line);
1490 public Collection<Segment> getSegments() {
1493 ArrayList<Segment> segments = new ArrayList<Segment>();
1494 for(RouteLine routeLine : lines)
1495 routeLine.collectSegments(segments);
1496 for(RouteLine routeLine : transientLines)
1497 routeLine.collectSegments(segments);
1501 public Segment findNearestSegment(double x, double y) {
1502 Segment nearest = null;
1503 double minDistanceSq = Double.MAX_VALUE;
1505 for (Segment segment : getSegments()) {
1506 RoutePoint p1 = segment.p1;
1507 RoutePoint p2 = segment.p2;
1509 double distanceSq = Line2D.ptSegDistSq(p1.x, p1.y, p2.x, p2.y, x, y);
1510 if (distanceSq < minDistanceSq) {
1511 minDistanceSq = distanceSq;
1518 public Point2D findNearestPoint(double x, double y) {
1519 Segment nearest = findNearestSegment(x, y);
1520 if (nearest == null) return null;
1522 RoutePoint p1 = nearest.p1;
1523 RoutePoint p2 = nearest.p2;
1525 double d = Math.pow(p2.x - p1.x, 2.0) + Math.pow(p2.y - p1.y, 2.0);
1528 return new Point2D.Double(p1.x, p1.y);
1530 double u = ((x - p1.x) * (p2.x - p1.x) + (y - p1.y) * (p2.y - p1.y)) / d;
1532 return new Point2D.Double(p2.x, p2.y);
1533 } else if (u <= 0.0) {
1534 return new Point2D.Double(p1.x, p1.y);
1536 return new Point2D.Double(p2.x * u + p1.x * (1.0-u), (p2.y * u + p1.y * (1.0- u)));
1541 public Path2D getPath2D() {
1542 Path2D result = new Path2D.Double();
1547 public SplittedRouteGraph splitGraph(RouteLine splitLine, double position) {
1548 THashSet<RouteNode> interfaceNodes1 = new THashSet<RouteNode>();
1549 THashSet<RouteLine> lines1 = new THashSet<RouteLine>();
1550 THashSet<RouteTerminal> terminals1 = new THashSet<RouteTerminal>();
1551 THashSet<RouteNode> interfaceNodes2 = new THashSet<RouteNode>();
1552 THashSet<RouteLine> lines2 = new THashSet<RouteLine>();
1553 THashSet<RouteTerminal> terminals2 = new THashSet<RouteTerminal>();
1555 if(splitLine.isTransient()) {
1556 RouteTerminal terminal = splitLine.terminal;
1558 if(splitLine.beginsWithTerminal()) {
1559 lines2.addAll(getLines());
1560 terminals2.addAll(getTerminals());
1561 terminals1.add(terminal);
1562 terminals2.remove(terminal);
1563 interfaceNodes1.add(terminal);
1564 if(isSimpleConnection())
1565 interfaceNodes2.addAll(terminals2);
1567 interfaceNodes2.add(terminal.line);
1570 lines1.addAll(getLines());
1571 terminals1.addAll(getTerminals());
1572 terminals2.add(terminal);
1573 terminals1.remove(terminal);
1574 interfaceNodes2.add(terminal);
1575 if(isSimpleConnection())
1576 interfaceNodes1.addAll(terminals1);
1578 interfaceNodes1.add(terminal.line);
1582 for(RoutePoint rp : splitLine.getPoints()) {
1583 double p = splitLine.isHorizontal ? rp.x : rp.y;
1584 if(rp instanceof RouteLink) {
1585 RouteLink link = (RouteLink)rp;
1586 RouteLine otherLine = link.getA() != splitLine ? link.getA()
1588 if(otherLine.isTransient()) {
1590 interfaceNodes1.add(otherLine.terminal);
1591 terminals1.add(otherLine.terminal);
1594 interfaceNodes2.add(otherLine.terminal);
1595 terminals2.add(otherLine.terminal);
1601 interfaceNodes1.add(otherLine);
1602 traverseGraph(link, otherLine, lines1);
1605 interfaceNodes2.add(otherLine);
1606 traverseGraph(link, otherLine, lines2);
1611 for(RouteTerminal terminal : getTerminals())
1612 if(lines1.contains(terminal.line))
1613 terminals1.add(terminal);
1614 else if(lines2.contains(terminal.line))
1615 terminals2.add(terminal);
1618 return new SplittedRouteGraph(splitLine,
1619 interfaceNodes1, lines1, terminals1,
1620 interfaceNodes2, lines2, terminals2);
1623 private void traverseGraph(RoutePoint previousPoint, RouteLine line,
1624 THashSet<RouteLine> lines) {
1625 if(lines.add(line)) {
1626 for(RoutePoint rp : line.getPoints()) {
1627 if(rp != previousPoint && rp instanceof RouteLink) {
1628 RouteLink link = (RouteLink)rp;
1629 RouteLine otherLine = line != link.getA()
1630 ? link.getA() : link.getB();
1631 if(otherLine.isTransient())
1633 traverseGraph(rp, otherLine, lines);
1639 public void reclaimTransientMemory() {
1640 removeTransientRouteLines();