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}