Ticket #20716: josm_20716_power_v7_reupload.patch
File josm_20716_power_v7_reupload.patch, 23.7 KB (added by , 4 years ago) |
---|
-
src/org/openstreetmap/josm/data/osm/Way.java
IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 diff --git a/src/org/openstreetmap/josm/data/osm/Way.java b/src/org/openstreetmap/josm/data/osm/Way.java
a b 650 650 return length; 651 651 } 652 652 653 /** 654 * Calculates the segment lengths of the way as computed by {@link LatLon#greatCircleDistance}. 655 * @return The segment lengths of a way in metres, from the first to last node 656 * @since xxx 657 */ 658 public double[] getSegmentLengths() { 659 double[] segmentLengths = new double[nodes.length - 1]; 660 Node prevNode = this.firstNode(); 661 662 for (int i = 1; i < nodes.length; i++) { 663 Node n = nodes[i]; 664 665 double distance = n.getCoor().greatCircleDistance(prevNode.getCoor()); 666 segmentLengths[i - 1] = distance; 667 prevNode = n; 668 } 669 670 return segmentLengths; 671 } 672 653 673 /** 654 674 * Replies the length of the longest segment of the way, in metres, as computed by {@link LatLon#greatCircleDistance}. 655 675 * @return The length of the segment, in metres -
src/org/openstreetmap/josm/data/validation/tests/PowerLines.java
IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 diff --git a/src/org/openstreetmap/josm/data/validation/tests/PowerLines.java b/src/org/openstreetmap/josm/data/validation/tests/PowerLines.java
a b 1 1 // License: GPL. For details, see LICENSE file. 2 2 package org.openstreetmap.josm.data.validation.tests; 3 3 4 import static org.openstreetmap.josm.gui.MainApplication.getLayerManager; 4 5 import static org.openstreetmap.josm.tools.I18n.tr; 5 6 6 7 import java.util.ArrayList; 7 8 import java.util.Arrays; 8 9 import java.util.Collection; 9 import java.util.LinkedHashSet; 10 import java.util.Collections; 11 import java.util.HashSet; 10 12 import java.util.List; 11 13 import java.util.Set; 14 import java.util.stream.Collectors; 12 15 16 import org.openstreetmap.josm.data.coor.EastNorth; 13 17 import org.openstreetmap.josm.data.osm.Node; 14 18 import org.openstreetmap.josm.data.osm.OsmPrimitive; 15 19 import org.openstreetmap.josm.data.osm.Relation; 16 20 import org.openstreetmap.josm.data.osm.RelationMember; 17 21 import org.openstreetmap.josm.data.osm.Way; 22 import org.openstreetmap.josm.data.osm.WaySegment; 18 23 import org.openstreetmap.josm.data.osm.visitor.paint.relations.Multipolygon; 19 24 import org.openstreetmap.josm.data.osm.visitor.paint.relations.Multipolygon.JoinedWay; 20 25 import org.openstreetmap.josm.data.osm.visitor.paint.relations.MultipolygonCache; … … 23 28 import org.openstreetmap.josm.data.validation.TestError; 24 29 import org.openstreetmap.josm.gui.progress.ProgressMonitor; 25 30 import org.openstreetmap.josm.tools.Geometry; 31 import org.openstreetmap.josm.tools.Pair; 26 32 27 33 /** 28 * Checks for nodes in power lines/minor_lines that do not have a power=tower/pole tag.<br> 29 * See #7812 for discussions about this test. 34 * Checks for 35 * <ul> 36 * <li>nodes in power lines/minor_lines that do not have a power=tower/pole/portal tag 37 * <li>nodes where the reference numbering not consistent 38 * <li>ways where are unusually long segments without line support feature 39 * <li>ways where the line type is possibly misused 40 * </ul> 41 * See #7812 and #20716 for discussions about this test. 30 42 */ 31 43 public class PowerLines extends Test { 32 44 33 /** Test identifier */34 protected static final int POWER_ LINES= 2501;45 /** Test identifiers */ 46 protected static final int POWER_SUPPORT = 2501; 35 47 protected static final int POWER_CONNECTION = 2502; 48 protected static final int POWER_SEGMENT_LENGTH = 2503; 49 protected static final int POWER_REF = 2504; 50 protected static final int POWER_LINE_TYPE = 2505; 36 51 37 52 /** Values for {@code power} key interpreted as power lines */ 38 53 static final Collection<String> POWER_LINE_TAGS = Arrays.asList("line", "minor_line"); 39 54 /** Values for {@code power} key interpreted as power towers */ 40 static final Collection<String> POWER_TOWER_TAGS = Arrays.asList(" tower", "pole");55 static final Collection<String> POWER_TOWER_TAGS = Arrays.asList("catenary_mast", "pole", "portal", "tower"); 41 56 /** Values for {@code power} key interpreted as power stations */ 42 static final Collection<String> POWER_STATION_TAGS = Arrays.asList(" station", "sub_station", "substation", "plant", "generator");57 static final Collection<String> POWER_STATION_TAGS = Arrays.asList("generator", "plant", "substation"); 43 58 /** Values for {@code building} key interpreted as power stations */ 44 59 static final Collection<String> BUILDING_STATION_TAGS = Arrays.asList("transformer_tower"); 45 60 /** Values for {@code power} key interpreted as allowed power items */ 46 static final Collection<String> POWER_ ALLOWED_TAGS = Arrays.asList("switch", "transformer", "busbar", "generator", "switchgear",47 " portal", "terminal", "insulator");61 static final Collection<String> POWER_INFRASTRUCTURE_TAGS = Arrays.asList("compensator", "converter", 62 "generator", "insulator", "switch", "switchgear", "terminal", "transformer"); 48 63 49 private final Set<Node> badConnections = new LinkedHashSet<>(); 50 private final Set<Node> missingTowerOrPole = new LinkedHashSet<>(); 64 private final Set<Node> badConnections = new HashSet<>(); 65 private final Set<Node> missingTags = new HashSet<>(); 66 private final Set<Node> refDiscontinuities = new HashSet<>(); 67 private final Set<Way> wrongLineType = new HashSet<>(); 68 private final Set<WaySegment> missingNodes = new HashSet<>(); 51 69 52 70 private final List<OsmPrimitive> powerStations = new ArrayList<>(); 53 71 72 private final Collection<Way> datasetWaterways = new HashSet<>(64); 73 54 74 /** 55 75 * Constructs a new {@code PowerLines} test. 56 76 */ 57 77 public PowerLines() { 58 super(tr("Power lines"), tr("Checks for nodes in power lines that do not have a power=tower/pole tag.")); 59 } 60 61 @Override 62 public void visit(Way w) { 63 if (w.isUsable()) { 64 if (isPowerLine(w) && !w.hasTag("location", "underground")) { 65 for (Node n : w.getNodes()) { 66 if (!isPowerTower(n) && !isPowerAllowed(n) && IN_DOWNLOADED_AREA.test(n) 67 && (!w.isFirstLastNode(n) || !isPowerStation(n))) { 68 missingTowerOrPole.add(n); 69 } 70 } 71 } else if (w.isClosed() && isPowerStation(w)) { 72 powerStations.add(w); 73 } 74 } 78 super(tr("Power lines"), tr("Checks if power line missing a support node and " + 79 "for nodes in power lines that do not have a power=tower/pole tag")); 80 } 81 82 /** 83 * Power line support features ref=* numbering direction. 84 */ 85 private enum NumberingDirection { 86 /** No direction */ 87 NONE, 88 /** Numbering follows way direction */ 89 SAME, 90 /** Numbering goes opposite way direction */ 91 OPPOSITE, 92 /** Not distinguishable numbering direction */ 93 MIXED 75 94 } 76 95 77 96 @Override … … 89 108 badConnections.add(n); 90 109 } 91 110 92 private static boolean isRelatedToPower(Way way) { 93 if (way.hasTag("power") || way.hasTag("building")) 94 return true; 95 for (OsmPrimitive ref : way.getReferrers()) { 96 if (ref instanceof Relation && ref.isMultipolygon() && (ref.hasTag("power") || ref.hasTag("building"))) { 97 for (RelationMember rm : ((Relation) ref).getMembers()) { 98 if (way == rm.getMember()) 99 return true; 111 @Override 112 public void visit(Way w) { 113 if (!isPrimitiveUsable(w)) return; 114 115 if (isPowerLine(w) && !w.hasKey("line") && !w.hasTag("location", "underground")) { 116 final int segmentCount = w.getNodesCount() - 1; 117 final double mean = w.getLength() / segmentCount; 118 final double stdDev = calculateStdDev(w.getSegmentLengths(), mean); 119 int poleCount = 0; 120 int towerCount = 0; 121 Node prevNode = w.firstNode(); 122 123 double threshold = w.hasTag("power", "line") ? 1.6 : 1.8; 124 if (mean / stdDev < 4) { 125 //compensate for possibly hilly areas where towers can't be put anywhere 126 threshold += 0.2; 127 } 128 129 for (int i = 1; i < w.getRealNodesCount(); i++) { 130 final Node n = w.getNode(i); 131 132 /// handle power station line connections (eg. power=line + line=*) 133 if (isConnectedToStationLine(n, w)) { 134 prevNode = n; 135 continue; // skip, it would be false positive 100 136 } 137 138 /// handle missing power line support tags (eg. tower) 139 if (!isPowerTower(n) && !isPowerInfrastructure(n) && IN_DOWNLOADED_AREA.test(n) 140 && (!w.isFirstLastNode(n) || !isPowerStation(n))) 141 missingTags.add(n); 142 143 /// handle missing nodes 144 double segmentLen = n.getCoor().greatCircleDistance(prevNode.getCoor()); 145 146 // check power=line waterway crossing 147 final Pair<Node, Node> pair = Pair.create(prevNode, n); 148 final Set<Way> crossingWaterWays = new HashSet<>(8); 149 final Set<Node> crossingPositions = new HashSet<>(8); 150 findCrossings(datasetWaterways, w, pair, crossingWaterWays, crossingPositions); 151 152 if (!crossingWaterWays.isEmpty()) { 153 double compensation = calculateIntersectingLen(prevNode, crossingPositions); 154 segmentLen -= compensation; 155 } 156 157 if (segmentCount > 4 && segmentLen > mean * threshold && !isPowerInfrastructure(n)) 158 missingNodes.add(WaySegment.forNodePair(w, prevNode, n)); 159 160 /// handle wrong line types 161 if (n.hasTag("power", "pole")) 162 poleCount++; 163 else if (n.hasTag("power", "tower")) 164 towerCount++; 165 166 prevNode = n; 101 167 } 168 169 /// handle ref=* numbering discontinuities 170 checkDiscontinuities(w); 171 172 /// handle wrong line types 173 if ((poleCount > towerCount && w.hasTag("power", "line")) || 174 (poleCount < towerCount && w.hasTag("power", "minor_line"))) 175 wrongLineType.add(w); 176 177 } else if (w.isClosed() && isPowerStation(w)) { 178 powerStations.add(w); 102 179 } 103 return false;104 180 } 105 181 106 182 @Override … … 113 189 @Override 114 190 public void startTest(ProgressMonitor progressMonitor) { 115 191 super.startTest(progressMonitor); 116 clearCollections(); 192 setShowElements(true); 193 194 // collect all waterways 195 getLayerManager() 196 .getActiveDataSet() 197 .getWays() 198 .parallelStream() 199 .filter(way -> 200 way.hasTag("water", "river", "lake") || 201 way.hasKey("waterway") || 202 way.referrers(Relation.class) 203 .collect(Collectors.toSet()) 204 // no parallel stream here, just makes the iteration slower 205 .stream() 206 .anyMatch(relation -> relation.hasTag("water", "river", "lake") || 207 relation.hasKey("waterway") 208 )) 209 .forEach(datasetWaterways::add); 117 210 } 118 211 119 212 @Override 120 213 public void endTest() { 121 for (Node n : missingT owerOrPole) {214 for (Node n : missingTags) { 122 215 if (!isInPowerStation(n)) { 123 errors.add(TestError.builder(this, Severity.WARNING, POWER_LINES) 124 .message(tr("Missing power tower/pole within power line")) 216 errors.add(TestError.builder(this, Severity.WARNING, POWER_SUPPORT) 217 // the "missing tag" grouping can become broken if the MapCSS message get reworded 218 .message(tr("missing tag"), tr("node without power=*")) 125 219 .primitives(n) 126 220 .build()); 127 221 } … … 130 224 for (Node n : badConnections) { 131 225 errors.add(TestError.builder(this, Severity.WARNING, POWER_CONNECTION) 132 226 .message(tr("Node connects a power line or cable with an object " 133 + "which is not related to the power infrastructure.")) 134 .primitives(n).build()); 227 + "which is not related to the power infrastructure")) 228 .primitives(n) 229 .build()); 230 } 231 232 for (WaySegment segment : missingNodes) { 233 errors.add(TestError.builder(this, Severity.WARNING, POWER_SEGMENT_LENGTH) 234 .message(tr("Missing line support node within power line")) 235 .primitives(segment.getFirstNode(), segment.getSecondNode()) 236 .highlightWaySegments(new HashSet<>(Collections.singleton(segment))) 237 .build()); 135 238 } 136 clearCollections(); 239 240 for (Node n : refDiscontinuities) { 241 errors.add(TestError.builder(this, Severity.WARNING, POWER_REF) 242 .message(tr("Mixed reference numbering")) 243 .primitives(n) 244 .build()); 245 } 246 247 for (Way w : wrongLineType) { 248 errors.add(TestError.builder(this, Severity.WARNING, POWER_LINE_TYPE) 249 .message(tr("Possibly wrong power line type used")) 250 .primitives(w) 251 .build()); 252 } 253 137 254 super.endTest(); 138 255 } 139 256 257 /** 258 * Checks reference numbering discontinuities on the given way. 259 * @param w A way to be checked for ref=* discontinuities 260 */ 261 private void checkDiscontinuities(Way w) { 262 final List<Integer> discontinuityAtIndexes = new ArrayList<>(); 263 final NumberingDirection direction = detectDirection(w, discontinuityAtIndexes); 264 265 if (direction == NumberingDirection.MIXED) { 266 for (int disC : discontinuityAtIndexes) { 267 refDiscontinuities.add(w.getNode(disC)); 268 } 269 } 270 } 271 272 /** 273 * Calculates the standard deviation from the given way segment lengths. 274 * @param segmentLengths Segment lengths of a way 275 * @param mean Precalculated average segment length of a way 276 * @return standard deviation of the given way segment lengths 277 */ 278 private static double calculateStdDev(double[] segmentLengths, double mean) { 279 double standardDeviation = 0; 280 int size = segmentLengths.length; 281 282 for (double length : segmentLengths) { 283 standardDeviation += Math.pow(length - mean, 2); 284 } 285 286 return Math.sqrt(standardDeviation / size); 287 } 288 289 /** 290 * The summarized length (in metres) of a way where a power line hangs over a water area. 291 * @param ref Reference point 292 * @param crossingNodes Crossing nodes, unordered 293 * @return The summarized length (in metres) of a way where a power line hangs over a water area 294 */ 295 private static double calculateIntersectingLen(Node ref, Set<Node> crossingNodes) { 296 double min = Double.POSITIVE_INFINITY; 297 double max = Double.NEGATIVE_INFINITY; 298 299 for (Node n : crossingNodes) { 300 double dist = ref.getCoor().greatCircleDistance(n.getCoor()); 301 302 if (dist < min) 303 min = dist; 304 if (dist > max) 305 max = dist; 306 } 307 return max - min; 308 } 309 310 /** 311 * Searches for way intersections, which intersect the {@code pair} attribute. 312 * @param ways collection of ways to search 313 * @param parent parent way for {@code pair} param 314 * @param pair {@link Node} pair among which search for another way 315 * @param crossingWays found crossing ways 316 * @param crossingPositions collection of the crossing positions 317 * @implNote Inspired by {@code utilsplugin2/selection/NodeWayUtils.java#addWaysIntersectingWay()} 318 */ 319 private static void findCrossings(Collection<Way> ways, Way parent, Pair<Node, Node> pair, Set<Way> crossingWays, 320 Set<Node> crossingPositions) { 321 ways.parallelStream() 322 .filter(way -> !way.isDisabled() 323 && !crossingWays.contains(way) 324 && way.getBBox().intersects(parent.getBBox())) 325 .forEach(way -> way.getNodePairs(false) 326 .forEach(pair2 -> { 327 EastNorth eastNorth = Geometry.getSegmentSegmentIntersection( 328 pair.a.getEastNorth(), pair.b.getEastNorth(), 329 pair2.a.getEastNorth(), pair2.b.getEastNorth()); 330 if (eastNorth != null) { 331 crossingWays.add(way); 332 crossingPositions.add(new Node(eastNorth)); 333 } 334 }) 335 ); 336 } 337 338 /** 339 * Detects ref=* numbering direction. Ignores the first and last node, because ways can be connected and the 340 * connection node could have a different numbering. 341 * @param way Way to check 342 * @param discontinuityAtIndex List of node indexes where the discontinuity found 343 * @return way numbering direction 344 */ 345 private static NumberingDirection detectDirection(Way way, List<Integer> discontinuityAtIndex) { 346 final Set<NumberingDirection> directions = new HashSet<>(4); 347 final int waySize = way.getNodesCount(); 348 int prevRef = -1; 349 int ref; 350 351 // skip first and last node 352 for (int i = 1; i < waySize - 1; i++) { 353 final Node n = way.getNode(i); 354 355 if (n.hasTag("ref")) { 356 try { 357 ref = Integer.parseInt(n.get("ref")); 358 359 if (i == 1) { 360 prevRef = ref; 361 continue; 362 } 363 364 if (ref > prevRef) { 365 directions.add(NumberingDirection.SAME); 366 } else if (ref < prevRef) { 367 directions.add(NumberingDirection.OPPOSITE); 368 } else { 369 directions.add(NumberingDirection.MIXED); 370 discontinuityAtIndex.add(i); 371 } 372 373 prevRef = ref; 374 } catch (NumberFormatException ignore) { 375 prevRef = getGuessedRef(prevRef, directions); 376 } 377 } else if (prevRef != -1) { 378 prevRef = getGuessedRef(prevRef, directions); 379 } 380 } 381 382 if (directions.isEmpty()) 383 return NumberingDirection.NONE; 384 else if (directions.size() > 1) 385 return NumberingDirection.MIXED; 386 else 387 return directions.stream().findAny().get(); 388 } 389 390 /** 391 * Guesses the ref=* number based on previous value and the recognized direction. 392 * @param prevRef Previous node ref number 393 * @param directions Recognised way numbering direction 394 * @return Guessed ref number of the next power=pole/tower 395 */ 396 private static int getGuessedRef(int prevRef, Set<NumberingDirection> directions) { 397 if (directions.size() == 1) { 398 NumberingDirection direction = directions.stream().findFirst().get(); 399 if (direction == NumberingDirection.SAME) 400 prevRef += 1; 401 else if (direction == NumberingDirection.OPPOSITE) 402 prevRef -= 1; 403 } 404 return prevRef; 405 } 406 407 private static boolean isRelatedToPower(Way way) { 408 if (way.hasTag("power") || way.hasTag("building")) 409 return true; 410 for (OsmPrimitive ref : way.getReferrers()) { 411 if (ref instanceof Relation && ref.isMultipolygon() && (ref.hasTag("power") || ref.hasTag("building"))) { 412 for (RelationMember rm : ((Relation) ref).getMembers()) { 413 if (way == rm.getMember()) 414 return true; 415 } 416 } 417 } 418 return false; 419 } 420 421 /** 422 * Determines if the current node connected to a line which used usually used inside power stations. 423 * @param n node to check 424 * @param w parent way of {@code n} 425 * @return {@code true} if {@code n} connected to power=line + line=* 426 */ 427 private static boolean isConnectedToStationLine(Node n, Way w) { 428 for (OsmPrimitive p : n.getReferrers()) { 429 if (p instanceof Way && !p.equals(w) && isPowerLine((Way) p) && p.hasKey("line")) 430 return true; 431 } 432 return false; 433 } 434 435 /** 436 * Checks if the given node is inside a power station. 437 * @param n Node to checked 438 */ 140 439 protected final boolean isInPowerStation(Node n) { 141 440 for (OsmPrimitive station : powerStations) { 142 441 List<List<Node>> nodesLists = new ArrayList<>(); … … 171 470 /** 172 471 * Determines if the specified primitive denotes a power station. 173 472 * @param p The primitive to be tested 174 * @return {@code true} if power key is set and equal to station/sub_station/plant473 * @return {@code true} if power key is set and equal to generator/substation/plant 175 474 */ 176 475 protected static final boolean isPowerStation(OsmPrimitive p) { 177 476 return isPowerIn(p, POWER_STATION_TAGS) || isBuildingIn(p, BUILDING_STATION_TAGS); 178 477 } 179 478 180 479 /** 181 * Determines if the specified node denotes a power tower/pole.480 * Determines if the specified node denotes a power support feature. 182 481 * @param n The node to be tested 183 * @return {@code true} if power key is set and equal to tower/pole482 * @return {@code true} if power key is set and equal to pole/tower/portal/catenary_mast 184 483 */ 185 484 protected static final boolean isPowerTower(Node n) { 186 485 return isPowerIn(n, POWER_TOWER_TAGS); … … 189 488 /** 190 489 * Determines if the specified node denotes a power infrastructure allowed on a power line. 191 490 * @param n The node to be tested 192 * @return True if power key is set and equal to switch/tranformer/busbar/generator 491 * @return True if power key is set and equal to compensator/converter/generator/insulator 492 * /switch/switchgear/terminal/transformer 193 493 */ 194 protected static final boolean isPower Allowed(Node n) {195 return isPowerIn(n, POWER_ ALLOWED_TAGS);494 protected static final boolean isPowerInfrastructure(Node n) { 495 return isPowerIn(n, POWER_INFRASTRUCTURE_TAGS); 196 496 } 197 497 198 498 /** … … 215 515 return p.hasTag("building", values); 216 516 } 217 517 218 private void clearCollections() { 518 @Override 519 public void clear() { 520 super.clear(); 219 521 powerStations.clear(); 220 522 badConnections.clear(); 221 missingTowerOrPole.clear(); 523 missingTags.clear(); 524 missingNodes.clear(); 525 wrongLineType.clear(); 526 refDiscontinuities.clear(); 527 datasetWaterways.clear(); 222 528 } 223 529 }