| 119 | | /** |
| 120 | | * Takes the data from computeHelperLine to determine which ways/nodes should be highlighted |
| 121 | | * (if feature enabled). Also sets the target cursor if appropriate. |
| 122 | | */ |
| 123 | | private void addHighlighting() { |
| 124 | | removeHighlighting(); |
| 125 | | // if ctrl key is held ("no join"), don't highlight anything |
| 126 | | if (ctrl) { |
| 127 | | Main.map.mapView.setNewCursor(cursor, this); |
| 128 | | return; |
| 129 | | } |
| 130 | | |
| 131 | | // This happens when nothing is selected, but we still want to highlight the "target node" |
| 132 | | if (mouseOnExistingNode == null && getCurrentDataSet().getSelected().size() == 0 |
| 133 | | && mousePos != null) { |
| 134 | | mouseOnExistingNode = Main.map.mapView.getNearestNode(mousePos, OsmPrimitive.isSelectablePredicate); |
| 135 | | } |
| 136 | | |
| 137 | | if (mouseOnExistingNode != null) { |
| 138 | | Main.map.mapView.setNewCursor(cursorJoinNode, this); |
| 139 | | // We also need this list for the statusbar help text |
| 140 | | oldHighlights.add(mouseOnExistingNode); |
| 141 | | if(drawTargetHighlight) { |
| 142 | | mouseOnExistingNode.setHighlighted(true); |
| 143 | | } |
| 144 | | return; |
| 145 | | } |
| 146 | | |
| 147 | | // Insert the node into all the nearby way segments |
| 148 | | if (mouseOnExistingWays.size() == 0) { |
| 149 | | Main.map.mapView.setNewCursor(cursor, this); |
| 150 | | return; |
| 151 | | } |
| 152 | | |
| 153 | | Main.map.mapView.setNewCursor(cursorJoinWay, this); |
| 154 | | |
| 155 | | // We also need this list for the statusbar help text |
| 156 | | oldHighlights.addAll(mouseOnExistingWays); |
| 157 | | if (!drawTargetHighlight) return; |
| 158 | | for (Way w : mouseOnExistingWays) { |
| 159 | | w.setHighlighted(true); |
| 160 | | } |
| 161 | | } |
| 162 | | |
| 163 | | /** |
| 164 | | * Removes target highlighting from primitives |
| 165 | | */ |
| 166 | | private void removeHighlighting() { |
| 167 | | for(OsmPrimitive prim : oldHighlights) { |
| 168 | | prim.setHighlighted(false); |
| 169 | | } |
| 170 | | oldHighlights = new HashSet<OsmPrimitive>(); |
| 171 | | } |
| 172 | | |
| 346 | | // Insert the node into all the nearby way segments |
| 347 | | List<WaySegment> wss = Main.map.mapView.getNearestWaySegments(e.getPoint(), OsmPrimitive.isSelectablePredicate); |
| 348 | | Map<Way, List<Integer>> insertPoints = new HashMap<Way, List<Integer>>(); |
| 349 | | for (WaySegment ws : wss) { |
| 350 | | List<Integer> is; |
| 351 | | if (insertPoints.containsKey(ws.way)) { |
| 352 | | is = insertPoints.get(ws.way); |
| 353 | | } else { |
| 354 | | is = new ArrayList<Integer>(); |
| 355 | | insertPoints.put(ws.way, is); |
| 356 | | } |
| 357 | | |
| 358 | | is.add(ws.lowerIndex); |
| 359 | | } |
| 360 | | |
| 361 | | Set<Pair<Node,Node>> segSet = new HashSet<Pair<Node,Node>>(); |
| 362 | | |
| 363 | | for (Map.Entry<Way, List<Integer>> insertPoint : insertPoints.entrySet()) { |
| 364 | | Way w = insertPoint.getKey(); |
| 365 | | List<Integer> is = insertPoint.getValue(); |
| 366 | | |
| 367 | | Way wnew = new Way(w); |
| 368 | | |
| 369 | | pruneSuccsAndReverse(is); |
| 370 | | for (int i : is) { |
| 371 | | segSet.add( |
| 372 | | Pair.sort(new Pair<Node,Node>(w.getNode(i), w.getNode(i+1)))); |
| 373 | | } |
| 374 | | for (int i : is) { |
| 375 | | wnew.addNode(i + 1, n); |
| 376 | | } |
| 377 | | |
| 378 | | // If ALT is pressed, a new way should be created and that new way should get |
| 379 | | // selected. This works everytime unless the ways the nodes get inserted into |
| 380 | | // are already selected. This is the case when creating a self-overlapping way |
| 381 | | // but pressing ALT prevents this. Therefore we must de-select the way manually |
| 382 | | // here so /only/ the new way will be selected after this method finishes. |
| 383 | | if(alt) { |
| 384 | | newSelection.add(insertPoint.getKey()); |
| 385 | | } |
| 386 | | |
| 387 | | cmds.add(new ChangeCommand(insertPoint.getKey(), wnew)); |
| 388 | | replacedWays.add(insertPoint.getKey()); |
| 389 | | reuseWays.add(wnew); |
| 390 | | } |
| 391 | | |
| 392 | | adjustNode(segSet, n); |
| 393 | | } |
| | 315 | // Insert the node into all the nearby way segments |
| | 316 | List<WaySegment> wss = Main.map.mapView.getNearestWaySegments( |
| | 317 | Main.map.mapView.getPoint(n), OsmPrimitive.isSelectablePredicate); |
| | 318 | insertNodeIntoAllNearbySegments(wss, n, newSelection, cmds, replacedWays, reuseWays); |
| | 319 | } |
| | 484 | is.add(ws.lowerIndex); |
| | 485 | } |
| | 486 | |
| | 487 | Set<Pair<Node,Node>> segSet = new HashSet<Pair<Node,Node>>(); |
| | 488 | |
| | 489 | for (Map.Entry<Way, List<Integer>> insertPoint : insertPoints.entrySet()) { |
| | 490 | Way w = insertPoint.getKey(); |
| | 491 | List<Integer> is = insertPoint.getValue(); |
| | 492 | |
| | 493 | Way wnew = new Way(w); |
| | 494 | |
| | 495 | pruneSuccsAndReverse(is); |
| | 496 | for (int i : is) { |
| | 497 | segSet.add( |
| | 498 | Pair.sort(new Pair<Node,Node>(w.getNode(i), w.getNode(i+1)))); |
| | 499 | } |
| | 500 | for (int i : is) { |
| | 501 | wnew.addNode(i + 1, n); |
| | 502 | } |
| | 503 | |
| | 504 | // If ALT is pressed, a new way should be created and that new way should get |
| | 505 | // selected. This works everytime unless the ways the nodes get inserted into |
| | 506 | // are already selected. This is the case when creating a self-overlapping way |
| | 507 | // but pressing ALT prevents this. Therefore we must de-select the way manually |
| | 508 | // here so /only/ the new way will be selected after this method finishes. |
| | 509 | if(alt) { |
| | 510 | newSelection.add(insertPoint.getKey()); |
| | 511 | } |
| | 512 | |
| | 513 | cmds.add(new ChangeCommand(insertPoint.getKey(), wnew)); |
| | 514 | replacedWays.add(insertPoint.getKey()); |
| | 515 | reuseWays.add(wnew); |
| | 516 | } |
| | 517 | |
| | 518 | adjustNode(segSet, n); |
| | 519 | } |
| | 520 | |
| | 521 | |
| | 651 | determineCurrentBaseNodeAndPreviousNode(selection); |
| | 652 | if (previousNode == null) snapHelper.reset(); |
| | 653 | |
| | 654 | if (currentBaseNode == null || currentBaseNode == currentMouseNode) |
| | 655 | return; // Don't create zero length way segments. |
| | 656 | |
| | 657 | // find out the distance, in metres, between the base point and the mouse cursor |
| | 658 | LatLon mouseLatLon = mv.getProjection().eastNorth2latlon(currentMouseEastNorth); |
| | 659 | distance = currentBaseNode.getCoor().greatCircleDistance(mouseLatLon); |
| | 660 | |
| | 661 | double hdg = Math.toDegrees(currentBaseNode.getEastNorth() |
| | 662 | .heading(currentMouseEastNorth)); |
| | 663 | if (previousNode != null) { |
| | 664 | angle = hdg - Math.toDegrees(previousNode.getEastNorth() |
| | 665 | .heading(currentBaseNode.getEastNorth())); |
| | 666 | angle += angle < 0 ? 360 : 0; |
| | 667 | } |
| | 668 | |
| | 669 | if (snapOn) snapHelper.checkAngleSnapping(currentMouseEastNorth,angle); |
| | 670 | |
| | 671 | Main.map.statusLine.setAngle(angle); |
| | 672 | Main.map.statusLine.setHeading(hdg); |
| | 673 | Main.map.statusLine.setDist(distance); |
| | 674 | // Now done in redrawIfRequired() |
| | 675 | //updateStatusLine(); |
| | 676 | } |
| | 677 | |
| | 678 | |
| | 679 | /** |
| | 680 | * Helper function that sets fields currentBaseNode and previousNode |
| | 681 | * @param selection |
| | 682 | * uses also lastUsedNode field |
| | 683 | */ |
| | 684 | private void determineCurrentBaseNodeAndPreviousNode(Collection<OsmPrimitive> selection) { |
| | 685 | Node selectedNode = null; |
| | 686 | Way selectedWay = null; |
| 711 | | // find out the distance, in metres, between the base point and the mouse cursor |
| 712 | | LatLon mouseLatLon = mv.getProjection().eastNorth2latlon(currentMouseEastNorth); |
| 713 | | distance = currentBaseNode.getCoor().greatCircleDistance(mouseLatLon); |
| 714 | | |
| 715 | | double hdg = Math.toDegrees(currentBaseNode.getEastNorth() |
| 716 | | .heading(currentMouseEastNorth)); |
| 717 | | if (previousNode != null) { |
| 718 | | angle = hdg - Math.toDegrees(previousNode.getEastNorth() |
| 719 | | .heading(currentBaseNode.getEastNorth())); |
| 720 | | angle += angle < 0 ? 360 : 0; |
| 721 | | } |
| 722 | | |
| 723 | | Main.map.statusLine.setAngle(angle); |
| 724 | | Main.map.statusLine.setHeading(hdg); |
| 725 | | Main.map.statusLine.setDist(distance); |
| 726 | | // Now done in redrawIfRequired() |
| 727 | | //updateStatusLine(); |
| 728 | | } |
| 729 | | |
| | 865 | // This happens when nothing is selected, but we still want to highlight the "target node" |
| | 866 | if (mouseOnExistingNode == null && getCurrentDataSet().getSelected().size() == 0 |
| | 867 | && mousePos != null) { |
| | 868 | mouseOnExistingNode = Main.map.mapView.getNearestNode(mousePos, OsmPrimitive.isSelectablePredicate); |
| | 869 | } |
| | 870 | |
| | 871 | if (mouseOnExistingNode != null) { |
| | 872 | Main.map.mapView.setNewCursor(cursorJoinNode, this); |
| | 873 | // We also need this list for the statusbar help text |
| | 874 | oldHighlights.add(mouseOnExistingNode); |
| | 875 | if(drawTargetHighlight) { |
| | 876 | mouseOnExistingNode.setHighlighted(true); |
| | 877 | } |
| | 878 | return; |
| | 879 | } |
| | 880 | |
| | 881 | // Insert the node into all the nearby way segments |
| | 882 | if (mouseOnExistingWays.size() == 0) { |
| | 883 | Main.map.mapView.setNewCursor(cursor, this); |
| | 884 | return; |
| | 885 | } |
| | 886 | |
| | 887 | Main.map.mapView.setNewCursor(cursorJoinWay, this); |
| | 888 | |
| | 889 | // We also need this list for the statusbar help text |
| | 890 | oldHighlights.addAll(mouseOnExistingWays); |
| | 891 | if (!drawTargetHighlight) return; |
| | 892 | for (Way w : mouseOnExistingWays) { |
| | 893 | w.setHighlighted(true); |
| | 894 | } |
| | 895 | } |
| | 896 | |
| | 897 | /** |
| | 898 | * Removes target highlighting from primitives |
| | 899 | */ |
| | 900 | private void removeHighlighting() { |
| | 901 | for(OsmPrimitive prim : oldHighlights) { |
| | 902 | prim.setHighlighted(false); |
| | 903 | } |
| | 904 | oldHighlights = new HashSet<OsmPrimitive>(); |
| | 905 | } |
| | 906 | |
| | 1056 | private class SnapHelper { |
| | 1057 | private boolean active; |
| | 1058 | private boolean fixed; |
| | 1059 | EastNorth dir2; |
| | 1060 | EastNorth projected; |
| | 1061 | String labelText; |
| | 1062 | double lastAngle; |
| | 1063 | |
| | 1064 | double snapAngleStep; |
| | 1065 | double snapAngleTolerance; |
| | 1066 | |
| | 1067 | double pe,pn; // (pe,pn) - direction of snapping line |
| | 1068 | double e0,n0; // (e0,n0) - origin of snapping line |
| | 1069 | |
| | 1070 | public SnapHelper() { } |
| | 1071 | |
| | 1072 | private void init() { |
| | 1073 | reset(); fixed=false; snapOn=false; |
| | 1074 | snapAngleStep = Main.pref.getDouble("draw.anglesnap.step", 90.0); |
| | 1075 | snapAngleTolerance = Main.pref.getDouble("draw.anglesnap.tol", 20.0); |
| | 1076 | } |
| | 1077 | |
| | 1078 | private void reset() { |
| | 1079 | active=false; |
| | 1080 | dir2=null; projected=null; |
| | 1081 | labelText=null; |
| | 1082 | } |
| | 1083 | |
| | 1084 | private void draw(Graphics2D g2, MapView mv) { |
| | 1085 | if (!active) return; |
| | 1086 | g2.setColor(Color.ORANGE); |
| | 1087 | g2.setStroke(new BasicStroke(3, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND)); |
| | 1088 | GeneralPath b = new GeneralPath(); |
| | 1089 | Point p1=mv.getPoint(currentBaseNode); |
| | 1090 | Point p2=mv.getPoint(dir2); |
| | 1091 | Point p3=mv.getPoint(projected); |
| | 1092 | |
| | 1093 | b.moveTo(p1.x,p1.y); |
| | 1094 | b.lineTo(p2.x,p2.y); |
| | 1095 | g2.draw(b); |
| | 1096 | g2.drawString(labelText, p3.x-5, p3.y+20); |
| | 1097 | g2.setStroke(new BasicStroke(3, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND)); |
| | 1098 | g2.drawOval(p3.x-5, p3.y-5, 10, 10); // projected point |
| | 1099 | } |
| | 1100 | |
| | 1101 | /* If mouse position is close to line at 15-30-45-... angle, remembers this direction |
| | 1102 | */ |
| | 1103 | private void checkAngleSnapping(EastNorth currentEN, double angle) { |
| | 1104 | if (previousNode==null) return; |
| | 1105 | if (fixed) angle = lastAngle; // if direction is fixed |
| | 1106 | double nearestAngle = snapAngleStep*Math.round(angle/snapAngleStep); |
| | 1107 | // 95->90, 50->90, 40->0, 280->270, 340->360 |
| | 1108 | |
| | 1109 | if (Math.abs(nearestAngle-180)>1e-3 && Math.abs(nearestAngle-angle)<snapAngleTolerance) { |
| | 1110 | if (Math.abs(nearestAngle-360)<1e-3) nearestAngle=0; |
| | 1111 | active=true; |
| | 1112 | if (fixed) labelText = String.format("%d FIX", (int) nearestAngle); |
| | 1113 | else labelText = String.format("%d", (int) nearestAngle); |
| | 1114 | EastNorth prev = previousNode.getEastNorth(); |
| | 1115 | EastNorth p0 = currentBaseNode.getEastNorth(); |
| | 1116 | |
| | 1117 | double de,dn,l, phi; |
| | 1118 | e0=p0.east(); n0=p0.north(); |
| | 1119 | de = e0-prev.east(); |
| | 1120 | dn = n0-prev.north(); |
| | 1121 | l=Math.hypot(de, dn); |
| | 1122 | de/=l; dn/=l; |
| | 1123 | |
| | 1124 | phi=nearestAngle*Math.PI/180; |
| | 1125 | // (pe,pn) - direction of snapping line |
| | 1126 | pe = de*Math.cos(phi) + dn*Math.sin(phi); |
| | 1127 | pn = -de*Math.sin(phi) + dn*Math.cos(phi); |
| | 1128 | double scale = 20*Main.map.mapView.getDist100Pixel(); |
| | 1129 | dir2 = new EastNorth( e0+scale*pe, n0+scale*pn); |
| | 1130 | lastAngle = nearestAngle; |
| | 1131 | getSnapPoint(currentEN); |
| | 1132 | } else { |
| | 1133 | reset(); |
| | 1134 | } |
| | 1135 | } |
| | 1136 | |
| | 1137 | private EastNorth getSnapPoint(EastNorth p) { |
| | 1138 | if (!active) return p; |
| | 1139 | double de=p.east()-e0; |
| | 1140 | double dn=p.north()-n0; |
| | 1141 | double l = de*pe+dn*pn; |
| | 1142 | if (l<1e-5) {active=false; return p; } // do not go backward! |
| | 1143 | return projected = new EastNorth(e0+l*pe, n0+l*pn); |
| | 1144 | } |
| | 1145 | |
| | 1146 | private void fixDirection() { |
| | 1147 | if (active) { |
| | 1148 | fixed=true; |
| | 1149 | } |
| | 1150 | } |
| | 1151 | |
| | 1152 | private void resetDirection() { |
| | 1153 | lastAngle=0; |
| | 1154 | } |
| | 1155 | |
| | 1156 | private void nextSnapMode() { |
| | 1157 | if (snapOn) { |
| | 1158 | if (fixed) { snapOn=false; fixed=false; } |
| | 1159 | else fixDirection(); |
| | 1160 | } else { |
| | 1161 | snapOn=true; |
| | 1162 | fixed=false; |
| | 1163 | } |
| | 1164 | } |
| | 1165 | |
| | 1166 | private boolean isActive() { |
| | 1167 | return active; |
| | 1168 | } |
| | 1169 | } |