source: josm/trunk/src/org/openstreetmap/josm/data/validation/tests/Coastlines.java@ 12402

Last change on this file since 12402 was 11608, checked in by Don-vip, 7 years ago

fix #14402 - add blacklist for leisure area values to avoid false positives - improve globally the detection of keys/tags

  • Property svn:eol-style set to native
File size: 10.5 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.data.validation.tests;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.awt.geom.Area;
7import java.util.ArrayList;
8import java.util.Collection;
9import java.util.Collections;
10import java.util.HashSet;
11import java.util.Iterator;
12import java.util.LinkedList;
13import java.util.List;
14import java.util.Set;
15
16import org.openstreetmap.josm.command.ChangeCommand;
17import org.openstreetmap.josm.command.Command;
18import org.openstreetmap.josm.data.osm.Node;
19import org.openstreetmap.josm.data.osm.OsmPrimitive;
20import org.openstreetmap.josm.data.osm.Way;
21import org.openstreetmap.josm.data.validation.Severity;
22import org.openstreetmap.josm.data.validation.Test;
23import org.openstreetmap.josm.data.validation.TestError;
24import org.openstreetmap.josm.gui.progress.ProgressMonitor;
25import org.openstreetmap.josm.tools.Geometry;
26
27/**
28 * Check coastlines for errors
29 *
30 * @author frsantos
31 * @author Teemu Koskinen
32 * @author Gerd Petermann
33 */
34public class Coastlines extends Test {
35
36 protected static final int UNORDERED_COASTLINE = 901;
37 protected static final int REVERSED_COASTLINE = 902;
38 protected static final int UNCONNECTED_COASTLINE = 903;
39 protected static final int WRONG_ORDER_COASTLINE = 904;
40
41 private List<Way> coastlineWays;
42
43 /**
44 * Constructor
45 */
46 public Coastlines() {
47 super(tr("Coastlines"), tr("This test checks that coastlines are correct."));
48 }
49
50 @Override
51 public void startTest(ProgressMonitor monitor) {
52 super.startTest(monitor);
53 coastlineWays = new LinkedList<>();
54 }
55
56 @Override
57 public void endTest() {
58 checkConnections();
59 checkDirection();
60 coastlineWays = null;
61 super.endTest();
62 }
63
64 /**
65 * Check connections between coastline ways.
66 * The nodes of a coastline way have to fullfil these rules:
67 * 1) the first node must be connected to the last node of a coastline way (which might be the same way)
68 * 2) the last node must be connected to the first node of a coastline way (which might be the same way)
69 * 3) all other nodes must not be connected to a coastline way
70 * 4) the number of referencing coastline ways must be 1 or 2
71 * Nodes outside the download area are special cases, we may not know enough about the connected ways.
72 */
73 private void checkConnections() {
74 Area downloadedArea = null;
75 for (Way w : coastlineWays) {
76 if (downloadedArea == null) {
77 downloadedArea = w.getDataSet().getDataSourceArea();
78 }
79 int numNodes = w.getNodesCount();
80 for (int i = 0; i < numNodes; i++) {
81 Node n = w.getNode(i);
82 List<OsmPrimitive> refs = n.getReferrers();
83 Set<Way> connectedWays = new HashSet<>();
84 for (OsmPrimitive p : refs) {
85 if (p != w && isCoastline(p)) {
86 connectedWays.add((Way) p);
87 }
88 }
89 if (i == 0) {
90 if (connectedWays.isEmpty() && n != w.lastNode() && n.getCoor().isIn(downloadedArea)) {
91 addError(UNCONNECTED_COASTLINE, w, null, n);
92 }
93 if (connectedWays.size() == 1 && n != connectedWays.iterator().next().lastNode()) {
94 checkIfReversed(w, connectedWays.iterator().next(), n);
95 }
96 if (connectedWays.size() == 1 && w.isClosed() && connectedWays.iterator().next().isClosed()) {
97 addError(UNORDERED_COASTLINE, w, connectedWays, n);
98 }
99 } else if (i == numNodes - 1) {
100 if (connectedWays.isEmpty() && n != w.firstNode() && n.getCoor().isIn(downloadedArea)) {
101 addError(UNCONNECTED_COASTLINE, w, null, n);
102 }
103 if (connectedWays.size() == 1 && n != connectedWays.iterator().next().firstNode()) {
104 checkIfReversed(w, connectedWays.iterator().next(), n);
105 }
106 } else if (!connectedWays.isEmpty()) {
107 addError(UNORDERED_COASTLINE, w, connectedWays, n);
108 }
109 }
110 }
111 }
112
113 /**
114 * Check if two or more coastline ways form a closed clockwise way
115 */
116 private void checkDirection() {
117 HashSet<Way> done = new HashSet<>();
118 for (Way w : coastlineWays) {
119 if (done.contains(w))
120 continue;
121 List<Way> visited = new ArrayList<>();
122 done.add(w);
123 visited.add(w);
124 List<Node> nodes = new ArrayList<>(w.getNodes());
125 Way curr = w;
126 while (nodes.get(0) != nodes.get(nodes.size()-1)) {
127 boolean foundContinuation = false;
128 for (OsmPrimitive p : curr.lastNode().getReferrers()) {
129 if (p != curr && isCoastline(p)) {
130 Way other = (Way) p;
131 if (done.contains(other))
132 continue;
133 if (other.firstNode() == curr.lastNode()) {
134 foundContinuation = true;
135 curr = other;
136 done.add(curr);
137 visited.add(curr);
138 nodes.remove(nodes.size()-1); // remove duplicate connection node
139 nodes.addAll(curr.getNodes());
140 break;
141 }
142 }
143 }
144 if (!foundContinuation)
145 break;
146 }
147 // simple closed ways are reported by WronglyOrderedWays
148 if (visited.size() > 1 && nodes.get(0) == nodes.get(nodes.size()-1) && Geometry.isClockwise(nodes)) {
149 errors.add(TestError.builder(this, Severity.WARNING, WRONG_ORDER_COASTLINE)
150 .message(tr("Reversed coastline: land not on left side"))
151 .primitives(visited)
152 .build());
153 }
154 }
155 }
156
157 /**
158 * Check if a reversed way would fit, if yes, add fixable "reversed" error, "unordered" else
159 * @param w way that might be reversed
160 * @param other other way that is connected to w
161 * @param n1 node at which w and other are connected
162 */
163 private void checkIfReversed(Way w, Way other, Node n1) {
164 boolean headFixedWithReverse = false;
165 boolean tailFixedWithReverse = false;
166 int otherCoastlineWays = 0;
167 Way connectedToFirst = null;
168 for (int i = 0; i < 2; i++) {
169 Node n = (i == 0) ? w.firstNode() : w.lastNode();
170 List<OsmPrimitive> refs = n.getReferrers();
171 for (OsmPrimitive p : refs) {
172 if (p != w && isCoastline(p)) {
173 Way cw = (Way) p;
174 if (i == 0 && cw.firstNode() == n) {
175 headFixedWithReverse = true;
176 connectedToFirst = cw;
177 } else if (i == 1 && cw.lastNode() == n) {
178 if (cw != connectedToFirst)
179 tailFixedWithReverse = true;
180 } else
181 otherCoastlineWays++;
182 }
183 }
184 }
185 if (otherCoastlineWays == 0 && headFixedWithReverse && tailFixedWithReverse)
186 addError(REVERSED_COASTLINE, w, null, null);
187 else
188 addError(UNORDERED_COASTLINE, w, Collections.singletonList(other), n1);
189 }
190
191 /**
192 * Add error if not already done
193 * @param errCode the error code
194 * @param w the way that is in error
195 * @param otherWays collection of other ways in error or null
196 * @param n the node to be highlighted or null
197 */
198 private void addError(int errCode, Way w, Collection<Way> otherWays, Node n) {
199 String msg;
200 switch (errCode) {
201 case UNCONNECTED_COASTLINE:
202 msg = tr("Unconnected coastline");
203 break;
204 case UNORDERED_COASTLINE:
205 msg = tr("Unordered coastline");
206 break;
207 case REVERSED_COASTLINE:
208 msg = tr("Reversed coastline");
209 break;
210 default:
211 msg = tr("invalid coastline"); // should not happen
212 }
213 Set<OsmPrimitive> primitives = new HashSet<>();
214 primitives.add(w);
215 if (otherWays != null)
216 primitives.addAll(otherWays);
217 // check for repeated error
218 for (TestError e : errors) {
219 if (e.getCode() != errCode)
220 continue;
221 if (errCode != REVERSED_COASTLINE && !e.getHighlighted().contains(n))
222 continue;
223 if (e.getPrimitives().size() != primitives.size())
224 continue;
225 if (!e.getPrimitives().containsAll(primitives))
226 continue;
227 return; // we already know this error
228 }
229 if (errCode != REVERSED_COASTLINE)
230 errors.add(TestError.builder(this, Severity.ERROR, errCode)
231 .message(msg)
232 .primitives(primitives)
233 .highlight(n)
234 .build());
235 else
236 errors.add(TestError.builder(this, Severity.ERROR, errCode)
237 .message(msg)
238 .primitives(primitives)
239 .build());
240 }
241
242 @Override
243 public void visit(Way way) {
244 if (!way.isUsable())
245 return;
246
247 if (isCoastline(way)) {
248 coastlineWays.add(way);
249 }
250 }
251
252 private static boolean isCoastline(OsmPrimitive osm) {
253 return osm instanceof Way && osm.hasTag("natural", "coastline");
254 }
255
256 @Override
257 public Command fixError(TestError testError) {
258 if (isFixable(testError)) {
259 // primitives list can be empty if all primitives have been purged
260 Iterator<? extends OsmPrimitive> it = testError.getPrimitives().iterator();
261 if (it.hasNext()) {
262 Way way = (Way) it.next();
263 Way newWay = new Way(way);
264
265 List<Node> nodesCopy = newWay.getNodes();
266 Collections.reverse(nodesCopy);
267 newWay.setNodes(nodesCopy);
268
269 return new ChangeCommand(way, newWay);
270 }
271 }
272 return null;
273 }
274
275 @Override
276 public boolean isFixable(TestError testError) {
277 if (testError.getTester() instanceof Coastlines)
278 return testError.getCode() == REVERSED_COASTLINE;
279
280 return false;
281 }
282}
Note: See TracBrowser for help on using the repository browser.