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::isSelectable); 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::isSelectable); 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}