001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.actions.mapmode;
003
004import java.awt.Point;
005import java.util.Collection;
006import java.util.List;
007
008import org.openstreetmap.josm.Main;
009import org.openstreetmap.josm.data.coor.EastNorth;
010import org.openstreetmap.josm.data.osm.Node;
011import org.openstreetmap.josm.data.osm.OsmPrimitive;
012import org.openstreetmap.josm.data.osm.Way;
013import org.openstreetmap.josm.data.osm.WaySegment;
014import org.openstreetmap.josm.gui.MapView;
015import org.openstreetmap.josm.tools.Geometry;
016import org.openstreetmap.josm.tools.Pair;
017
018/**
019 * This static class contains functions used to find target way, node to move or
020 * segment to divide.
021 *
022 * @author Alexander Kachkaev <alexander@kachkaev.ru>, 2011
023 */
024final class ImproveWayAccuracyHelper {
025
026    private ImproveWayAccuracyHelper() {
027        // Hide default constructor for utils classes
028    }
029
030    /**
031     * Finds the way to work on. If the mouse is on the node, extracts one of
032     * the ways containing it. If the mouse is on the way, simply returns it.
033     *
034     * @param mv the current map view
035     * @param p the cursor position
036     * @return {@code Way} or {@code null} in case there is nothing under the cursor.
037     */
038    public static Way findWay(MapView mv, Point p) {
039        if (mv == null || p == null) {
040            return null;
041        }
042
043        Node node = mv.getNearestNode(p, OsmPrimitive.isSelectablePredicate);
044        Way candidate = null;
045
046        if (node != null) {
047            final Collection<OsmPrimitive> candidates = node.getReferrers();
048            for (OsmPrimitive refferer : candidates) {
049                if (refferer instanceof Way) {
050                    candidate = (Way) refferer;
051                    break;
052                }
053            }
054            if (candidate != null) {
055                return candidate;
056            }
057        }
058
059        return Main.map.mapView.getNearestWay(p, OsmPrimitive.isSelectablePredicate);
060    }
061
062    /**
063     * Returns the nearest node to cursor. All nodes that are “behind” segments
064     * are neglected. This is to avoid way self-intersection after moving the
065     * candidateNode to a new place.
066     *
067     * @param mv the current map view
068     * @param w the way to check
069     * @param p the cursor position
070     * @return nearest node to cursor
071     */
072    public static Node findCandidateNode(MapView mv, Way w, Point p) {
073        if (mv == null || w == null || p == null) {
074            return null;
075        }
076
077        EastNorth pEN = mv.getEastNorth(p.x, p.y);
078
079        Double bestDistance = Double.MAX_VALUE;
080        Double currentDistance;
081        List<Pair<Node, Node>> wpps = w.getNodePairs(false);
082
083        Node result = null;
084
085        mainLoop:
086        for (Node n : w.getNodes()) {
087            EastNorth nEN = n.getEastNorth();
088
089            if (nEN == null) {
090                // Might happen if lat/lon for that point are not known.
091                continue;
092            }
093
094            currentDistance = pEN.distance(nEN);
095
096            if (currentDistance < bestDistance) {
097                // Making sure this candidate is not behind any segment.
098                for (Pair<Node, Node> wpp : wpps) {
099                    if (!wpp.a.equals(n)
100                            && !wpp.b.equals(n)
101                            && Geometry.getSegmentSegmentIntersection(
102                            wpp.a.getEastNorth(), wpp.b.getEastNorth(),
103                            pEN, nEN) != null) {
104                        continue mainLoop;
105                    }
106                }
107                result = n;
108                bestDistance = currentDistance;
109            }
110        }
111
112        return result;
113    }
114
115    /**
116     * Returns the nearest way segment to cursor. The distance to segment ab is
117     * the length of altitude from p to ab (say, c) or the minimum distance from
118     * p to a or b if c is out of ab.
119     *
120     * The priority is given to segments where c is in ab. Otherwise, a segment
121     * with the largest angle apb is chosen.
122     *
123     * @param mv the current map view
124     * @param w the way to check
125     * @param p the cursor position
126     * @return nearest way segment to cursor
127     */
128    public static WaySegment findCandidateSegment(MapView mv, Way w, Point p) {
129        if (mv == null || w == null || p == null) {
130            return null;
131        }
132
133        EastNorth pEN = mv.getEastNorth(p.x, p.y);
134
135        Double currentDistance;
136        Double currentAngle;
137        Double bestDistance = Double.MAX_VALUE;
138        Double bestAngle = 0.0;
139
140        int candidate = -1;
141
142        List<Pair<Node, Node>> wpps = w.getNodePairs(true);
143
144        int i = -1;
145        for (Pair<Node, Node> wpp : wpps) {
146            ++i;
147
148            EastNorth a = wpp.a.getEastNorth();
149            EastNorth b = wpp.b.getEastNorth();
150
151            // Finding intersection of the segment with its altitude from p
152            EastNorth altitudeIntersection = Geometry.closestPointToSegment(a, b, pEN);
153            currentDistance = pEN.distance(altitudeIntersection);
154
155            if (!altitudeIntersection.equals(a) && !altitudeIntersection.equals(b)) {
156                // If the segment intersects with the altitude from p,
157                // make an angle too big to let this candidate win any others
158                // having the same distance.
159                currentAngle = Double.MAX_VALUE;
160            } else {
161                // Otherwise measure the angle
162                currentAngle = Math.abs(Geometry.getCornerAngle(a, pEN, b));
163            }
164
165            if (currentDistance < bestDistance
166                    || (currentAngle > bestAngle && currentDistance < bestDistance * 1.0001 /*
167                     * equality
168                     */)) {
169                candidate = i;
170                bestAngle = currentAngle;
171                bestDistance = currentDistance;
172            }
173
174        }
175        return candidate != -1 ? new WaySegment(w, candidate) : null;
176    }
177}