[11495] | 1 | // License: GPL. For details, see LICENSE file.
|
---|
| 2 | package org.openstreetmap.josm.actions.mapmode;
|
---|
| 3 |
|
---|
| 4 | import static org.openstreetmap.josm.tools.I18n.tr;
|
---|
| 5 |
|
---|
| 6 | import java.awt.Graphics2D;
|
---|
| 7 | import java.awt.event.ActionEvent;
|
---|
| 8 | import java.awt.event.MouseEvent;
|
---|
| 9 | import java.awt.event.MouseListener;
|
---|
| 10 | import java.util.ArrayList;
|
---|
| 11 | import java.util.Arrays;
|
---|
| 12 | import java.util.Collection;
|
---|
| 13 | import java.util.Comparator;
|
---|
| 14 | import java.util.stream.DoubleStream;
|
---|
| 15 |
|
---|
| 16 | import javax.swing.AbstractAction;
|
---|
| 17 | import javax.swing.JCheckBoxMenuItem;
|
---|
| 18 | import javax.swing.JPopupMenu;
|
---|
| 19 |
|
---|
| 20 | import org.openstreetmap.josm.Main;
|
---|
| 21 | import org.openstreetmap.josm.data.coor.EastNorth;
|
---|
| 22 | import org.openstreetmap.josm.data.coor.LatLon;
|
---|
| 23 | import org.openstreetmap.josm.data.osm.DataSet;
|
---|
| 24 | import org.openstreetmap.josm.data.osm.Node;
|
---|
| 25 | import org.openstreetmap.josm.data.osm.Way;
|
---|
| 26 | import org.openstreetmap.josm.data.osm.WaySegment;
|
---|
[12630] | 27 | import org.openstreetmap.josm.gui.MainApplication;
|
---|
| 28 | import org.openstreetmap.josm.gui.MapView;
|
---|
[11495] | 29 | import org.openstreetmap.josm.gui.MapViewState;
|
---|
| 30 | import org.openstreetmap.josm.gui.MapViewState.MapViewPoint;
|
---|
| 31 | import org.openstreetmap.josm.gui.draw.MapViewPath;
|
---|
| 32 | import org.openstreetmap.josm.gui.draw.SymbolShape;
|
---|
| 33 | import org.openstreetmap.josm.gui.widgets.PopupMenuLauncher;
|
---|
[12620] | 34 | import org.openstreetmap.josm.tools.Logging;
|
---|
[12131] | 35 | import org.openstreetmap.josm.tools.Utils;
|
---|
[11495] | 36 |
|
---|
[12581] | 37 | /**
|
---|
| 38 | * Class that enables the user to draw way segments in angles of exactly 30, 45,
|
---|
| 39 | * 60, 90 degrees.
|
---|
| 40 | *
|
---|
| 41 | * With enabled snapping, the new way node will be projected onto the helper line
|
---|
| 42 | * that indicates a certain fixed angle relative to the previous segment.
|
---|
| 43 | */
|
---|
[11495] | 44 | class DrawSnapHelper {
|
---|
| 45 |
|
---|
| 46 | private final DrawAction drawAction;
|
---|
| 47 |
|
---|
| 48 | /**
|
---|
| 49 | * Constructs a new {@code SnapHelper}.
|
---|
| 50 | * @param drawAction enclosing DrawAction
|
---|
| 51 | */
|
---|
| 52 | DrawSnapHelper(DrawAction drawAction) {
|
---|
| 53 | this.drawAction = drawAction;
|
---|
| 54 | this.anglePopupListener = new PopupMenuLauncher(new AnglePopupMenu(this)) {
|
---|
| 55 | @Override
|
---|
| 56 | public void mouseClicked(MouseEvent e) {
|
---|
| 57 | super.mouseClicked(e);
|
---|
| 58 | if (e.getButton() == MouseEvent.BUTTON1) {
|
---|
| 59 | toggleSnapping();
|
---|
| 60 | drawAction.updateStatusLine();
|
---|
| 61 | }
|
---|
| 62 | }
|
---|
| 63 | };
|
---|
| 64 | }
|
---|
| 65 |
|
---|
| 66 | private static final String DRAW_ANGLESNAP_ANGLES = "draw.anglesnap.angles";
|
---|
| 67 |
|
---|
[11502] | 68 | private static final class RepeatedAction extends AbstractAction {
|
---|
| 69 | RepeatedAction(DrawSnapHelper snapHelper) {
|
---|
| 70 | super(tr("Toggle snapping by {0}", snapHelper.drawAction.getShortcut().getKeyText()));
|
---|
| 71 | }
|
---|
| 72 |
|
---|
| 73 | @Override
|
---|
| 74 | public void actionPerformed(ActionEvent e) {
|
---|
| 75 | boolean sel = ((JCheckBoxMenuItem) e.getSource()).getState();
|
---|
| 76 | DrawAction.USE_REPEATED_SHORTCUT.put(sel);
|
---|
| 77 | }
|
---|
| 78 | }
|
---|
| 79 |
|
---|
| 80 | private static final class HelperAction extends AbstractAction {
|
---|
| 81 | private final transient DrawSnapHelper snapHelper;
|
---|
| 82 |
|
---|
| 83 | HelperAction(DrawSnapHelper snapHelper) {
|
---|
| 84 | super(tr("Show helper geometry"));
|
---|
| 85 | this.snapHelper = snapHelper;
|
---|
| 86 | }
|
---|
| 87 |
|
---|
| 88 | @Override
|
---|
| 89 | public void actionPerformed(ActionEvent e) {
|
---|
| 90 | boolean sel = ((JCheckBoxMenuItem) e.getSource()).getState();
|
---|
| 91 | DrawAction.DRAW_CONSTRUCTION_GEOMETRY.put(sel);
|
---|
| 92 | DrawAction.SHOW_PROJECTED_POINT.put(sel);
|
---|
| 93 | DrawAction.SHOW_ANGLE.put(sel);
|
---|
| 94 | snapHelper.enableSnapping();
|
---|
| 95 | }
|
---|
| 96 | }
|
---|
| 97 |
|
---|
| 98 | private static final class ProjectionAction extends AbstractAction {
|
---|
| 99 | private final transient DrawSnapHelper snapHelper;
|
---|
| 100 |
|
---|
| 101 | ProjectionAction(DrawSnapHelper snapHelper) {
|
---|
| 102 | super(tr("Snap to node projections"));
|
---|
| 103 | this.snapHelper = snapHelper;
|
---|
| 104 | }
|
---|
| 105 |
|
---|
| 106 | @Override
|
---|
| 107 | public void actionPerformed(ActionEvent e) {
|
---|
| 108 | boolean sel = ((JCheckBoxMenuItem) e.getSource()).getState();
|
---|
| 109 | DrawAction.SNAP_TO_PROJECTIONS.put(sel);
|
---|
| 110 | snapHelper.enableSnapping();
|
---|
| 111 | }
|
---|
| 112 | }
|
---|
| 113 |
|
---|
| 114 | private static final class DisableAction extends AbstractAction {
|
---|
| 115 | private final transient DrawSnapHelper snapHelper;
|
---|
| 116 |
|
---|
| 117 | DisableAction(DrawSnapHelper snapHelper) {
|
---|
| 118 | super(tr("Disable"));
|
---|
| 119 | this.snapHelper = snapHelper;
|
---|
| 120 | }
|
---|
| 121 |
|
---|
| 122 | @Override
|
---|
| 123 | public void actionPerformed(ActionEvent e) {
|
---|
| 124 | snapHelper.saveAngles("180");
|
---|
| 125 | snapHelper.init();
|
---|
| 126 | snapHelper.enableSnapping();
|
---|
| 127 | }
|
---|
| 128 | }
|
---|
| 129 |
|
---|
| 130 | private static final class Snap90DegreesAction extends AbstractAction {
|
---|
| 131 | private final transient DrawSnapHelper snapHelper;
|
---|
| 132 |
|
---|
| 133 | Snap90DegreesAction(DrawSnapHelper snapHelper) {
|
---|
| 134 | super(tr("0,90,..."));
|
---|
| 135 | this.snapHelper = snapHelper;
|
---|
| 136 | }
|
---|
| 137 |
|
---|
| 138 | @Override
|
---|
| 139 | public void actionPerformed(ActionEvent e) {
|
---|
| 140 | snapHelper.saveAngles("0", "90", "180");
|
---|
| 141 | snapHelper.init();
|
---|
| 142 | snapHelper.enableSnapping();
|
---|
| 143 | }
|
---|
| 144 | }
|
---|
| 145 |
|
---|
| 146 | private static final class Snap45DegreesAction extends AbstractAction {
|
---|
| 147 | private final transient DrawSnapHelper snapHelper;
|
---|
| 148 |
|
---|
| 149 | Snap45DegreesAction(DrawSnapHelper snapHelper) {
|
---|
| 150 | super(tr("0,45,90,..."));
|
---|
| 151 | this.snapHelper = snapHelper;
|
---|
| 152 | }
|
---|
| 153 |
|
---|
| 154 | @Override
|
---|
| 155 | public void actionPerformed(ActionEvent e) {
|
---|
| 156 | snapHelper.saveAngles("0", "45", "90", "135", "180");
|
---|
| 157 | snapHelper.init();
|
---|
| 158 | snapHelper.enableSnapping();
|
---|
| 159 | }
|
---|
| 160 | }
|
---|
| 161 |
|
---|
| 162 | private static final class Snap30DegreesAction extends AbstractAction {
|
---|
| 163 | private final transient DrawSnapHelper snapHelper;
|
---|
| 164 |
|
---|
| 165 | Snap30DegreesAction(DrawSnapHelper snapHelper) {
|
---|
| 166 | super(tr("0,30,45,60,90,..."));
|
---|
| 167 | this.snapHelper = snapHelper;
|
---|
| 168 | }
|
---|
| 169 |
|
---|
| 170 | @Override
|
---|
| 171 | public void actionPerformed(ActionEvent e) {
|
---|
| 172 | snapHelper.saveAngles("0", "30", "45", "60", "90", "120", "135", "150", "180");
|
---|
| 173 | snapHelper.init();
|
---|
| 174 | snapHelper.enableSnapping();
|
---|
| 175 | }
|
---|
| 176 | }
|
---|
| 177 |
|
---|
[11495] | 178 | private static final class AnglePopupMenu extends JPopupMenu {
|
---|
| 179 |
|
---|
| 180 | private AnglePopupMenu(final DrawSnapHelper snapHelper) {
|
---|
[11534] | 181 | JCheckBoxMenuItem repeatedCb = new JCheckBoxMenuItem(new RepeatedAction(snapHelper));
|
---|
| 182 | JCheckBoxMenuItem helperCb = new JCheckBoxMenuItem(new HelperAction(snapHelper));
|
---|
| 183 | JCheckBoxMenuItem projectionCb = new JCheckBoxMenuItem(new ProjectionAction(snapHelper));
|
---|
[11495] | 184 |
|
---|
| 185 | helperCb.setState(DrawAction.DRAW_CONSTRUCTION_GEOMETRY.get());
|
---|
| 186 | projectionCb.setState(DrawAction.SNAP_TO_PROJECTIONS.get());
|
---|
| 187 | repeatedCb.setState(DrawAction.USE_REPEATED_SHORTCUT.get());
|
---|
| 188 | add(repeatedCb);
|
---|
| 189 | add(helperCb);
|
---|
| 190 | add(projectionCb);
|
---|
[11502] | 191 | add(new DisableAction(snapHelper));
|
---|
| 192 | add(new Snap90DegreesAction(snapHelper));
|
---|
| 193 | add(new Snap45DegreesAction(snapHelper));
|
---|
| 194 | add(new Snap30DegreesAction(snapHelper));
|
---|
[11495] | 195 | }
|
---|
| 196 | }
|
---|
| 197 |
|
---|
| 198 | private boolean snapOn; // snapping is turned on
|
---|
| 199 |
|
---|
| 200 | private boolean active; // snapping is active for current mouse position
|
---|
| 201 | private boolean fixed; // snap angle is fixed
|
---|
| 202 | private boolean absoluteFix; // snap angle is absolute
|
---|
| 203 |
|
---|
| 204 | EastNorth dir2;
|
---|
| 205 | private EastNorth projected;
|
---|
| 206 | private String labelText;
|
---|
| 207 | private double lastAngle;
|
---|
| 208 |
|
---|
| 209 | private double customBaseHeading = -1; // angle of base line, if not last segment)
|
---|
| 210 | private EastNorth segmentPoint1; // remembered first point of base segment
|
---|
| 211 | private EastNorth segmentPoint2; // remembered second point of base segment
|
---|
| 212 | private EastNorth projectionSource; // point that we are projecting to the line
|
---|
| 213 |
|
---|
| 214 | private double[] snapAngles;
|
---|
| 215 |
|
---|
| 216 | private double pe, pn; // (pe, pn) - direction of snapping line
|
---|
| 217 | private double e0, n0; // (e0, n0) - origin of snapping line
|
---|
| 218 |
|
---|
| 219 | private final String fixFmt = "%d "+tr("FIX");
|
---|
| 220 |
|
---|
| 221 | private JCheckBoxMenuItem checkBox;
|
---|
| 222 |
|
---|
| 223 | final MouseListener anglePopupListener;
|
---|
| 224 |
|
---|
| 225 | /**
|
---|
| 226 | * Set the initial state
|
---|
| 227 | */
|
---|
| 228 | public void init() {
|
---|
| 229 | snapOn = false;
|
---|
| 230 | checkBox.setState(snapOn);
|
---|
| 231 | fixed = false;
|
---|
| 232 | absoluteFix = false;
|
---|
| 233 |
|
---|
| 234 | computeSnapAngles();
|
---|
| 235 | Main.pref.addWeakKeyPreferenceChangeListener(DRAW_ANGLESNAP_ANGLES, e -> this.computeSnapAngles());
|
---|
| 236 | }
|
---|
| 237 |
|
---|
| 238 | private void computeSnapAngles() {
|
---|
[12841] | 239 | snapAngles = Main.pref.getList(DRAW_ANGLESNAP_ANGLES,
|
---|
[11495] | 240 | Arrays.asList("0", "30", "45", "60", "90", "120", "135", "150", "180"))
|
---|
| 241 | .stream()
|
---|
| 242 | .mapToDouble(DrawSnapHelper::parseSnapAngle)
|
---|
| 243 | .flatMap(s -> DoubleStream.of(s, 360-s))
|
---|
| 244 | .toArray();
|
---|
| 245 | }
|
---|
| 246 |
|
---|
| 247 | private static double parseSnapAngle(String string) {
|
---|
| 248 | try {
|
---|
| 249 | return Double.parseDouble(string);
|
---|
| 250 | } catch (NumberFormatException e) {
|
---|
[12620] | 251 | Logging.warn("Incorrect number in draw.anglesnap.angles preferences: {0}", string);
|
---|
[11495] | 252 | return 0;
|
---|
| 253 | }
|
---|
| 254 | }
|
---|
| 255 |
|
---|
| 256 | /**
|
---|
| 257 | * Save the snap angles
|
---|
| 258 | * @param angles The angles
|
---|
| 259 | */
|
---|
[11747] | 260 | public void saveAngles(String... angles) {
|
---|
[12841] | 261 | Main.pref.putList(DRAW_ANGLESNAP_ANGLES, Arrays.asList(angles));
|
---|
[11495] | 262 | }
|
---|
| 263 |
|
---|
| 264 | /**
|
---|
| 265 | * Sets the menu checkbox.
|
---|
| 266 | * @param checkBox menu checkbox
|
---|
| 267 | */
|
---|
| 268 | public void setMenuCheckBox(JCheckBoxMenuItem checkBox) {
|
---|
| 269 | this.checkBox = checkBox;
|
---|
| 270 | }
|
---|
| 271 |
|
---|
| 272 | /**
|
---|
| 273 | * Draw the snap hint line.
|
---|
| 274 | * @param g2 graphics
|
---|
| 275 | * @param mv MapView state
|
---|
| 276 | * @since 10874
|
---|
| 277 | */
|
---|
| 278 | public void drawIfNeeded(Graphics2D g2, MapViewState mv) {
|
---|
| 279 | if (!snapOn || !active)
|
---|
| 280 | return;
|
---|
| 281 | MapViewPoint p1 = mv.getPointFor(drawAction.getCurrentBaseNode());
|
---|
| 282 | MapViewPoint p2 = mv.getPointFor(dir2);
|
---|
| 283 | MapViewPoint p3 = mv.getPointFor(projected);
|
---|
| 284 | if (DrawAction.DRAW_CONSTRUCTION_GEOMETRY.get()) {
|
---|
| 285 | g2.setColor(DrawAction.SNAP_HELPER_COLOR.get());
|
---|
| 286 | g2.setStroke(DrawAction.HELPER_STROKE.get());
|
---|
| 287 |
|
---|
| 288 | MapViewPath b = new MapViewPath(mv);
|
---|
| 289 | b.moveTo(p2);
|
---|
| 290 | if (absoluteFix) {
|
---|
| 291 | b.lineTo(p2.interpolate(p1, 2)); // bi-directional line
|
---|
| 292 | } else {
|
---|
| 293 | b.lineTo(p3);
|
---|
| 294 | }
|
---|
| 295 | g2.draw(b);
|
---|
| 296 | }
|
---|
| 297 | if (projectionSource != null) {
|
---|
| 298 | g2.setColor(DrawAction.SNAP_HELPER_COLOR.get());
|
---|
| 299 | g2.setStroke(DrawAction.HELPER_STROKE.get());
|
---|
| 300 | MapViewPath b = new MapViewPath(mv);
|
---|
| 301 | b.moveTo(p3);
|
---|
| 302 | b.lineTo(projectionSource);
|
---|
| 303 | g2.draw(b);
|
---|
| 304 | }
|
---|
| 305 |
|
---|
| 306 | if (customBaseHeading >= 0) {
|
---|
| 307 | g2.setColor(DrawAction.HIGHLIGHT_COLOR.get());
|
---|
| 308 | g2.setStroke(DrawAction.HIGHLIGHT_STROKE.get());
|
---|
| 309 | MapViewPath b = new MapViewPath(mv);
|
---|
| 310 | b.moveTo(segmentPoint1);
|
---|
| 311 | b.lineTo(segmentPoint2);
|
---|
| 312 | g2.draw(b);
|
---|
| 313 | }
|
---|
| 314 |
|
---|
| 315 | g2.setColor(DrawAction.RUBBER_LINE_COLOR.get());
|
---|
| 316 | g2.setStroke(DrawAction.RUBBER_LINE_STROKE.get());
|
---|
| 317 | MapViewPath b = new MapViewPath(mv);
|
---|
| 318 | b.moveTo(p1);
|
---|
| 319 | b.lineTo(p3);
|
---|
| 320 | g2.draw(b);
|
---|
| 321 |
|
---|
| 322 | g2.drawString(labelText, (int) p3.getInViewX()-5, (int) p3.getInViewY()+20);
|
---|
| 323 | if (DrawAction.SHOW_PROJECTED_POINT.get()) {
|
---|
| 324 | g2.setStroke(DrawAction.RUBBER_LINE_STROKE.get());
|
---|
| 325 | g2.draw(new MapViewPath(mv).shapeAround(p3, SymbolShape.CIRCLE, 10)); // projected point
|
---|
| 326 | }
|
---|
| 327 |
|
---|
| 328 | g2.setColor(DrawAction.SNAP_HELPER_COLOR.get());
|
---|
| 329 | g2.setStroke(DrawAction.HELPER_STROKE.get());
|
---|
| 330 | }
|
---|
| 331 |
|
---|
| 332 | /**
|
---|
| 333 | * If mouse position is close to line at 15-30-45-... angle, remembers this direction
|
---|
| 334 | * @param currentEN Current position
|
---|
| 335 | * @param baseHeading The heading
|
---|
| 336 | * @param curHeading The current mouse heading
|
---|
| 337 | */
|
---|
| 338 | public void checkAngleSnapping(EastNorth currentEN, double baseHeading, double curHeading) {
|
---|
[12630] | 339 | MapView mapView = MainApplication.getMap().mapView;
|
---|
[11495] | 340 | EastNorth p0 = drawAction.getCurrentBaseNode().getEastNorth();
|
---|
| 341 | EastNorth snapPoint = currentEN;
|
---|
| 342 | double angle = -1;
|
---|
| 343 |
|
---|
| 344 | double activeBaseHeading = (customBaseHeading >= 0) ? customBaseHeading : baseHeading;
|
---|
| 345 |
|
---|
| 346 | if (snapOn && (activeBaseHeading >= 0)) {
|
---|
| 347 | angle = curHeading - activeBaseHeading;
|
---|
| 348 | if (angle < 0) {
|
---|
| 349 | angle += 360;
|
---|
| 350 | }
|
---|
| 351 | if (angle > 360) {
|
---|
| 352 | angle = 0;
|
---|
| 353 | }
|
---|
| 354 |
|
---|
| 355 | double nearestAngle;
|
---|
| 356 | if (fixed) {
|
---|
| 357 | nearestAngle = lastAngle; // if direction is fixed use previous angle
|
---|
| 358 | active = true;
|
---|
| 359 | } else {
|
---|
| 360 | nearestAngle = getNearestAngle(angle);
|
---|
| 361 | if (getAngleDelta(nearestAngle, angle) < DrawAction.SNAP_ANGLE_TOLERANCE.get()) {
|
---|
| 362 | active = customBaseHeading >= 0 || Math.abs(nearestAngle - 180) > 1e-3;
|
---|
| 363 | // if angle is to previous segment, exclude 180 degrees
|
---|
| 364 | lastAngle = nearestAngle;
|
---|
| 365 | } else {
|
---|
| 366 | active = false;
|
---|
| 367 | }
|
---|
| 368 | }
|
---|
| 369 |
|
---|
| 370 | if (active) {
|
---|
| 371 | double phi;
|
---|
| 372 | e0 = p0.east();
|
---|
| 373 | n0 = p0.north();
|
---|
| 374 | buildLabelText((nearestAngle <= 180) ? nearestAngle : (nearestAngle-360));
|
---|
| 375 |
|
---|
| 376 | phi = (nearestAngle + activeBaseHeading) * Math.PI / 180;
|
---|
| 377 | // (pe,pn) - direction of snapping line
|
---|
| 378 | pe = Math.sin(phi);
|
---|
| 379 | pn = Math.cos(phi);
|
---|
[12630] | 380 | double scale = 20 * mapView.getDist100Pixel();
|
---|
[11495] | 381 | dir2 = new EastNorth(e0 + scale * pe, n0 + scale * pn);
|
---|
| 382 | snapPoint = getSnapPoint(currentEN);
|
---|
| 383 | } else {
|
---|
| 384 | noSnapNow();
|
---|
| 385 | }
|
---|
| 386 | }
|
---|
| 387 |
|
---|
| 388 | // find out the distance, in metres, between the base point and projected point
|
---|
[12630] | 389 | LatLon mouseLatLon = mapView.getProjection().eastNorth2latlon(snapPoint);
|
---|
[11495] | 390 | double distance = this.drawAction.getCurrentBaseNode().getCoor().greatCircleDistance(mouseLatLon);
|
---|
[12131] | 391 | double hdg = Utils.toDegrees(p0.heading(snapPoint));
|
---|
[11495] | 392 | // heading of segment from current to calculated point, not to mouse position
|
---|
| 393 |
|
---|
| 394 | if (baseHeading >= 0) { // there is previous line segment with some heading
|
---|
| 395 | angle = hdg - baseHeading;
|
---|
| 396 | if (angle < 0) {
|
---|
| 397 | angle += 360;
|
---|
| 398 | }
|
---|
| 399 | if (angle > 360) {
|
---|
| 400 | angle = 0;
|
---|
| 401 | }
|
---|
| 402 | }
|
---|
| 403 | DrawAction.showStatusInfo(angle, hdg, distance, isSnapOn());
|
---|
| 404 | }
|
---|
| 405 |
|
---|
| 406 | private void buildLabelText(double nearestAngle) {
|
---|
| 407 | if (DrawAction.SHOW_ANGLE.get()) {
|
---|
| 408 | if (fixed) {
|
---|
| 409 | if (absoluteFix) {
|
---|
| 410 | labelText = "=";
|
---|
| 411 | } else {
|
---|
| 412 | labelText = String.format(fixFmt, (int) nearestAngle);
|
---|
| 413 | }
|
---|
| 414 | } else {
|
---|
| 415 | labelText = String.format("%d", (int) nearestAngle);
|
---|
| 416 | }
|
---|
| 417 | } else {
|
---|
| 418 | if (fixed) {
|
---|
| 419 | if (absoluteFix) {
|
---|
| 420 | labelText = "=";
|
---|
| 421 | } else {
|
---|
| 422 | labelText = String.format(tr("FIX"), 0);
|
---|
| 423 | }
|
---|
| 424 | } else {
|
---|
| 425 | labelText = "";
|
---|
| 426 | }
|
---|
| 427 | }
|
---|
| 428 | }
|
---|
| 429 |
|
---|
| 430 | /**
|
---|
| 431 | * Gets a snap point close to p. Stores the result for display.
|
---|
| 432 | * @param p The point
|
---|
| 433 | * @return The snap point close to p.
|
---|
| 434 | */
|
---|
| 435 | public EastNorth getSnapPoint(EastNorth p) {
|
---|
| 436 | if (!active)
|
---|
| 437 | return p;
|
---|
| 438 | double de = p.east()-e0;
|
---|
| 439 | double dn = p.north()-n0;
|
---|
| 440 | double l = de*pe+dn*pn;
|
---|
[12630] | 441 | double delta = MainApplication.getMap().mapView.getDist100Pixel()/20;
|
---|
[11495] | 442 | if (!absoluteFix && l < delta) {
|
---|
| 443 | active = false;
|
---|
| 444 | return p;
|
---|
| 445 | } // do not go backward!
|
---|
| 446 |
|
---|
| 447 | projectionSource = null;
|
---|
| 448 | if (DrawAction.SNAP_TO_PROJECTIONS.get()) {
|
---|
| 449 | DataSet ds = drawAction.getLayerManager().getEditDataSet();
|
---|
| 450 | Collection<Way> selectedWays = ds.getSelectedWays();
|
---|
| 451 | if (selectedWays.size() == 1) {
|
---|
| 452 | Way w = selectedWays.iterator().next();
|
---|
| 453 | Collection<EastNorth> pointsToProject = new ArrayList<>();
|
---|
| 454 | if (w.getNodesCount() < 1000) {
|
---|
| 455 | for (Node n: w.getNodes()) {
|
---|
| 456 | pointsToProject.add(n.getEastNorth());
|
---|
| 457 | }
|
---|
| 458 | }
|
---|
| 459 | if (customBaseHeading >= 0) {
|
---|
| 460 | pointsToProject.add(segmentPoint1);
|
---|
| 461 | pointsToProject.add(segmentPoint2);
|
---|
| 462 | }
|
---|
| 463 | EastNorth enOpt = null;
|
---|
| 464 | double dOpt = 1e5;
|
---|
| 465 | for (EastNorth en: pointsToProject) { // searching for besht projection
|
---|
| 466 | double l1 = (en.east()-e0)*pe+(en.north()-n0)*pn;
|
---|
| 467 | double d1 = Math.abs(l1-l);
|
---|
| 468 | if (d1 < delta && d1 < dOpt) {
|
---|
| 469 | l = l1;
|
---|
| 470 | enOpt = en;
|
---|
| 471 | dOpt = d1;
|
---|
| 472 | }
|
---|
| 473 | }
|
---|
| 474 | if (enOpt != null) {
|
---|
| 475 | projectionSource = enOpt;
|
---|
| 476 | }
|
---|
| 477 | }
|
---|
| 478 | }
|
---|
| 479 | projected = new EastNorth(e0+l*pe, n0+l*pn);
|
---|
| 480 | return projected;
|
---|
| 481 | }
|
---|
| 482 |
|
---|
| 483 | /**
|
---|
| 484 | * Disables snapping
|
---|
| 485 | */
|
---|
| 486 | void noSnapNow() {
|
---|
| 487 | active = false;
|
---|
| 488 | dir2 = null;
|
---|
| 489 | projected = null;
|
---|
| 490 | labelText = null;
|
---|
| 491 | }
|
---|
| 492 |
|
---|
| 493 | void setBaseSegment(WaySegment seg) {
|
---|
| 494 | if (seg == null) return;
|
---|
| 495 | segmentPoint1 = seg.getFirstNode().getEastNorth();
|
---|
| 496 | segmentPoint2 = seg.getSecondNode().getEastNorth();
|
---|
| 497 |
|
---|
| 498 | double hdg = segmentPoint1.heading(segmentPoint2);
|
---|
[12131] | 499 | hdg = Utils.toDegrees(hdg);
|
---|
[11495] | 500 | if (hdg < 0) {
|
---|
| 501 | hdg += 360;
|
---|
| 502 | }
|
---|
| 503 | if (hdg > 360) {
|
---|
| 504 | hdg -= 360;
|
---|
| 505 | }
|
---|
| 506 | customBaseHeading = hdg;
|
---|
| 507 | }
|
---|
| 508 |
|
---|
| 509 | /**
|
---|
| 510 | * Enable snapping.
|
---|
| 511 | */
|
---|
| 512 | void enableSnapping() {
|
---|
| 513 | snapOn = true;
|
---|
| 514 | checkBox.setState(snapOn);
|
---|
| 515 | customBaseHeading = -1;
|
---|
| 516 | unsetFixedMode();
|
---|
| 517 | }
|
---|
| 518 |
|
---|
| 519 | void toggleSnapping() {
|
---|
| 520 | snapOn = !snapOn;
|
---|
| 521 | checkBox.setState(snapOn);
|
---|
| 522 | customBaseHeading = -1;
|
---|
| 523 | unsetFixedMode();
|
---|
| 524 | }
|
---|
| 525 |
|
---|
| 526 | void setFixedMode() {
|
---|
| 527 | if (active) {
|
---|
| 528 | fixed = true;
|
---|
| 529 | }
|
---|
| 530 | }
|
---|
| 531 |
|
---|
| 532 | void unsetFixedMode() {
|
---|
| 533 | fixed = false;
|
---|
| 534 | absoluteFix = false;
|
---|
| 535 | lastAngle = 0;
|
---|
| 536 | active = false;
|
---|
| 537 | }
|
---|
| 538 |
|
---|
| 539 | boolean isActive() {
|
---|
| 540 | return active;
|
---|
| 541 | }
|
---|
| 542 |
|
---|
| 543 | boolean isSnapOn() {
|
---|
| 544 | return snapOn;
|
---|
| 545 | }
|
---|
| 546 |
|
---|
| 547 | private double getNearestAngle(double angle) {
|
---|
| 548 | double bestAngle = DoubleStream.of(snapAngles).boxed()
|
---|
| 549 | .min(Comparator.comparing(snapAngle -> getAngleDelta(angle, snapAngle))).orElse(0.0);
|
---|
| 550 | if (Math.abs(bestAngle-360) < 1e-3) {
|
---|
| 551 | bestAngle = 0;
|
---|
| 552 | }
|
---|
| 553 | return bestAngle;
|
---|
| 554 | }
|
---|
| 555 |
|
---|
| 556 | private static double getAngleDelta(double a, double b) {
|
---|
| 557 | double delta = Math.abs(a-b);
|
---|
| 558 | if (delta > 180)
|
---|
| 559 | return 360-delta;
|
---|
| 560 | else
|
---|
| 561 | return delta;
|
---|
| 562 | }
|
---|
| 563 |
|
---|
| 564 | void unFixOrTurnOff() {
|
---|
| 565 | if (absoluteFix) {
|
---|
| 566 | unsetFixedMode();
|
---|
| 567 | } else {
|
---|
| 568 | toggleSnapping();
|
---|
| 569 | }
|
---|
| 570 | }
|
---|
| 571 | }
|
---|