001 // License: GPL. See LICENSE file for details.
002 package org.openstreetmap.josm.data.validation.tests;
003
004 import static org.openstreetmap.josm.tools.I18n.tr;
005
006 import java.awt.geom.Area;
007 import java.util.ArrayList;
008 import java.util.Collection;
009 import java.util.Collections;
010 import java.util.LinkedList;
011 import java.util.List;
012
013 import org.openstreetmap.josm.Main;
014 import org.openstreetmap.josm.command.ChangeCommand;
015 import org.openstreetmap.josm.command.Command;
016 import org.openstreetmap.josm.data.osm.Node;
017 import org.openstreetmap.josm.data.osm.OsmPrimitive;
018 import org.openstreetmap.josm.data.osm.Way;
019 import org.openstreetmap.josm.data.validation.Severity;
020 import org.openstreetmap.josm.data.validation.Test;
021 import org.openstreetmap.josm.data.validation.TestError;
022 import org.openstreetmap.josm.gui.layer.OsmDataLayer;
023 import org.openstreetmap.josm.gui.progress.ProgressMonitor;
024
025 /**
026 * Check coastlines for errors
027 *
028 * @author frsantos
029 * @author Teemu Koskinen
030 */
031 public class Coastlines extends Test {
032
033 protected static final int UNORDERED_COASTLINE = 901;
034 protected static final int REVERSED_COASTLINE = 902;
035 protected static final int UNCONNECTED_COASTLINE = 903;
036
037 private List<Way> coastlines;
038
039 private Area downloadedArea = null;
040
041 /**
042 * Constructor
043 */
044 public Coastlines() {
045 super(tr("Coastlines"),
046 tr("This test checks that coastlines are correct."));
047 }
048
049 @Override
050 public void startTest(ProgressMonitor monitor) {
051
052 super.startTest(monitor);
053
054 OsmDataLayer layer = Main.map.mapView.getEditLayer();
055
056 if (layer != null) {
057 downloadedArea = layer.data.getDataSourceArea();
058 }
059
060 coastlines = new LinkedList<Way>();
061 }
062
063 @Override
064 public void endTest() {
065 for (Way c1 : coastlines) {
066 Node head = c1.firstNode();
067 Node tail = c1.lastNode();
068
069 if (c1.getNodesCount() == 0 || head.equals(tail)) {
070 continue;
071 }
072
073 int headWays = 0;
074 int tailWays = 0;
075 boolean headReversed = false;
076 boolean tailReversed = false;
077 boolean headUnordered = false;
078 boolean tailUnordered = false;
079 Way next = null;
080 Way prev = null;
081
082 for (Way c2 : coastlines) {
083 if (c1 == c2) {
084 continue;
085 }
086
087 if (c2.containsNode(head)) {
088 headWays++;
089 next = c2;
090
091 if (head.equals(c2.firstNode())) {
092 headReversed = true;
093 } else if (!head.equals(c2.lastNode())) {
094 headUnordered = true;
095 }
096 }
097
098 if (c2.containsNode(tail)) {
099 tailWays++;
100 prev = c2;
101
102 if (tail.equals(c2.lastNode())) {
103 tailReversed = true;
104 } else if (!tail.equals(c2.firstNode())) {
105 tailUnordered = true;
106 }
107 }
108 }
109
110 // To avoid false positives on upload (only modified primitives
111 // are visited), we have to check possible connection to ways
112 // that are not in the set of validated primitives.
113 if (headWays == 0) {
114 Collection<OsmPrimitive> refs = head.getReferrers();
115 for (OsmPrimitive ref : refs) {
116 if (ref != c1 && isCoastline(ref)) {
117 // ref cannot be in <code>coastlines</code>, otherwise we would
118 // have picked it up already
119 headWays++;
120 next = (Way) ref;
121
122 if (head.equals(next.firstNode())) {
123 headReversed = true;
124 } else if (!head.equals(next.lastNode())) {
125 headUnordered = true;
126 }
127 }
128 }
129 }
130 if (tailWays == 0) {
131 Collection<OsmPrimitive> refs = tail.getReferrers();
132 for (OsmPrimitive ref : refs) {
133 if (ref != c1 && isCoastline(ref)) {
134 tailWays++;
135 prev = (Way) ref;
136
137 if (tail.equals(prev.lastNode())) {
138 tailReversed = true;
139 } else if (!tail.equals(prev.firstNode())) {
140 tailUnordered = true;
141 }
142 }
143 }
144 }
145
146 List<OsmPrimitive> primitives = new ArrayList<OsmPrimitive>();
147 primitives.add(c1);
148
149 if (headWays == 0 || tailWays == 0) {
150 List<OsmPrimitive> highlight = new ArrayList<OsmPrimitive>();
151
152 if (headWays == 0 && (downloadedArea == null || downloadedArea.contains(head.getCoor()))) {
153 highlight.add(head);
154 }
155 if (tailWays == 0 && (downloadedArea == null || downloadedArea.contains(tail.getCoor()))) {
156 highlight.add(tail);
157 }
158
159 if (highlight.size() > 0) {
160 errors.add(new TestError(this, Severity.ERROR, tr("Unconnected coastline"),
161 UNCONNECTED_COASTLINE, primitives, highlight));
162 }
163 }
164
165 boolean unordered = false;
166 boolean reversed = headWays == 1 && headReversed && tailWays == 1 && tailReversed;
167
168 if (headWays > 1 || tailWays > 1) {
169 unordered = true;
170 } else if (headUnordered || tailUnordered) {
171 unordered = true;
172 } else if (reversed && next == prev) {
173 unordered = true;
174 } else if ((headReversed || tailReversed) && headReversed != tailReversed) {
175 unordered = true;
176 }
177
178 if (unordered) {
179 List<OsmPrimitive> highlight = new ArrayList<OsmPrimitive>();
180
181 if (headWays > 1 || headUnordered || headReversed || reversed) {
182 highlight.add(head);
183 }
184 if (tailWays > 1 || tailUnordered || tailReversed || reversed) {
185 highlight.add(tail);
186 }
187
188 errors.add(new TestError(this, Severity.ERROR, tr("Unordered coastline"),
189 UNORDERED_COASTLINE, primitives, highlight));
190 }
191 else if (reversed) {
192 errors.add(new TestError(this, Severity.ERROR, tr("Reversed coastline"),
193 REVERSED_COASTLINE, primitives));
194 }
195 }
196
197 coastlines = null;
198 downloadedArea = null;
199
200 super.endTest();
201 }
202
203 @Override
204 public void visit(Way way) {
205 if (!way.isUsable())
206 return;
207
208 if (isCoastline(way)) {
209 coastlines.add(way);
210 }
211 }
212
213 private static boolean isCoastline(OsmPrimitive osm) {
214 return osm instanceof Way && "coastline".equals(osm.get("natural"));
215 }
216
217 @Override
218 public Command fixError(TestError testError) {
219 if (isFixable(testError)) {
220 Way way = (Way) testError.getPrimitives().iterator().next();
221 Way newWay = new Way(way);
222
223 List<Node> nodesCopy = newWay.getNodes();
224 Collections.reverse(nodesCopy);
225 newWay.setNodes(nodesCopy);
226
227 return new ChangeCommand(way, newWay);
228 }
229 return null;
230 }
231
232 @Override
233 public boolean isFixable(TestError testError) {
234 if (testError.getTester() instanceof Coastlines)
235 return (testError.getCode() == REVERSED_COASTLINE);
236
237 return false;
238 }
239 }