// License: GPL. See LICENSE file for details.
package org.openstreetmap.josm.data.validation.tests;
import static org.openstreetmap.josm.tools.I18n.tr;
import java.awt.geom.Area;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import org.openstreetmap.josm.Main;
import org.openstreetmap.josm.command.ChangeCommand;
import org.openstreetmap.josm.command.Command;
import org.openstreetmap.josm.data.osm.Node;
import org.openstreetmap.josm.data.osm.OsmPrimitive;
import org.openstreetmap.josm.data.osm.Way;
import org.openstreetmap.josm.data.validation.Severity;
import org.openstreetmap.josm.data.validation.Test;
import org.openstreetmap.josm.data.validation.TestError;
import org.openstreetmap.josm.gui.layer.OsmDataLayer;
import org.openstreetmap.josm.gui.progress.ProgressMonitor;
/**
* Check coastlines for errors
*
* @author frsantos
* @author Teemu Koskinen
*/
public class Coastlines extends Test {
protected static final int UNORDERED_COASTLINE = 901;
protected static final int REVERSED_COASTLINE = 902;
protected static final int UNCONNECTED_COASTLINE = 903;
private List<Way> coastlines;
private Area downloadedArea = null;
/**
* Constructor
*/
public Coastlines() {
super(tr("Coastlines"),
tr("This test checks that coastlines are correct."));
}
@Override
public void startTest(ProgressMonitor monitor) {
super.startTest(monitor);
OsmDataLayer layer = Main.main.getEditLayer();
if (layer != null) {
downloadedArea = layer.data.getDataSourceArea();
}
coastlines = new LinkedList<>();
}
@Override
public void endTest() {
for (Way c1 : coastlines) {
Node head = c1.firstNode();
Node tail = c1.lastNode();
if (c1.getNodesCount() == 0 || head.equals(tail)) {
continue;
}
int headWays = 0;
int tailWays = 0;
boolean headReversed = false;
boolean tailReversed = false;
boolean headUnordered = false;
boolean tailUnordered = false;
Way next = null;
Way prev = null;
for (Way c2 : coastlines) {
if (c1 == c2) {
continue;
}
if (c2.containsNode(head)) {
headWays++;
next = c2;
if (head.equals(c2.firstNode())) {
headReversed = true;
} else if (!head.equals(c2.lastNode())) {
headUnordered = true;
}
}
if (c2.containsNode(tail)) {
tailWays++;
prev = c2;
if (tail.equals(c2.lastNode())) {
tailReversed = true;
} else if (!tail.equals(c2.firstNode())) {
tailUnordered = true;
}
}
}
// To avoid false positives on upload (only modified primitives
// are visited), we have to check possible connection to ways
// that are not in the set of validated primitives.
if (headWays == 0) {
Collection<OsmPrimitive> refs = head.getReferrers();
for (OsmPrimitive ref : refs) {
if (ref != c1 && isCoastline(ref)) {
// ref cannot be in <code>coastlines</code>, otherwise we would
// have picked it up already
headWays++;
next = (Way) ref;
if (head.equals(next.firstNode())) {
headReversed = true;
} else if (!head.equals(next.lastNode())) {
headUnordered = true;
}
}
}
}
if (tailWays == 0) {
Collection<OsmPrimitive> refs = tail.getReferrers();
for (OsmPrimitive ref : refs) {
if (ref != c1 && isCoastline(ref)) {
tailWays++;
prev = (Way) ref;
if (tail.equals(prev.lastNode())) {
tailReversed = true;
} else if (!tail.equals(prev.firstNode())) {
tailUnordered = true;
}
}
}
}
List<OsmPrimitive> primitives = new ArrayList<>();
primitives.add(c1);
if (headWays == 0 || tailWays == 0) {
List<OsmPrimitive> highlight = new ArrayList<>();
if (headWays == 0 && head.getCoor().isIn(downloadedArea)) {
highlight.add(head);
}
if (tailWays == 0 && tail.getCoor().isIn(downloadedArea)) {
highlight.add(tail);
}
if (!highlight.isEmpty()) {
errors.add(new TestError(this, Severity.ERROR, tr("Unconnected coastline"),
UNCONNECTED_COASTLINE, primitives, highlight));
}
}
boolean unordered = false;
boolean reversed = headWays == 1 && headReversed && tailWays == 1 && tailReversed;
if (headWays > 1 || tailWays > 1) {
unordered = true;
} else if (headUnordered || tailUnordered) {
unordered = true;
} else if (reversed && next == prev) {
unordered = true;
} else if ((headReversed || tailReversed) && headReversed != tailReversed) {
unordered = true;
}
if (unordered) {
List<OsmPrimitive> highlight = new ArrayList<>();
if (headWays > 1 || headUnordered || headReversed || reversed) {
highlight.add(head);
}
if (tailWays > 1 || tailUnordered || tailReversed || reversed) {
highlight.add(tail);
}
errors.add(new TestError(this, Severity.ERROR, tr("Unordered coastline"),
UNORDERED_COASTLINE, primitives, highlight));
}
else if (reversed) {
errors.add(new TestError(this, Severity.ERROR, tr("Reversed coastline"),
REVERSED_COASTLINE, primitives));
}
}
coastlines = null;
downloadedArea = null;
super.endTest();
}
@Override
public void visit(Way way) {
if (!way.isUsable())
return;
if (isCoastline(way)) {
coastlines.add(way);
}
}
private static boolean isCoastline(OsmPrimitive osm) {
return osm instanceof Way && "coastline".equals(osm.get("natural"));
}
@Override
public Command fixError(TestError testError) {
if (isFixable(testError)) {
// primitives list can be empty if all primitives have been purged
Iterator<? extends OsmPrimitive> it = testError.getPrimitives().iterator();
if (it.hasNext()) {
Way way = (Way) it.next();
Way newWay = new Way(way);
List<Node> nodesCopy = newWay.getNodes();
Collections.reverse(nodesCopy);
newWay.setNodes(nodesCopy);
return new ChangeCommand(way, newWay);
}
}
return null;
}
@Override
public boolean isFixable(TestError testError) {
if (testError.getTester() instanceof Coastlines)
return (testError.getCode() == REVERSED_COASTLINE);
return false;
}
}