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