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