Ticket #20716: josm_20716_power_v11.patch
File josm_20716_power_v11.patch, 42.2 KB (added by , 2 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 * Replies the segment lengths of the way as computed by {@link LatLon#greatCircleDistance}. 655 * 656 * @return The segment lengths of a way in metres, following way direction 657 * @since xxx 658 */ 659 public double[] getSegmentLengths() { 660 List<Double> segmentLengths = new ArrayList<>(); 661 Node lastN = null; 662 for (Node n:nodes) { 663 if (lastN != null) { 664 LatLon lastNcoor = lastN.getCoor(); 665 LatLon coor = n.getCoor(); 666 if (lastNcoor != null && coor != null) { 667 double distance = coor.greatCircleDistance(lastNcoor); 668 segmentLengths.add(distance); 669 } 670 } 671 lastN = n; 672 } 673 return segmentLengths.stream().mapToDouble(i -> i).toArray(); 674 } 675 653 676 /** 654 677 * Replies the length of the longest segment of the way, in metres, as computed by {@link LatLon#greatCircleDistance}. 655 678 * @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.EnumSet; 12 import java.util.HashSet; 10 13 import java.util.List; 11 14 import java.util.Set; 12 15 16 import org.openstreetmap.josm.data.coor.EastNorth; 17 import org.openstreetmap.josm.data.coor.LatLon; 13 18 import org.openstreetmap.josm.data.osm.Node; 14 19 import org.openstreetmap.josm.data.osm.OsmPrimitive; 15 20 import org.openstreetmap.josm.data.osm.Relation; 16 21 import org.openstreetmap.josm.data.osm.RelationMember; 17 22 import org.openstreetmap.josm.data.osm.Way; 23 import org.openstreetmap.josm.data.osm.WaySegment; 18 24 import org.openstreetmap.josm.data.osm.visitor.paint.relations.Multipolygon; 19 25 import org.openstreetmap.josm.data.osm.visitor.paint.relations.Multipolygon.JoinedWay; 20 26 import org.openstreetmap.josm.data.osm.visitor.paint.relations.MultipolygonCache; 27 import org.openstreetmap.josm.data.preferences.sources.ValidatorPrefHelper; 21 28 import org.openstreetmap.josm.data.validation.Severity; 22 29 import org.openstreetmap.josm.data.validation.Test; 23 30 import org.openstreetmap.josm.data.validation.TestError; 24 31 import org.openstreetmap.josm.gui.progress.ProgressMonitor; 32 import org.openstreetmap.josm.spi.preferences.Config; 25 33 import org.openstreetmap.josm.tools.Geometry; 34 import org.openstreetmap.josm.tools.Logging; 35 import org.openstreetmap.josm.tools.Pair; 36 import org.openstreetmap.josm.tools.Utils; 26 37 27 38 /** 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. 39 * Checks for 40 * <ul> 41 * <li>nodes in power lines/minor_lines that do not have a power=tower/pole/portal tag 42 * <li>nodes where the reference numbering not consistent 43 * <li>ways where are unusually long segments without line support feature 44 * <li>ways where the line type is possibly misused 45 * </ul> 46 * See #7812 and #20716 for discussions about this test. 30 47 */ 31 48 public class PowerLines extends Test { 32 49 33 / ** Test identifier */34 protected static final int POWER_ LINES= 2501;50 // Test identifiers 51 protected static final int POWER_SUPPORT = 2501; 35 52 protected static final int POWER_CONNECTION = 2502; 53 protected static final int POWER_SEGMENT_LENGTH = 2503; 54 protected static final int POWER_LOCAL_REF_CONTINUITY = 2504; 55 protected static final int POWER_WAY_REF_CONTINUITY = 2505; 56 protected static final int POWER_LINE_TYPE = 2506; 57 58 protected static final String PREFIX = ValidatorPrefHelper.PREFIX + "." + PowerLines.class.getSimpleName(); 59 private double hillyCompensation; 60 private double hillyThreshold; 36 61 37 62 /** Values for {@code power} key interpreted as power lines */ 38 63 static final Collection<String> POWER_LINE_TAGS = Arrays.asList("line", "minor_line"); 39 64 /** Values for {@code power} key interpreted as power towers */ 40 static final Collection<String> POWER_TOWER_TAGS = Arrays.asList(" tower", "pole");65 static final Collection<String> POWER_TOWER_TAGS = Arrays.asList("catenary_mast", "pole", "portal", "tower"); 41 66 /** 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");67 static final Collection<String> POWER_STATION_TAGS = Arrays.asList("generator", "plant", "substation"); 43 68 /** Values for {@code building} key interpreted as power stations */ 44 69 static final Collection<String> BUILDING_STATION_TAGS = Arrays.asList("transformer_tower"); 45 70 /** 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", "connection");71 static final Collection<String> POWER_INFRASTRUCTURE_TAGS = Arrays.asList("compensator", "connection", "converter", 72 "generator", "insulator", "switch", "switchgear", "terminal", "transformer"); 48 73 49 private final Set<Node> badConnections = new LinkedHashSet<>(); 50 private final Set<Node> missingTowerOrPole = new LinkedHashSet<>(); 74 private final Set<Node> badConnections = new HashSet<>(); 75 private final Set<Node> missingTags = new HashSet<>(); 76 private final Set<Way> wrongLineType = new HashSet<>(); 77 private final Set<WaySegment> missingNodes = new HashSet<>(); 78 private final Set<OsmPrimitive> refDiscontinuities = new HashSet<>(); 51 79 80 private final List<Set<Node>> segmentRefDiscontinuities = new ArrayList<>(); 52 81 private final List<OsmPrimitive> powerStations = new ArrayList<>(); 53 82 83 private final Collection<Way> datasetWaterways = new HashSet<>(64); 84 54 85 /** 55 86 * Constructs a new {@code PowerLines} test. 56 87 */ 57 88 public PowerLines() { 58 super(tr("Power lines"), tr("Checks for nodes in power lines that do not have a power=tower/pole/connection 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 } 89 super(tr("Power lines"), tr("Checks if power line missing a support node and " + 90 "for nodes in power lines that do not have a power=tower/pole tag")); 91 } 92 93 /** Power line support features ref=* numbering direction. */ 94 private enum NumberingDirection { 95 /** No direction */ 96 NONE, 97 /** Numbering follows way direction */ 98 SAME, 99 /** Numbering goes opposite way direction */ 100 OPPOSITE 75 101 } 76 102 77 103 @Override … … 81 107 for (Way parent : n.getParentWays()) { 82 108 if (parent.hasTag("power", "line", "minor_line", "cable")) 83 109 nodeInLineOrCable = true; 84 else if (!isRelatedToPower(parent)) {110 else if (!isRelatedToPower(parent)) 85 111 connectedToUnrelated = true; 86 }87 112 } 88 113 if (nodeInLineOrCable && connectedToUnrelated) 89 114 badConnections.add(n); 90 115 } 91 116 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; 117 @Override 118 public void visit(Way w) { 119 if (!isPrimitiveUsable(w)) return; 120 121 if (isPowerLine(w) && !w.hasKey("line") && !w.hasTag("location", "underground")) { 122 final int segmentCount = w.getNodesCount() - 1; 123 final double mean = w.getLength() / segmentCount; 124 final double stdDev = Utils.getStandardDeviation(w.getSegmentLengths(), mean); 125 final boolean isContinuesAsMinorLine = isContinuesAsMinorLine(w); 126 boolean isCrossingWater = false; 127 int poleCount = 0; 128 int towerCount = 0; 129 Node prevNode = w.firstNode(); 130 131 double baseThreshold = w.hasTag("power", "line") ? 1.6 : 1.8; 132 if (mean / stdDev < hillyThreshold) { 133 //compensate for possibly hilly areas where towers can't be put anywhere 134 baseThreshold += hillyCompensation; 135 } 136 137 for (int i = 0; i < w.getRealNodesCount(); i++) { 138 final Node n = w.getNode(i); 139 140 /// handle power station line connections (e.g. power=line + line=*) 141 if (isConnectedToStationLine(n, w) || n.hasTag("power", "connection")) { 142 prevNode = n; 143 continue; // skip, it would be false positive 100 144 } 145 146 /// handle missing power line support tags (e.g. tower) 147 if (!isPowerTower(n) && !isPowerInfrastructure(n) && IN_DOWNLOADED_AREA.test(n) 148 && (!w.isFirstLastNode(n) || !isPowerStation(n))) 149 missingTags.add(n); 150 151 /// handle missing nodes 152 double segmentLen = n.getCoor().greatCircleDistance(prevNode.getCoor()); 153 final Pair<Node, Node> pair = Pair.create(prevNode, n); 154 final Set<Way> crossingWaterWays = new HashSet<>(8); 155 final Set<Node> crossingPositions = new HashSet<>(8); 156 findCrossings(datasetWaterways, w, pair, crossingWaterWays, crossingPositions); 157 158 if (!crossingWaterWays.isEmpty()) { 159 double compensation = calculateIntersectingLen(prevNode, crossingPositions); 160 segmentLen -= compensation; 161 } 162 163 if (segmentCount > 4 164 && segmentLen > mean * baseThreshold 165 && !isPowerInfrastructure(n) 166 && IN_DOWNLOADED_AREA.test(n)) 167 missingNodes.add(WaySegment.forNodePair(w, prevNode, n)); 168 169 /// handle wrong line types 170 if (!crossingWaterWays.isEmpty()) 171 isCrossingWater = true; 172 173 if (n.hasTag("power", "pole")) 174 poleCount++; 175 else if (n.hasTag("power", "tower", "portal")) 176 towerCount++; 177 178 prevNode = n; 101 179 } 180 181 /// handle ref=* numbering discontinuities 182 if (detectDiscontinuity(w, refDiscontinuities, segmentRefDiscontinuities)) 183 refDiscontinuities.add(w); 184 185 /// handle wrong line types 186 if (((poleCount > towerCount && w.hasTag("power", "line")) 187 || (poleCount < towerCount && w.hasTag("power", "minor_line") 188 && !isCrossingWater 189 && !isContinuesAsMinorLine)) 190 && IN_DOWNLOADED_AREA.test(w)) 191 wrongLineType.add(w); 192 193 } else if (w.isClosed() && isPowerStation(w)) { 194 powerStations.add(w); 102 195 } 103 return false;104 196 } 105 197 106 198 @Override … … 113 205 @Override 114 206 public void startTest(ProgressMonitor progressMonitor) { 115 207 super.startTest(progressMonitor); 116 clearCollections(); 208 // the test run can take a bit of time, show detailed progress 209 setShowElements(true); 210 211 hillyCompensation = Config.getPref().getDouble(PREFIX + ".hilly_compensation", 0.2); 212 hillyThreshold = Config.getPref().getDouble(PREFIX + ".hilly_threshold", 4.0); 213 214 // collect all waterways 215 getLayerManager() 216 .getActiveDataSet() 217 .getWays() 218 .parallelStream() 219 .filter(way -> way.isUsable() && (concernsWaterArea(way) 220 || way.referrers(Relation.class).anyMatch(PowerLines::concernsWaterArea))) 221 .forEach(datasetWaterways::add); 117 222 } 118 223 119 224 @Override 120 225 public void endTest() { 121 for (Node n : missingT owerOrPole) {226 for (Node n : missingTags) { 122 227 if (!isInPowerStation(n)) { 123 errors.add(TestError.builder(this, Severity.WARNING, POWER_LINES) 124 .message(tr("Missing power tower/pole/connection within power line")) 228 errors.add(TestError.builder(this, Severity.WARNING, POWER_SUPPORT) 229 // the "missing tag" grouping can become broken if the MapCSS message get reworded 230 .message(tr("missing tag"), tr("node without power=*")) 125 231 .primitives(n) 126 232 .build()); 127 233 } … … 130 236 for (Node n : badConnections) { 131 237 errors.add(TestError.builder(this, Severity.WARNING, POWER_CONNECTION) 132 238 .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()); 239 + "which is not related to the power infrastructure")) 240 .primitives(n) 241 .build()); 242 } 243 244 for (WaySegment s : missingNodes) { 245 errors.add(TestError.builder(this, Severity.WARNING, POWER_SEGMENT_LENGTH) 246 .message(tr("Possibly missing line support node within power line")) 247 .primitives(s.getFirstNode(), s.getSecondNode()) 248 .highlightWaySegments(new HashSet<>(Collections.singleton(s))) 249 .build()); 135 250 } 136 clearCollections(); 251 252 for (OsmPrimitive p : refDiscontinuities) { 253 if (p instanceof Way) 254 errors.add(TestError.builder(this, Severity.WARNING, POWER_WAY_REF_CONTINUITY) 255 .message(tr("Mixed reference numbering")) 256 .primitives(p) 257 .build()); 258 } 259 260 final String discontinuityMsg = tr("Reference numbering don''t match majority of way''s nodes"); 261 262 for (OsmPrimitive p : refDiscontinuities) { 263 if (p instanceof Node) 264 errors.add(TestError.builder(this, Severity.WARNING, POWER_LOCAL_REF_CONTINUITY) 265 .message(discontinuityMsg) 266 .primitives(p) 267 .build()); 268 } 269 270 for (Set<Node> nodes : segmentRefDiscontinuities) { 271 errors.add(TestError.builder(this, Severity.WARNING, POWER_LOCAL_REF_CONTINUITY) 272 .message(discontinuityMsg) 273 .primitives(nodes) 274 .build()); 275 } 276 277 for (Way w : wrongLineType) { 278 errors.add(TestError.builder(this, Severity.WARNING, POWER_LINE_TYPE) 279 .message(tr("Possibly wrong power line type used")) 280 .primitives(w) 281 .build()); 282 } 283 137 284 super.endTest(); 138 285 } 139 286 287 /** 288 * The summarized length (in metres) of a way where a power line hangs over a water area. 289 * @param ref Reference point 290 * @param crossingNodes Crossing nodes, unordered 291 * @return The summarized length (in metres) of a way where a power line hangs over a water area 292 */ 293 private static double calculateIntersectingLen(Node ref, Set<Node> crossingNodes) { 294 double min = Double.POSITIVE_INFINITY; 295 double max = Double.NEGATIVE_INFINITY; 296 297 for (Node n : crossingNodes) { 298 LatLon refcoor = ref.getCoor(); 299 LatLon coor = n.getCoor(); 300 301 if (refcoor != null && coor != null) { 302 double dist = refcoor.greatCircleDistance(coor); 303 304 if (dist < min) 305 min = dist; 306 if (dist > max) 307 max = dist; 308 } 309 } 310 return max - min; 311 } 312 313 /** 314 * Searches for way intersections, which intersect the {@code pair} attribute. 315 * @param ways collection of ways to search 316 * @param parent parent way for {@code pair} param 317 * @param pair node pair among which search for another way 318 * @param crossingWays found crossing ways 319 * @param crossingPositions collection of the crossing positions 320 * @implNote Inspired by {@code utilsplugin2/selection/NodeWayUtils.java#addWaysIntersectingWay()} 321 */ 322 private static void findCrossings(Collection<Way> ways, Way parent, Pair<Node, Node> pair, Set<Way> crossingWays, 323 Set<Node> crossingPositions) { 324 for (Way way : ways) { 325 if (way.isUsable() 326 && !crossingWays.contains(way) 327 && way.getBBox().intersects(parent.getBBox())) { 328 for (Pair<Node, Node> pair2 : way.getNodePairs(false)) { 329 EastNorth eastNorth = Geometry.getSegmentSegmentIntersection( 330 pair.a.getEastNorth(), pair.b.getEastNorth(), 331 pair2.a.getEastNorth(), pair2.b.getEastNorth()); 332 if (eastNorth != null) { 333 crossingPositions.add(new Node(eastNorth)); 334 crossingWays.add(way); 335 } 336 } 337 } 338 } 339 } 340 341 /** Helper class for reference numbering test. Used for storing continuous reference segment info. */ 342 private static class SegmentInfo { 343 /** Node index, follows way direction */ 344 private final int startIndex; 345 /** ref=* value at {@link SegmentInfo#startIndex} */ 346 private final int startRef; 347 /** Segment length */ 348 private final int length; 349 /** Segment direction */ 350 private final NumberingDirection direction; 351 352 SegmentInfo(int startIndex, int length, int ref, NumberingDirection direction) { 353 this.startIndex = startIndex; 354 this.length = length; 355 this.direction = direction; 356 357 if (direction == NumberingDirection.SAME) 358 this.startRef = ref - length; 359 else 360 this.startRef = ref + length; 361 362 if (length == 0 && direction != NumberingDirection.NONE) { 363 throw new IllegalArgumentException("When segment length is zero, the direction should be NONE"); 364 } 365 } 366 367 @Override 368 public String toString() { 369 return String.format("SegmentInfo{startIndex=%d, startRef=%d, length=%d, direction=%s}", 370 startIndex, startRef, length, direction); 371 } 372 } 373 374 /** 375 * Detects ref=* numbering discontinuities in the given way. 376 * @param way checked way 377 * @param nRefDiscontinuities single node ref=* discontinuities 378 * @param sRefDiscontinuities continuous node ref=* discontinuities 379 * @return {@code true} if warning needs to be issued for the whole way 380 */ 381 static boolean detectDiscontinuity(Way way, Set<OsmPrimitive> nRefDiscontinuities, List<Set<Node>> sRefDiscontinuities) { 382 final RefChecker checker = new RefChecker(way); 383 final ArrayList<SegmentInfo> segments = checker.getSegments(); 384 final SegmentInfo referenceSegment = checker.getLongestSegment(); 385 386 if (referenceSegment == null) 387 return !segments.isEmpty(); 388 389 // collect disconnected ref segments which are not align up to the reference 390 for (SegmentInfo segment : segments) { 391 if (!isSegmentAlign(referenceSegment, segment)) { 392 if (referenceSegment.length == 0) 393 return true; 394 395 if (segment.length == 0) { 396 nRefDiscontinuities.add(way.getNode(segment.startIndex)); 397 } else { 398 Set<Node> nodeGroup = new HashSet<>(); 399 400 for (int i = segment.startIndex; i <= segment.startIndex + segment.length; i++) { 401 nodeGroup.add(way.getNode(i)); 402 } 403 sRefDiscontinuities.add(nodeGroup); 404 } 405 } 406 } 407 408 return false; 409 } 410 411 /** 412 * Checks if parameter segments align. The {@code reference} is expected to be at least as long as the {@code candidate}. 413 * @param reference Reference segment to check against 414 * @param candidate Candidate segment 415 * @return {@code true} if the two segments ref=* numbering align 416 */ 417 private static boolean isSegmentAlign(SegmentInfo reference, SegmentInfo candidate) { 418 if (reference.direction == NumberingDirection.NONE 419 || reference.direction == candidate.direction 420 || candidate.direction == NumberingDirection.NONE) 421 return Math.abs(candidate.startIndex - reference.startIndex) == Math.abs(candidate.startRef - reference.startRef); 422 return false; 423 } 424 425 /** 426 * Detects continuous reference numbering sequences. Ignores the first and last node because 427 * ways can be connected, and the connection nodes can have different numbering. 428 * <p> 429 * If the numbering switches in the middle of the way, this can also be seen as error, 430 * because line relations would require split ways. 431 */ 432 static class RefChecker { 433 private final ArrayList<SegmentInfo> segments = new ArrayList<>(); 434 private NumberingDirection direction = NumberingDirection.NONE; 435 private Integer startIndex; 436 private Integer previousRef; 437 438 RefChecker(final Way way) { 439 run(way); 440 } 441 442 private void run(Way way) { 443 final int wayLength = way.getNodesCount(); 444 445 // first and last node skipped 446 for (int i = 1; i < wayLength - 1; i++) { 447 Node n = way.getNode(i); 448 if (!isPowerTower(n)) { 449 continue; 450 } 451 maintain(parseRef(n.get("ref")), i); 452 } 453 454 // needed for creation of the last segment 455 maintain(null, wayLength - 1); 456 } 457 458 /** 459 * Maintains class variables and constructs a new segment when necessary. 460 * @param ref recognised ref=* number 461 * @param index node index in a {@link Way} 462 */ 463 private void maintain(Integer ref, int index) { 464 if (previousRef == null && ref != null) { 465 // ref change: null -> number 466 startIndex = index; 467 } else if (previousRef != null && ref == null) { 468 // ref change: number -> null 469 segments.add(new SegmentInfo(startIndex, index - 1 - startIndex, previousRef, direction)); 470 direction = NumberingDirection.NONE; // to fix directionality 471 } else if (previousRef != null && ref != null) { 472 // ref change: number -> number 473 if (Math.abs(ref - previousRef) != 1) { 474 segments.add(new SegmentInfo(startIndex, index - 1 - startIndex, previousRef, direction)); 475 startIndex = index; 476 previousRef = ref; // to fix directionality 477 } 478 direction = detectDirection(ref, previousRef); 479 } 480 previousRef = ref; 481 } 482 483 /** 484 * Parses integer tag values. Later can be relatively easily extended or rewritten to handle 485 * complex references like 25/A, 25/B etc. 486 * @param value the value to be parsed 487 * @return parsed int or {@code null} in case of {@link NumberFormatException} 488 */ 489 private static Integer parseRef(String value) { 490 try { 491 return Integer.parseInt(value); 492 } catch (NumberFormatException ignore) { 493 Logging.trace("The " + RefChecker.class + " couldn't parse ref=" + value + ", consider rewriting the parser"); 494 return null; 495 } 496 } 497 498 /** 499 * Detects numbering direction. The parameters should follow way direction. 500 * @param ref last known reference value 501 * @param previousRef reference value before {@code ref} 502 * @return recognised direction 503 */ 504 private static NumberingDirection detectDirection(int ref, int previousRef) { 505 if (ref > previousRef) 506 return NumberingDirection.SAME; 507 else if (ref < previousRef) 508 return NumberingDirection.OPPOSITE; 509 return NumberingDirection.NONE; 510 } 511 512 /** 513 * Calculates the longest segment. 514 * @return the longest segment, or the lowest index if there are more than one with same length and direction, 515 * or {@code null} if there are more than one with same length and different direction 516 */ 517 SegmentInfo getLongestSegment() { 518 final Set<NumberingDirection> directions = EnumSet.noneOf(NumberingDirection.class); 519 int longestLength = -1; 520 int counter = 0; 521 SegmentInfo longest = null; 522 523 for (SegmentInfo segment : segments) { 524 if (segment.length > longestLength) { 525 longestLength = segment.length; 526 longest = segment; 527 counter = 0; 528 directions.clear(); 529 directions.add(segment.direction); 530 } else if (segment.length == longestLength) { 531 counter++; 532 directions.add(segment.direction); 533 } 534 } 535 536 // there are multiple segments with the same longest length and their directions don't match 537 if (counter > 0 && directions.size() > 1) 538 return null; 539 540 return longest; 541 } 542 543 /** 544 * @return the detected segments 545 */ 546 ArrayList<SegmentInfo> getSegments() { 547 return segments; 548 } 549 } 550 551 private static boolean isRelatedToPower(Way way) { 552 if (way.hasTag("power") || way.hasTag("building")) 553 return true; 554 for (OsmPrimitive ref : way.getReferrers()) { 555 if (ref instanceof Relation && ref.isMultipolygon() && (ref.hasTag("power") || ref.hasTag("building"))) { 556 for (RelationMember rm : ((Relation) ref).getMembers()) { 557 if (way == rm.getMember()) 558 return true; 559 } 560 } 561 } 562 return false; 563 } 564 565 /** 566 * Determines if the current node connected to a line which used usually used inside power stations. 567 * @param n node to check 568 * @param w parent way of {@code n} 569 * @return {@code true} if {@code n} connected to power=line + line=* 570 */ 571 private static boolean isConnectedToStationLine(Node n, Way w) { 572 for (OsmPrimitive p : n.getReferrers()) { 573 if (p instanceof Way && !p.equals(w) && isPowerLine((Way) p) && p.hasKey("line")) 574 return true; 575 } 576 return false; 577 } 578 579 /** 580 * Checks if the way continues as a power=minor_line. 581 * @param way Way to be checked 582 * @return {@code true} if the way continues as a power=minor_line 583 */ 584 private static boolean isContinuesAsMinorLine(Way way) { 585 return way.firstNode().referrers(Way.class).filter(referrer -> !way.equals(referrer)).anyMatch(PowerLines::isMinorLine) || 586 way.lastNode().referrers(Way.class).filter(referrer -> !way.equals(referrer)).anyMatch(PowerLines::isMinorLine); 587 } 588 589 /** 590 * Checks if the given primitive denotes a power=minor_line. 591 * @param p primitive to be checked 592 * @return {@code true} if the given primitive denotes a power=minor_line 593 */ 594 private static boolean isMinorLine(OsmPrimitive p) { 595 return p.hasTag("power", "minor_line"); 596 } 597 598 /** 599 * Check if primitive has a tag that marks it as a water area or boundary of a water area. 600 * @param p the primitive 601 * @return {@code true} if primitive has a tag that marks it as a water area or boundary of a water area 602 */ 603 private static boolean concernsWaterArea(OsmPrimitive p) { 604 return p.hasTag("water", "river", "lake") || p.hasKey("waterway") || p.hasTag("natural", "coastline"); 605 } 606 607 /** 608 * Checks if the given node is inside a power station. 609 * @param n Node to be checked 610 * @return true if the given node is inside a power station 611 */ 140 612 protected final boolean isInPowerStation(Node n) { 141 613 for (OsmPrimitive station : powerStations) { 142 614 List<List<Node>> nodesLists = new ArrayList<>(); … … 171 643 /** 172 644 * Determines if the specified primitive denotes a power station. 173 645 * @param p The primitive to be tested 174 * @return {@code true} if power key is set and equal to station/sub_station/plant646 * @return {@code true} if power key is set and equal to generator/substation/plant 175 647 */ 176 648 protected static final boolean isPowerStation(OsmPrimitive p) { 177 649 return isPowerIn(p, POWER_STATION_TAGS) || isBuildingIn(p, BUILDING_STATION_TAGS); 178 650 } 179 651 180 652 /** 181 * Determines if the specified node denotes a power tower/pole.653 * Determines if the specified node denotes a power support feature. 182 654 * @param n The node to be tested 183 * @return {@code true} if power key is set and equal to tower/pole655 * @return {@code true} if power key is set and equal to pole/tower/portal/catenary_mast 184 656 */ 185 657 protected static final boolean isPowerTower(Node n) { 186 658 return isPowerIn(n, POWER_TOWER_TAGS); … … 189 661 /** 190 662 * Determines if the specified node denotes a power infrastructure allowed on a power line. 191 663 * @param n The node to be tested 192 * @return True if power key is set and equal to switch/tranformer/busbar/generator 664 * @return {@code true} if power key is set and equal to compensator/converter/generator/insulator 665 * /switch/switchgear/terminal/transformer 193 666 */ 194 protected static final boolean isPower Allowed(Node n) {195 return isPowerIn(n, POWER_ ALLOWED_TAGS);667 protected static final boolean isPowerInfrastructure(Node n) { 668 return isPowerIn(n, POWER_INFRASTRUCTURE_TAGS); 196 669 } 197 670 198 671 /** … … 215 688 return p.hasTag("building", values); 216 689 } 217 690 218 private void clearCollections() { 691 @Override 692 public void clear() { 693 super.clear(); 219 694 powerStations.clear(); 220 695 badConnections.clear(); 221 missingTowerOrPole.clear(); 696 missingTags.clear(); 697 missingNodes.clear(); 698 wrongLineType.clear(); 699 refDiscontinuities.clear(); 700 segmentRefDiscontinuities.clear(); 701 datasetWaterways.clear(); 222 702 } 223 703 } -
src/org/openstreetmap/josm/gui/dialogs/InspectPrimitiveDataText.java
IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 diff --git a/src/org/openstreetmap/josm/gui/dialogs/InspectPrimitiveDataText.java b/src/org/openstreetmap/josm/gui/dialogs/InspectPrimitiveDataText.java
a b 32 32 import org.openstreetmap.josm.data.projection.proj.TransverseMercator.Hemisphere; 33 33 import org.openstreetmap.josm.tools.Geometry; 34 34 import org.openstreetmap.josm.tools.Pair; 35 import org.openstreetmap.josm.tools.Utils; 35 36 36 37 /** 37 38 * Textual representation of primitive contents, used in {@code InspectPrimitiveDialog}. … … 174 175 add(tr("Centroid: "), toStringCSV(false, 175 176 ProjectionRegistry.getProjection().eastNorth2latlon(Geometry.getCentroid(((IWay<?>) o).getNodes())))); 176 177 if (o instanceof Way) { 177 double dist = ((Way) o).getLength(); 178 String distText = SystemOfMeasurement.getSystemOfMeasurement().getDistText(dist); 179 add(tr("Length: {0}", distText)); 178 double length = ((Way) o).getLength(); 179 String lenText = SystemOfMeasurement.getSystemOfMeasurement().getDistText(length); 180 add(tr("Length: {0}", lenText)); 181 182 double avgNodeDistance = length / (((Way) o).getNodesCount() - 1); 183 String nodeDistText = SystemOfMeasurement.getSystemOfMeasurement().getDistText(avgNodeDistance); 184 add(tr("Average segment length: {0}", nodeDistText)); 185 186 double stdDev = Utils.getStandardDeviation(((Way) o).getSegmentLengths(), avgNodeDistance); 187 String stdDevText = SystemOfMeasurement.getSystemOfMeasurement().getDistText(stdDev); 188 add(tr("Standard deviation: {0}", stdDevText)); 180 189 } 181 190 if (o instanceof Way && ((Way) o).concernsArea() && ((Way) o).isClosed()) { 182 191 double area = Geometry.closedWayArea((Way) o); -
src/org/openstreetmap/josm/tools/Utils.java
IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 diff --git a/src/org/openstreetmap/josm/tools/Utils.java b/src/org/openstreetmap/josm/tools/Utils.java
a b 1303 1303 } 1304 1304 } 1305 1305 1306 /** 1307 * Calculates the <a href="https://en.wikipedia.org/wiki/Standard_deviation">standard deviation</a> of population. 1308 * @param values an array of values 1309 * @return standard deviation of the given array, or -1.0 if the array has less than two values 1310 * @see #getStandardDeviation(double[], double) 1311 * @since xxx 1312 */ 1313 public static double getStandardDeviation(double[] values) { 1314 return getStandardDeviation(values, Double.NaN); 1315 } 1316 1317 /** 1318 * Calculates the <a href="https://en.wikipedia.org/wiki/Standard_deviation">standard deviation</a> of population with the given 1319 * mean value. 1320 * @param values an array of values 1321 * @param mean precalculated average value of the array 1322 * @return standard deviation of the given array, or -1.0 if the array has less than two values 1323 * @see #getStandardDeviation(double[]) 1324 * @since xxx 1325 */ 1326 public static double getStandardDeviation(double[] values, double mean) { 1327 if (values.length < 2) { 1328 return -1.0; 1329 } 1330 1331 double standardDeviation = 0; 1332 1333 if (Double.isNaN(mean)) { 1334 mean = Arrays.stream(values).average().orElse(0); 1335 } 1336 1337 for (double length : values) { 1338 standardDeviation += Math.pow(length - mean, 2); 1339 } 1340 1341 return Math.sqrt(standardDeviation / values.length); 1342 } 1343 1306 1344 /** 1307 1345 * A ForkJoinWorkerThread that will always inherit caller permissions, 1308 1346 * unlike JDK's InnocuousForkJoinWorkerThread, used if a security manager exists. -
new file test/unit/org/openstreetmap/josm/data/validation/tests/PowerLinesTest.java
IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 diff --git a/test/unit/org/openstreetmap/josm/data/validation/tests/PowerLinesTest.java b/test/unit/org/openstreetmap/josm/data/validation/tests/PowerLinesTest.java new file mode 100644
- + 1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.data.validation.tests; 3 4 import static org.junit.jupiter.api.Assertions.assertFalse; 5 import static org.junit.jupiter.api.Assertions.assertTrue; 6 7 import org.junit.jupiter.api.BeforeEach; 8 import org.junit.jupiter.api.Test; 9 import org.openstreetmap.josm.JOSMFixture; 10 import org.openstreetmap.josm.data.coor.LatLon; 11 import org.openstreetmap.josm.data.osm.DataSet; 12 import org.openstreetmap.josm.data.osm.Node; 13 import org.openstreetmap.josm.data.osm.TagMap; 14 import org.openstreetmap.josm.data.osm.Way; 15 import org.openstreetmap.josm.gui.MainApplication; 16 import org.openstreetmap.josm.gui.layer.OsmDataLayer; 17 18 19 public class PowerLinesTest { 20 private PowerLines powerLines; 21 private DataSet ds; 22 23 @BeforeEach 24 public void setUp() throws Exception { 25 JOSMFixture.createUnitTestFixture().init(); 26 ds = new DataSet(); 27 MainApplication.getLayerManager().addLayer(new OsmDataLayer(ds, null, null)); 28 29 powerLines = new PowerLines(); 30 powerLines.initialize(); 31 powerLines.startTest(null); 32 } 33 34 @Test 35 void testNoBreakInLine() { 36 Way powerline = new Way(); 37 powerline.setKeys(new TagMap("power", "line")); 38 ds.addPrimitive(powerline); 39 40 for (int i = 0; i < 10; i++) { 41 Node node = new Node(new LatLon(0, 0.001 * i)); 42 node.setKeys(new TagMap("power", "tower")); 43 ds.addPrimitive(node); 44 powerline.addNode(node); 45 } 46 powerLines.visit(powerline); 47 powerLines.endTest(); 48 assertTrue(powerLines.getErrors().isEmpty()); 49 } 50 51 @Test 52 void testBreakInLine() { 53 Way powerline = new Way(); 54 powerline.setKeys(new TagMap("power", "line")); 55 ds.addPrimitive(powerline); 56 57 for (int i = 0; i < 10; i++) { 58 if (i != 4 && i != 5) { 59 Node node = new Node(new LatLon(0, 0.001 * i)); 60 node.setKeys(new TagMap("power", "tower")); 61 ds.addPrimitive(node); 62 powerline.addNode(node); 63 } 64 } 65 powerLines.visit(powerline); 66 powerLines.endTest(); 67 assertFalse(powerLines.getErrors().isEmpty()); 68 } 69 70 @Test 71 void testConnectionAndRefInLine() { 72 Way powerline = new Way(); 73 powerline.setKeys(new TagMap("power", "line")); 74 ds.addPrimitive(powerline); 75 76 int connectionCount = 0; 77 78 for (int i = 0; i < 10; i++) { 79 Node node = new Node(new LatLon(0, 0.001 * i)); 80 node.setKeys(new TagMap("power", "tower", "ref", Integer.toString(i))); 81 if (i == 4 || i == 5) { 82 node.setKeys(new TagMap("power", "connection")); 83 connectionCount++; 84 } 85 if (i > 5) { 86 node.setKeys(new TagMap("power", "tower", "ref", Integer.toString(i - connectionCount))); 87 } 88 ds.addPrimitive(node); 89 powerline.addNode(node); 90 } 91 powerLines.visit(powerline); 92 powerLines.endTest(); 93 assertTrue(powerLines.getErrors().isEmpty()); 94 } 95 96 @Test 97 void testRefDiscontinuityInLine() { 98 Way powerline = new Way(); 99 powerline.setKeys(new TagMap("power", "minor_line")); 100 ds.addPrimitive(powerline); 101 102 for (int i = 0; i < 10; i++) { 103 Node node = new Node(new LatLon(0, 0.001 * i)); 104 node.setKeys(new TagMap("power", "tower", "ref", Integer.toString(i))); 105 if (i < 4) { 106 // add discontinuity 107 node.setKeys(new TagMap("power", "tower", "ref", Integer.toString(i + 1))); 108 } 109 ds.addPrimitive(node); 110 powerline.addNode(node); 111 } 112 powerLines.visit(powerline); 113 powerLines.endTest(); 114 assertFalse(powerLines.getErrors().isEmpty()); 115 } 116 } -
test/unit/org/openstreetmap/josm/tools/UtilsTest.java
IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 diff --git a/test/unit/org/openstreetmap/josm/tools/UtilsTest.java b/test/unit/org/openstreetmap/josm/tools/UtilsTest.java
a b 531 531 final String output = Utils.execOutput(Arrays.asList("echo", "Hello", "World")); 532 532 assertEquals("Hello World", output); 533 533 } 534 535 /** 536 * Test of {@link Utils#getStandardDeviation(double[])} and {@link Utils#getStandardDeviation(double[], double)} 537 */ 538 @Test 539 void testGetStandardDeviation() { 540 assertEquals(0.0, Utils.getStandardDeviation(new double[]{1, 1, 1, 1})); 541 assertEquals(0.0, Utils.getStandardDeviation(new double[]{1, 1, 1, 1}, 1.0)); 542 assertEquals(0.5, Utils.getStandardDeviation(new double[]{1, 1, 2, 2})); 543 544 assertEquals(-1.0, Utils.getStandardDeviation(new double[]{})); 545 assertEquals(-1.0, Utils.getStandardDeviation(new double[]{0})); 546 } 534 547 }