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