| 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); |
| 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); |
| | 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); |
| 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; |
| 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)); |
| 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++; |
| 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)); |
| 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)); |