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    }