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 }