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 | } |