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 }