Ticket #13570: 13570.patch

File 13570.patch, 13.5 KB (added by GerdP, 8 years ago)
  • src/org/openstreetmap/josm/data/validation/tests/Coastlines.java

     
    77import java.util.ArrayList;
    88import java.util.Collection;
    99import java.util.Collections;
     10import java.util.HashSet;
    1011import java.util.Iterator;
    1112import java.util.LinkedList;
    1213import java.util.List;
     14import java.util.Set;
    1315
    1416import org.openstreetmap.josm.Main;
    1517import org.openstreetmap.josm.command.ChangeCommand;
     
    2224import org.openstreetmap.josm.data.validation.TestError;
    2325import org.openstreetmap.josm.gui.layer.OsmDataLayer;
    2426import org.openstreetmap.josm.gui.progress.ProgressMonitor;
     27import org.openstreetmap.josm.tools.Geometry;
    2528
    2629/**
    2730 * Check coastlines for errors
     
    2831 *
    2932 * @author frsantos
    3033 * @author Teemu Koskinen
     34 * @author Gerd Petermann
    3135 */
    3236public class Coastlines extends Test {
    3337
     
    3438    protected static final int UNORDERED_COASTLINE = 901;
    3539    protected static final int REVERSED_COASTLINE = 902;
    3640    protected static final int UNCONNECTED_COASTLINE = 903;
     41    protected static final int WRONG_ORDER_COASTLINE = 904;
    3742
    3843    private List<Way> coastlines;
    3944
     
    6267
    6368    @Override
    6469    public void endTest() {
    65         for (Way c1 : coastlines) {
    66             Node head = c1.firstNode();
    67             Node tail = c1.lastNode();
     70        checkConnections();
     71        CheckDirection();
     72        coastlines = null;
     73        downloadedArea = null;
    6874
    69             if (c1.getNodesCount() == 0 || head.equals(tail)) {
    70                 continue;
    71             }
     75        super.endTest();
     76    }
    7277
    73             int headWays = 0;
    74             int tailWays = 0;
    75             boolean headReversed = false;
    76             boolean tailReversed = false;
    77             boolean headUnordered = false;
    78             boolean tailUnordered = false;
    79             Way next = null;
    80             Way prev = null;
    81 
    82             for (Way c2 : coastlines) {
    83                 if (c1 == c2) {
    84                     continue;
    85                 }
    86 
    87                 if (c2.containsNode(head)) {
    88                     headWays++;
    89                     next = c2;
    90 
    91                     if (head.equals(c2.firstNode())) {
    92                         headReversed = true;
    93                     } else if (!head.equals(c2.lastNode())) {
    94                         headUnordered = true;
     78    /**
     79     * Check connections between coastline ways.
     80     * The nodes of a coastline way have to fullfil these rules:
     81     * 1) the first node must be connected to the last node of a coastline way (which might be the same way)
     82     * 2) the last node must be connected to the first node of a coastline way (which might be the same way)
     83     * 3) all other nodes must not be connected to a coastline way
     84     * 4) the number of referencing coastline ways must be 1 or 2
     85     * Nodes outside the download area are special cases, we may not know enough about the connected ways.
     86     *
     87     */
     88    private void checkConnections() {
     89        for (Way w : coastlines) {
     90            int numNodes = w.getNodesCount();
     91            for (int i = 0; i < numNodes; i++) {
     92                Node n = w.getNode(i);
     93                List<OsmPrimitive> refs = n.getReferrers();
     94                Set<Way> connectedWays = new HashSet<>();
     95                for (OsmPrimitive p : refs) {
     96                    if (p != w && isCoastline(p)) {
     97                        connectedWays.add((Way) p);
    9598                    }
    9699                }
    97 
    98                 if (c2.containsNode(tail)) {
    99                     tailWays++;
    100                     prev = c2;
    101 
    102                     if (tail.equals(c2.lastNode())) {
    103                         tailReversed = true;
    104                     } else if (!tail.equals(c2.firstNode())) {
    105                         tailUnordered = true;
     100                if (i == 0) {
     101                    if (connectedWays.isEmpty() && n != w.lastNode() && n.getCoor().isIn(downloadedArea)) {
     102                        addError(UNCONNECTED_COASTLINE, w, null, n);
    106103                    }
    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                         }
     104                    if (connectedWays.size() == 1 && n != connectedWays.iterator().next().lastNode()) {
     105                        checkIfReversed(w, connectedWays.iterator().next(), n);
    127106                    }
     107                } else if (i == numNodes - 1) {
     108                    if (connectedWays.isEmpty() && n != w.firstNode() && n.getCoor().isIn(downloadedArea)) {
     109                        addError(UNCONNECTED_COASTLINE, w, null, n);
     110                    }
     111                    if (connectedWays.size() == 1 && n != connectedWays.iterator().next().firstNode()) {
     112                        checkIfReversed(w, connectedWays.iterator().next(), n);
     113                    }
     114                } else if (!connectedWays.isEmpty()) {
     115                    addError(UNORDERED_COASTLINE, w, connectedWays, n);
    128116                }
    129117            }
    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;
     118        }
     119    }
    136120
    137                         if (tail.equals(prev.lastNode())) {
    138                             tailReversed = true;
    139                         } else if (!tail.equals(prev.firstNode())) {
    140                             tailUnordered = true;
     121    /**
     122     * Check if two or more coastline ways form a closed clockwise way
     123     */
     124    private void CheckDirection() {
     125        HashSet<Way> done = new HashSet<>();
     126        for (Way w : coastlines) {
     127            if (done.contains(w))
     128                continue;
     129            List<Way> visited = new ArrayList<>();
     130            done.add(w);
     131            visited.add(w);
     132            List<Node> nodes = new ArrayList<>(w.getNodes());
     133            Way curr = w;
     134            while (nodes.get(0) != nodes.get(nodes.size()-1)) {
     135                boolean foundContinuation = false;
     136                for (OsmPrimitive p : curr.lastNode().getReferrers()) {
     137                    if (p != curr && isCoastline(p)) {
     138                        Way other = (Way) p;
     139                        if (done.contains(other))
     140                            continue;
     141                        if (other.firstNode() == curr.lastNode()) {
     142                            foundContinuation = true;
     143                            curr = other;
     144                            done.add(curr);
     145                            visited.add(curr);
     146                            nodes.remove(nodes.size()-1); // remove duplicate connection node
     147                            nodes.addAll(curr.getNodes());
     148                            break;
    141149                        }
    142150                    }
    143151                }
     152                if (!foundContinuation)
     153                    break;
    144154            }
    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);
     155            // simple closed ways are reported by WronglyOrderedWays
     156            if(visited.size() > 1 && nodes.get(0) == nodes.get(nodes.size()-1)) {
     157                if (Geometry.isClockwise(nodes)) {
     158                    errors.add(new TestError(this, Severity.WARNING, tr("Reversed coastline: land not on left side"), WRONG_ORDER_COASTLINE, visited));
    154159                }
    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                 }
    163160            }
     161        }
     162    }
    164163
    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);
     164    /**
     165     * Check if a reversed way would fit, if yes, add fixable "reversed" error, "unordered" else
     166     * @param w way that might be reversed
     167     * @param other other way that is connected to w
     168     * @param n1 node at which w and other are connected
     169     */
     170    private void checkIfReversed(Way w, Way other, Node n1) {
     171        boolean headFixedWithReverse = false;
     172        boolean tailFixedWithReverse = false;
     173        int otherCoastlineWays = 0;
     174        Way connectedToFirst = null;
     175        for (int i = 0; i < 2; i++) {
     176            Node n = (i == 0) ? w.firstNode() : w.lastNode();
     177            List<OsmPrimitive> refs = n.getReferrers();
     178            for (OsmPrimitive p : refs) {
     179                if (p != w && isCoastline(p)) {
     180                    Way cw = (Way) p;
     181                    if (i == 0 && cw.firstNode() == n) {
     182                        headFixedWithReverse = true;
     183                        connectedToFirst = cw;
     184                    }
     185                    else if (i == 1 && cw.lastNode() == n) {
     186                        if (cw != connectedToFirst)
     187                            tailFixedWithReverse = true;
     188                    }
     189                    else
     190                        otherCoastlineWays++;
    183191                }
    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));
    193192            }
    194193        }
     194        if (otherCoastlineWays == 0 && headFixedWithReverse && tailFixedWithReverse)
     195            addError(REVERSED_COASTLINE, w, null, null);
     196        else
     197            addError(UNORDERED_COASTLINE, w, Collections.singletonList(other), n1);
    195198
    196         coastlines = null;
    197         downloadedArea = null;
     199    }
    198200
    199         super.endTest();
     201    /**
     202     * Add error if not already done
     203     * @param errCode the error code
     204     * @param w the way that is in error
     205     * @param otherWays collection of other ways in error or null
     206     * @param n the node to be highlighted or null
     207     */
     208    private void addError(int errCode, Way w, Collection<Way> otherWays, Node n) {
     209        String msg = null;
     210        switch (errCode) {
     211        case UNCONNECTED_COASTLINE:
     212            msg = tr("Unconnected coastline");
     213            break;
     214        case UNORDERED_COASTLINE:
     215            msg = tr("Unordered coastline");
     216            break;
     217        case REVERSED_COASTLINE:
     218            msg = tr("Reversed coastline");
     219            break;
     220        default:
     221            msg = tr("invalid coastline"); // should not happen
     222        }
     223        Set<OsmPrimitive> primitives = new HashSet<>();
     224        primitives.add(w);
     225        if (otherWays != null)
     226            primitives.addAll(otherWays);
     227        // check for repeated error
     228        for (TestError e : errors) {
     229            if (e.getCode() != errCode)
     230                continue;
     231            if (errCode != REVERSED_COASTLINE && e.getHighlighted().contains(n) == false)
     232                continue;
     233            if (e.getPrimitives().size() != primitives.size())
     234                continue;
     235            if (e.getPrimitives().containsAll(primitives) == false)
     236                continue;
     237            return; // we already know this error
     238        }
     239        if (errCode != REVERSED_COASTLINE)
     240            errors.add(new TestError(this, Severity.ERROR, msg, errCode, primitives, Collections.singletonList(n)));
     241        else
     242            errors.add(new TestError(this, Severity.ERROR, msg, errCode, primitives));
    200243    }
    201244
    202245    @Override