001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.actions.mapmode; 003 004import java.awt.Point; 005import java.util.List; 006import java.util.Optional; 007 008import org.openstreetmap.josm.data.coor.EastNorth; 009import org.openstreetmap.josm.data.osm.Node; 010import org.openstreetmap.josm.data.osm.OsmPrimitive; 011import org.openstreetmap.josm.data.osm.Way; 012import org.openstreetmap.josm.data.osm.WaySegment; 013import org.openstreetmap.josm.gui.MainApplication; 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 045 if (node != null) { 046 Optional<Way> candidate = node.referrers(Way.class).findFirst(); 047 if (candidate.isPresent()) { 048 return candidate.get(); 049 } 050 } 051 052 return MainApplication.getMap().mapView.getNearestWay(p, OsmPrimitive::isSelectable); 053 } 054 055 /** 056 * Returns the nearest node to cursor. All nodes that are “behind” segments 057 * are neglected. This is to avoid way self-intersection after moving the 058 * candidateNode to a new place. 059 * 060 * @param mv the current map view 061 * @param w the way to check 062 * @param p the cursor position 063 * @return nearest node to cursor 064 */ 065 public static Node findCandidateNode(MapView mv, Way w, Point p) { 066 if (mv == null || w == null || p == null) { 067 return null; 068 } 069 070 EastNorth pEN = mv.getEastNorth(p.x, p.y); 071 072 Double bestDistance = Double.MAX_VALUE; 073 Double currentDistance; 074 List<Pair<Node, Node>> wpps = w.getNodePairs(false); 075 076 Node result = null; 077 078 mainLoop: 079 for (Node n : w.getNodes()) { 080 EastNorth nEN = n.getEastNorth(); 081 082 if (nEN == null) { 083 // Might happen if lat/lon for that point are not known. 084 continue; 085 } 086 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 the current map view 117 * @param w the way to check 118 * @param p the cursor position 119 * @return nearest way segment to cursor 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 EastNorth a = wpp.a.getEastNorth(); 142 EastNorth b = wpp.b.getEastNorth(); 143 144 // Finding intersection of the segment with its altitude from p 145 EastNorth altitudeIntersection = Geometry.closestPointToSegment(a, b, pEN); 146 currentDistance = pEN.distance(altitudeIntersection); 147 148 if (!altitudeIntersection.equals(a) && !altitudeIntersection.equals(b)) { 149 // If the segment intersects with the altitude from p, 150 // make an angle too big to let this candidate win any others 151 // having the same distance. 152 currentAngle = Double.MAX_VALUE; 153 } else { 154 // Otherwise measure the angle 155 currentAngle = Math.abs(Geometry.getCornerAngle(a, pEN, b)); 156 } 157 158 if (currentDistance < bestDistance 159 || (currentAngle > bestAngle && currentDistance < bestDistance * 1.0001 /* 160 * equality 161 */)) { 162 candidate = i; 163 bestAngle = currentAngle; 164 bestDistance = currentDistance; 165 } 166 167 } 168 return candidate != -1 ? new WaySegment(w, candidate) : null; 169 } 170}