001// License: GPL. For details, see LICENSE file. 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; 041 042 /** 043 * Constructor 044 */ 045 public Coastlines() { 046 super(tr("Coastlines"), 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.getLayerManager().getEditLayer(); 055 056 if (layer != null) { 057 downloadedArea = layer.data.getDataSourceArea(); 058 } 059 060 coastlines = new LinkedList<>(); 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<>(); 147 primitives.add(c1); 148 149 if (headWays == 0 || tailWays == 0) { 150 List<OsmPrimitive> highlight = new ArrayList<>(); 151 152 if (headWays == 0 && head.getCoor().isIn(downloadedArea)) { 153 highlight.add(head); 154 } 155 if (tailWays == 0 && tail.getCoor().isIn(downloadedArea)) { 156 highlight.add(tail); 157 } 158 159 if (!highlight.isEmpty()) { 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<>(); 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 } else if (reversed) { 191 errors.add(new TestError(this, Severity.ERROR, tr("Reversed coastline"), 192 REVERSED_COASTLINE, primitives)); 193 } 194 } 195 196 coastlines = null; 197 downloadedArea = null; 198 199 super.endTest(); 200 } 201 202 @Override 203 public void visit(Way way) { 204 if (!way.isUsable()) 205 return; 206 207 if (isCoastline(way)) { 208 coastlines.add(way); 209 } 210 } 211 212 private static boolean isCoastline(OsmPrimitive osm) { 213 return osm instanceof Way && "coastline".equals(osm.get("natural")); 214 } 215 216 @Override 217 public Command fixError(TestError testError) { 218 if (isFixable(testError)) { 219 // primitives list can be empty if all primitives have been purged 220 Iterator<? extends OsmPrimitive> it = testError.getPrimitives().iterator(); 221 if (it.hasNext()) { 222 Way way = (Way) it.next(); 223 Way newWay = new Way(way); 224 225 List<Node> nodesCopy = newWay.getNodes(); 226 Collections.reverse(nodesCopy); 227 newWay.setNodes(nodesCopy); 228 229 return new ChangeCommand(way, newWay); 230 } 231 } 232 return null; 233 } 234 235 @Override 236 public boolean isFixable(TestError testError) { 237 if (testError.getTester() instanceof Coastlines) 238 return testError.getCode() == REVERSED_COASTLINE; 239 240 return false; 241 } 242}