| | 103 | private void insertNodeIntoAllNearbySegments(List<WaySegment> wss, Node n, Collection<OsmPrimitive> newSelection, Collection<Command> cmds, ArrayList<Way> replacedWays, ArrayList<Way> reuseWays) { |
| | 104 | Map<Way, List<Integer>> insertPoints = new HashMap<Way, List<Integer>>(); |
| | 105 | for (WaySegment ws : wss) { |
| | 106 | List<Integer> is; |
| | 107 | if (insertPoints.containsKey(ws.way)) { |
| | 108 | is = insertPoints.get(ws.way); |
| | 109 | } else { |
| | 110 | is = new ArrayList<Integer>(); |
| | 111 | insertPoints.put(ws.way, is); |
| | 112 | } |
| | 113 | |
| | 114 | is.add(ws.lowerIndex); |
| | 115 | } |
| | 116 | |
| | 117 | Set<Pair<Node,Node>> segSet = new HashSet<Pair<Node,Node>>(); |
| | 118 | |
| | 119 | for (Map.Entry<Way, List<Integer>> insertPoint : insertPoints.entrySet()) { |
| | 120 | Way w = insertPoint.getKey(); |
| | 121 | List<Integer> is = insertPoint.getValue(); |
| | 122 | |
| | 123 | Way wnew = new Way(w); |
| | 124 | |
| | 125 | pruneSuccsAndReverse(is); |
| | 126 | for (int i : is) { |
| | 127 | segSet.add( |
| | 128 | Pair.sort(new Pair<Node,Node>(w.getNode(i), w.getNode(i+1)))); |
| | 129 | } |
| | 130 | for (int i : is) { |
| | 131 | wnew.addNode(i + 1, n); |
| | 132 | } |
| | 133 | |
| | 134 | // If ALT is pressed, a new way should be created and that new way should get |
| | 135 | // selected. This works everytime unless the ways the nodes get inserted into |
| | 136 | // are already selected. This is the case when creating a self-overlapping way |
| | 137 | // but pressing ALT prevents this. Therefore we must de-select the way manually |
| | 138 | // here so /only/ the new way will be selected after this method finishes. |
| | 139 | if(alt) { |
| | 140 | newSelection.add(insertPoint.getKey()); |
| | 141 | } |
| | 142 | |
| | 143 | cmds.add(new ChangeCommand(insertPoint.getKey(), wnew)); |
| | 144 | replacedWays.add(insertPoint.getKey()); |
| | 145 | reuseWays.add(wnew); |
| | 146 | } |
| | 147 | |
| | 148 | adjustNode(segSet, n); |
| | 149 | } |
| | 150 | |
| 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 | | |
| 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); |
| | 357 | insertNodeIntoAllNearbySegments(wss, n, newSelection, cmds, replacedWays, reuseWays); |
| 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 | | } |
| 394 | | } |
| 395 | | |
| | 640 | determineCurrentBaseNodeAndPreviousNode(selection); |
| | 641 | if (previousNode == null) snapHelper.reset(); |
| | 642 | |
| | 643 | if (currentBaseNode == null || currentBaseNode == currentMouseNode) |
| | 644 | return; // Don't create zero length way segments. |
| | 645 | |
| | 646 | // find out the distance, in metres, between the base point and the mouse cursor |
| | 647 | LatLon mouseLatLon = mv.getProjection().eastNorth2latlon(currentMouseEastNorth); |
| | 648 | distance = currentBaseNode.getCoor().greatCircleDistance(mouseLatLon); |
| | 649 | |
| | 650 | double hdg = Math.toDegrees(currentBaseNode.getEastNorth() |
| | 651 | .heading(currentMouseEastNorth)); |
| | 652 | if (previousNode != null) { |
| | 653 | angle = hdg - Math.toDegrees(previousNode.getEastNorth() |
| | 654 | .heading(currentBaseNode.getEastNorth())); |
| | 655 | angle += angle < 0 ? 360 : 0; |
| | 656 | } |
| | 657 | |
| | 658 | if (snapOn) snapHelper.checkAngleSnapping(currentMouseEastNorth,angle); |
| | 659 | |
| | 660 | Main.map.statusLine.setAngle(angle); |
| | 661 | Main.map.statusLine.setHeading(hdg); |
| | 662 | Main.map.statusLine.setDist(distance); |
| | 663 | // Now done in redrawIfRequired() |
| | 664 | //updateStatusLine(); |
| | 665 | } |
| | 666 | private void determineCurrentBaseNodeAndPreviousNode(Collection<OsmPrimitive> selection) { |
| | 667 | Node selectedNode = null; |
| | 668 | 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 | // This happens when nothing is selected, but we still want to highlight the "target node" |
| | 715 | if (mouseOnExistingNode == null && getCurrentDataSet().getSelected().size() == 0 |
| | 716 | && mousePos != null) { |
| | 717 | mouseOnExistingNode = Main.map.mapView.getNearestNode(mousePos, OsmPrimitive.isSelectablePredicate); |
| | 718 | } |
| 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; |
| | 720 | if (mouseOnExistingNode != null) { |
| | 721 | Main.map.mapView.setNewCursor(cursorJoinNode, this); |
| | 722 | // We also need this list for the statusbar help text |
| | 723 | oldHighlights.add(mouseOnExistingNode); |
| | 724 | if(drawTargetHighlight) { |
| | 725 | mouseOnExistingNode.setHighlighted(true); |
| | 1033 | private class SnapHelper { |
| | 1034 | private boolean active; |
| | 1035 | EastNorth dir2; |
| | 1036 | EastNorth projected; |
| | 1037 | String labelText; |
| | 1038 | |
| | 1039 | |
| | 1040 | double pe,pn; // (pe,pn) - direction of snapping line |
| | 1041 | double e0,n0; // (e0,n0) - origin of snapping line |
| | 1042 | |
| | 1043 | |
| | 1044 | |
| | 1045 | public SnapHelper() { } |
| | 1046 | |
| | 1047 | private void reset() { |
| | 1048 | active=false; |
| | 1049 | dir2=null; projected=null; |
| | 1050 | labelText=null; |
| | 1051 | } |
| | 1052 | |
| | 1053 | private void draw(Graphics2D g2, MapView mv) { |
| | 1054 | if (!active) return; |
| | 1055 | g2.setColor(Color.ORANGE); |
| | 1056 | g2.setStroke(new BasicStroke(3, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND)); |
| | 1057 | GeneralPath b = new GeneralPath(); |
| | 1058 | Point p1=mv.getPoint(currentBaseNode); |
| | 1059 | Point p2=mv.getPoint(dir2); |
| | 1060 | Point p3=mv.getPoint(projected); |
| | 1061 | |
| | 1062 | b.moveTo(p1.x,p1.y); |
| | 1063 | b.lineTo(p2.x,p2.y); |
| | 1064 | g2.draw(b); |
| | 1065 | g2.drawString(labelText, p3.x-5, p3.y+20); |
| | 1066 | g2.setStroke(new BasicStroke(3, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND)); |
| | 1067 | g2.drawOval(p3.x-5, p3.y-5, 10, 10); // projected point |
| | 1068 | } |
| | 1069 | |
| | 1070 | /* If mouse position is close to line at 15-30-45-... angle, remembers this direction |
| | 1071 | */ |
| | 1072 | private void checkAngleSnapping(EastNorth currrentEN, double angle) { |
| | 1073 | if (previousNode==null) return; |
| | 1074 | |
| | 1075 | double nearestAngle = 15*Math.round(angle/15); |
| | 1076 | // 95->90, 50->90, 40->0, 280->270, 340->360 |
| | 1077 | if (nearestAngle!=180 && Math.abs(nearestAngle-angle)<7) { |
| | 1078 | active=true; |
| | 1079 | labelText = String.format("%d", (int) nearestAngle); |
| | 1080 | EastNorth prev = previousNode.getEastNorth(); |
| | 1081 | EastNorth p0 = currentBaseNode.getEastNorth(); |
| | 1082 | |
| | 1083 | double de,dn,l, phi; |
| | 1084 | e0=p0.east(); n0=p0.north(); |
| | 1085 | de = e0-prev.east(); |
| | 1086 | dn = n0-prev.north(); |
| | 1087 | l=Math.hypot(de, dn); |
| | 1088 | de/=l; dn/=l; |
| | 1089 | |
| | 1090 | phi=nearestAngle*Math.PI/180; |
| | 1091 | // (pe,pn) - direction of snapping line |
| | 1092 | pe = de*Math.cos(phi) + dn*Math.sin(phi); |
| | 1093 | pn = -de*Math.sin(phi) + dn*Math.cos(phi); |
| | 1094 | double scale = 20*Main.map.mapView.getDist100Pixel(); |
| | 1095 | dir2 = new EastNorth( e0+scale*pe, n0+scale*pn); |
| | 1096 | getSnapPoint(currrentEN); |
| | 1097 | } else { |
| | 1098 | reset(); |
| | 1099 | } |
| | 1100 | } |
| | 1101 | |
| | 1102 | private EastNorth getSnapPoint(EastNorth p) { |
| | 1103 | if (!active) return p; |
| | 1104 | double de=p.east()-e0; |
| | 1105 | double dn=p.north()-n0; |
| | 1106 | double l = de*pe+dn*pn; |
| | 1107 | return projected = new EastNorth(e0+l*pe, n0+l*pn); |
| | 1108 | } |
| | 1109 | } |
| | 1110 | |