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 }