| 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 | | } |
| | 323 | // Insert the node into all the nearby way segments |
| | 324 | List<WaySegment> wss = Main.map.mapView.getNearestWaySegments( |
| | 325 | Main.map.mapView.getPoint(n), OsmPrimitive.isSelectablePredicate); |
| | 326 | insertNodeIntoAllNearbySegments(wss, n, newSelection, cmds, replacedWays, reuseWays); |
| | 327 | } |
| | 659 | determineCurrentBaseNodeAndPreviousNode(selection); |
| | 660 | if (previousNode == null) snapHelper.noSnapNow(); |
| | 661 | |
| | 662 | if (currentBaseNode == null || currentBaseNode == currentMouseNode) |
| | 663 | return; // Don't create zero length way segments. |
| | 664 | |
| | 665 | // find out the distance, in metres, between the base point and the mouse cursor |
| | 666 | LatLon mouseLatLon = mv.getProjection().eastNorth2latlon(currentMouseEastNorth); |
| | 667 | distance = currentBaseNode.getCoor().greatCircleDistance(mouseLatLon); |
| | 668 | |
| | 669 | double hdg = Math.toDegrees(currentBaseNode.getEastNorth() |
| | 670 | .heading(currentMouseEastNorth)); |
| | 671 | if (previousNode != null) { |
| | 672 | angle = hdg - Math.toDegrees(previousNode.getEastNorth() |
| | 673 | .heading(currentBaseNode.getEastNorth())); |
| | 674 | angle += angle < 0 ? 360 : 0; |
| | 675 | } |
| | 676 | |
| | 677 | if (snapOn) snapHelper.checkAngleSnapping(currentMouseEastNorth,angle); |
| | 678 | |
| | 679 | Main.map.statusLine.setAngle(angle); |
| | 680 | Main.map.statusLine.setHeading(hdg); |
| | 681 | Main.map.statusLine.setDist(distance); |
| | 682 | // Now done in redrawIfRequired() |
| | 683 | //updateStatusLine(); |
| | 684 | } |
| | 685 | |
| | 686 | |
| | 687 | /** |
| | 688 | * Helper function that sets fields currentBaseNode and previousNode |
| | 689 | * @param selection |
| | 690 | * uses also lastUsedNode field |
| | 691 | */ |
| | 692 | private void determineCurrentBaseNodeAndPreviousNode(Collection<OsmPrimitive> selection) { |
| | 693 | Node selectedNode = null; |
| | 694 | Way selectedWay = null; |
| | 873 | // This happens when nothing is selected, but we still want to highlight the "target node" |
| | 874 | if (mouseOnExistingNode == null && getCurrentDataSet().getSelected().size() == 0 |
| | 875 | && mousePos != null) { |
| | 876 | mouseOnExistingNode = Main.map.mapView.getNearestNode(mousePos, OsmPrimitive.isSelectablePredicate); |
| | 877 | } |
| | 878 | |
| | 879 | if (mouseOnExistingNode != null) { |
| | 880 | Main.map.mapView.setNewCursor(cursorJoinNode, this); |
| | 881 | // We also need this list for the statusbar help text |
| | 882 | oldHighlights.add(mouseOnExistingNode); |
| | 883 | if(drawTargetHighlight) { |
| | 884 | mouseOnExistingNode.setHighlighted(true); |
| | 885 | } |
| | 886 | return; |
| | 887 | } |
| | 888 | |
| | 889 | // Insert the node into all the nearby way segments |
| | 890 | if (mouseOnExistingWays.size() == 0) { |
| | 891 | Main.map.mapView.setNewCursor(cursor, this); |
| | 892 | return; |
| | 893 | } |
| | 894 | |
| | 895 | Main.map.mapView.setNewCursor(cursorJoinWay, this); |
| | 896 | |
| | 897 | // We also need this list for the statusbar help text |
| | 898 | oldHighlights.addAll(mouseOnExistingWays); |
| | 899 | if (!drawTargetHighlight) return; |
| | 900 | for (Way w : mouseOnExistingWays) { |
| | 901 | w.setHighlighted(true); |
| | 902 | } |
| | 903 | } |
| | 904 | |
| | 905 | /** |
| | 906 | * Removes target highlighting from primitives |
| | 907 | */ |
| | 908 | private void removeHighlighting() { |
| | 909 | for(OsmPrimitive prim : oldHighlights) { |
| | 910 | prim.setHighlighted(false); |
| | 911 | } |
| | 912 | oldHighlights = new HashSet<OsmPrimitive>(); |
| | 913 | } |
| | 914 | |
| | 1066 | private class SnapHelper { |
| | 1067 | private boolean active; // snapping is activa for current mouse position |
| | 1068 | private boolean fixed; // snap angle is fixed |
| | 1069 | private boolean absoluteFix; // snap angle is absolute |
| | 1070 | EastNorth dir2; |
| | 1071 | EastNorth projected; |
| | 1072 | String labelText; |
| | 1073 | double lastAngle; |
| | 1074 | |
| | 1075 | double snapAngles[]; |
| | 1076 | double snapAngleTolerance; |
| | 1077 | |
| | 1078 | double pe,pn; // (pe,pn) - direction of snapping line |
| | 1079 | double e0,n0; // (e0,n0) - origin of snapping line |
| | 1080 | |
| | 1081 | final String fixFmt="%d "+tr("FIX"); |
| | 1082 | Color snapHelperColor; |
| | 1083 | private Stroke normalStroke; |
| | 1084 | private Stroke helperStroke; |
| | 1085 | |
| | 1086 | private void init() { |
| | 1087 | snapOn=false; |
| | 1088 | fixed=false; absoluteFix=false; |
| | 1089 | |
| | 1090 | Collection<String> angles = Main.pref.getCollection("draw.anglesnap.angles", Arrays.asList("0","90","270")); |
| | 1091 | snapAngles = new double[angles.size()]; |
| | 1092 | int i=0; |
| | 1093 | for (String s: angles) { |
| | 1094 | try { |
| | 1095 | snapAngles[i] = Double.parseDouble(s); |
| | 1096 | } catch (NumberFormatException e) { |
| | 1097 | System.err.println("Warning: incorrect number in draw.anglesnap.angles preferences: "+s); |
| | 1098 | snapAngles[i]=0; |
| | 1099 | } |
| | 1100 | i++; |
| | 1101 | } |
| | 1102 | snapAngleTolerance = Main.pref.getDouble("draw.anglesnap.tolerance", 10.0); |
| | 1103 | |
| | 1104 | normalStroke = new BasicStroke(3, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND); |
| | 1105 | snapHelperColor = Main.pref.getColor("draw.anglesnap.color", Color.ORANGE); |
| | 1106 | |
| | 1107 | float dash1[] = { 4.0f }; |
| | 1108 | helperStroke = new BasicStroke(1.0f, BasicStroke.CAP_BUTT, |
| | 1109 | BasicStroke.JOIN_MITER, 10.0f, dash1, 0.0f); |
| | 1110 | |
| | 1111 | } |
| | 1112 | |
| | 1113 | private void noSnapNow() { |
| | 1114 | active=false; |
| | 1115 | dir2=null; projected=null; |
| | 1116 | labelText=null; |
| | 1117 | } |
| | 1118 | |
| | 1119 | private void draw(Graphics2D g2, MapView mv) { |
| | 1120 | if (!active) return; |
| | 1121 | Point p1=mv.getPoint(currentBaseNode); |
| | 1122 | Point p2=mv.getPoint(dir2); |
| | 1123 | Point p3=mv.getPoint(projected); |
| | 1124 | GeneralPath b; |
| | 1125 | |
| | 1126 | g2.setColor(snapHelperColor); |
| | 1127 | g2.setStroke(helperStroke); |
| | 1128 | |
| | 1129 | b = new GeneralPath(); |
| | 1130 | if (absoluteFix) { |
| | 1131 | b.moveTo(p2.x,p2.y); |
| | 1132 | b.lineTo(2*p1.x-p2.x,2*p1.y-p2.y); // bi-directional line |
| | 1133 | } else { |
| | 1134 | b.moveTo(p2.x,p2.y); |
| | 1135 | b.lineTo(p3.x,p3.y); |
| | 1136 | } |
| | 1137 | g2.draw(b); |
| | 1138 | |
| | 1139 | g2.setColor(selectedColor); |
| | 1140 | g2.setStroke(normalStroke); |
| | 1141 | b = new GeneralPath(); |
| | 1142 | b.moveTo(p1.x,p1.y); |
| | 1143 | b.lineTo(p3.x,p3.y); |
| | 1144 | g2.draw(b); |
| | 1145 | |
| | 1146 | g2.drawString(labelText, p3.x-5, p3.y+20); |
| | 1147 | g2.setStroke(normalStroke); |
| | 1148 | g2.drawOval(p3.x-5, p3.y-5, 10, 10); // projected point |
| | 1149 | |
| | 1150 | g2.setColor(snapHelperColor); |
| | 1151 | g2.setStroke(helperStroke); |
| | 1152 | |
| | 1153 | } |
| | 1154 | |
| | 1155 | private double getAngleDelta(double a, double b) { |
| | 1156 | double delta = Math.abs(a-b); |
| | 1157 | if (delta>180) return 360-delta; else return delta; |
| | 1158 | } |
| | 1159 | |
| | 1160 | /* If mouse position is close to line at 15-30-45-... angle, remembers this direction |
| | 1161 | */ |
| | 1162 | private void checkAngleSnapping(EastNorth currentEN, double angle) { |
| | 1163 | if (previousNode==null) return; |
| | 1164 | |
| | 1165 | double nearestAngle; |
| | 1166 | if (fixed) { |
| | 1167 | nearestAngle = lastAngle; // if direction is fixed |
| | 1168 | active=true; |
| | 1169 | } else { |
| | 1170 | nearestAngle = getNearestAngle(angle); |
| | 1171 | lastAngle = nearestAngle; |
| | 1172 | active = Math.abs(nearestAngle-180)>1e-3 && getAngleDelta(nearestAngle,angle)<snapAngleTolerance; |
| | 1173 | } |
| | 1174 | |
| | 1175 | if (active) { |
| | 1176 | double de,dn,l, phi; |
| | 1177 | |
| | 1178 | EastNorth prev = previousNode.getEastNorth(); |
| | 1179 | EastNorth p0 = currentBaseNode.getEastNorth(); |
| | 1180 | |
| | 1181 | e0=p0.east(); n0=p0.north(); |
| | 1182 | |
| | 1183 | if (fixed) { |
| | 1184 | if (absoluteFix) labelText = "="; |
| | 1185 | else labelText = String.format(fixFmt, (int) nearestAngle); |
| | 1186 | } else labelText = String.format("%d", (int) nearestAngle); |
| | 1187 | |
| | 1188 | if (absoluteFix) { |
| | 1189 | de=0; dn=1; |
| | 1190 | } else { |
| | 1191 | de = e0-prev.east(); |
| | 1192 | dn = n0-prev.north(); |
| | 1193 | l=Math.hypot(de, dn); |
| | 1194 | de/=l; dn/=l; |
| | 1195 | } |
| | 1196 | |
| | 1197 | phi=nearestAngle*Math.PI/180; |
| | 1198 | // (pe,pn) - direction of snapping line |
| | 1199 | pe = de*Math.cos(phi) + dn*Math.sin(phi); |
| | 1200 | pn = -de*Math.sin(phi) + dn*Math.cos(phi); |
| | 1201 | double scale = 20*Main.map.mapView.getDist100Pixel(); |
| | 1202 | dir2 = new EastNorth( e0+scale*pe, n0+scale*pn); |
| | 1203 | getSnapPoint(currentEN); |
| | 1204 | } else { |
| | 1205 | noSnapNow(); |
| | 1206 | } |
| | 1207 | } |
| | 1208 | |
| | 1209 | private EastNorth getSnapPoint(EastNorth p) { |
| | 1210 | if (!active) return p; |
| | 1211 | double de=p.east()-e0; |
| | 1212 | double dn=p.north()-n0; |
| | 1213 | double l = de*pe+dn*pn; |
| | 1214 | if (!absoluteFix && l<1e-5) {active=false; return p; } // do not go backward! |
| | 1215 | return projected = new EastNorth(e0+l*pe, n0+l*pn); |
| | 1216 | } |
| | 1217 | |
| | 1218 | private void fixDirection() { |
| | 1219 | if (active) { |
| | 1220 | fixed=true; |
| | 1221 | } |
| | 1222 | } |
| | 1223 | |
| | 1224 | private void unsetFixedMode() { |
| | 1225 | fixed=false; absoluteFix=false; |
| | 1226 | lastAngle=0; |
| | 1227 | active=false; |
| | 1228 | } |
| | 1229 | |
| | 1230 | private void nextSnapMode() { |
| | 1231 | if (snapOn) { |
| | 1232 | if (fixed) { snapOn=false; unsetFixedMode(); } |
| | 1233 | else fixDirection(); |
| | 1234 | } else { |
| | 1235 | snapOn=true; |
| | 1236 | unsetFixedMode(); |
| | 1237 | } |
| | 1238 | } |
| | 1239 | |
| | 1240 | private boolean isActive() { |
| | 1241 | return active; |
| | 1242 | } |
| | 1243 | |
| | 1244 | private double getNearestAngle(double angle) { |
| | 1245 | double delta,minDelta=1e5, bestAngle=0.0; |
| | 1246 | for (int i=0; i<snapAngles.length; i++) { |
| | 1247 | delta = getAngleDelta(angle,snapAngles[i]); |
| | 1248 | if (delta<minDelta) { |
| | 1249 | minDelta=delta; |
| | 1250 | bestAngle=snapAngles[i]; |
| | 1251 | } |
| | 1252 | } |
| | 1253 | if (Math.abs(bestAngle-360)<1e-3) bestAngle=0; |
| | 1254 | return bestAngle; |
| | 1255 | } |
| | 1256 | |
| | 1257 | private void fixToSegment(WaySegment seg) { |
| | 1258 | if (seg==null) return; |
| | 1259 | double hdg = seg.getFirstNode().getEastNorth().heading(seg.getSecondNode().getEastNorth()); |
| | 1260 | hdg=Math.toDegrees(hdg); |
| | 1261 | if (hdg<0) hdg+=360; |
| | 1262 | if (hdg>360) hdg=hdg-360; |
| | 1263 | System.out.println("fix to segment "+hdg); |
| | 1264 | |
| | 1265 | fixed=true; |
| | 1266 | absoluteFix=true; |
| | 1267 | lastAngle=hdg; |
| | 1268 | } |
| | 1269 | |
| | 1270 | |
| | 1271 | } |