Ticket #20716: 20716.patch
| File 20716.patch, 42.7 KB (added by , 4 years ago) |
|---|
-
src/org/openstreetmap/josm/data/osm/Way.java
diff --git a/src/org/openstreetmap/josm/data/osm/Way.java b/src/org/openstreetmap/josm/data/osm/Way.java index b746a98891..a1eb5f60d8 100644
a b public final class Way extends OsmPrimitive implements IWay<Node> { 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
diff --git a/src/org/openstreetmap/josm/data/validation/tests/PowerLines.java b/src/org/openstreetmap/josm/data/validation/tests/PowerLines.java index b0c9f8bc08..abce756c79 100644
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.ILatLon; 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<>(32); 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.")); 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")); 59 91 } 60 92 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 } 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 … … public class PowerLines extends Test { 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 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<LatLon> 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; 100 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 … … public class PowerLines extends Test { 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 } … … public class PowerLines extends Test { 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()); 250 } 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()); 135 268 } 136 clearCollections(); 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<LatLon> crossingNodes) { 294 double min = Double.POSITIVE_INFINITY; 295 double max = Double.NEGATIVE_INFINITY; 296 297 LatLon refcoor = ref.getCoor(); 298 for (LatLon coor : crossingNodes) { 299 300 if (refcoor != null && coor != null) { 301 double dist = refcoor.greatCircleDistance(coor); 302 303 if (dist < min) 304 min = dist; 305 if (dist > max) 306 max = dist; 307 } 308 } 309 return max - min; 310 } 311 312 /** 313 * Searches for way intersections, which intersect the {@code pair} attribute. 314 * @param ways collection of ways to search 315 * @param parent parent way for {@code pair} param 316 * @param pair node pair among which search for another way 317 * @param crossingWays found crossing ways 318 * @param crossingPositions collection of the crossing positions 319 * @implNote Inspired by {@code utilsplugin2/selection/NodeWayUtils.java#addWaysIntersectingWay()} 320 */ 321 private static void findCrossings(Collection<Way> ways, Way parent, Pair<Node, Node> pair, Set<Way> crossingWays, 322 Set<LatLon> crossingPositions) { 323 for (Way way : ways) { 324 if (way.isUsable() 325 && !crossingWays.contains(way) 326 && way.getBBox().intersects(parent.getBBox())) { 327 for (Pair<Node, Node> pair2 : way.getNodePairs(false)) { 328 ILatLon ll = Geometry.getSegmentSegmentIntersection(pair.a, pair.b, pair2.a, pair2.b); 329 if (ll != null) { 330 crossingPositions.add(ll instanceof LatLon ? (LatLon) ll : new LatLon(ll)); 331 crossingWays.add(way); 332 } 333 } 334 } 335 } 336 } 337 338 /** Helper class for reference numbering test. Used for storing continuous reference segment info. */ 339 private static class SegmentInfo { 340 /** Node index, follows way direction */ 341 private final int startIndex; 342 /** ref=* value at {@link SegmentInfo#startIndex} */ 343 private final int startRef; 344 /** Segment length */ 345 private final int length; 346 /** Segment direction */ 347 private final NumberingDirection direction; 348 349 SegmentInfo(int startIndex, int length, int ref, NumberingDirection direction) { 350 this.startIndex = startIndex; 351 this.length = length; 352 this.direction = direction; 353 354 if (direction == NumberingDirection.SAME) 355 this.startRef = ref - length; 356 else 357 this.startRef = ref + length; 358 359 if (length == 0 && direction != NumberingDirection.NONE) { 360 throw new IllegalArgumentException("When the segment length is zero, the direction should be NONE"); 361 } 362 } 363 364 @Override 365 public String toString() { 366 return String.format("SegmentInfo{startIndex=%d, startRef=%d, length=%d, direction=%s}", 367 startIndex, startRef, length, direction); 368 } 369 } 370 371 /** 372 * Detects ref=* numbering discontinuities in the given way. 373 * @param way checked way 374 * @param nRefDiscontinuities single node ref=* discontinuities 375 * @param sRefDiscontinuities continuous node ref=* discontinuities 376 * @return {@code true} if warning needs to be issued for the whole way 377 */ 378 static boolean detectDiscontinuity(Way way, Set<OsmPrimitive> nRefDiscontinuities, List<Set<Node>> sRefDiscontinuities) { 379 final RefChecker checker = new RefChecker(way); 380 final List<SegmentInfo> segments = checker.getSegments(); 381 final SegmentInfo referenceSegment = checker.getLongestSegment(); 382 383 if (referenceSegment == null) 384 return !segments.isEmpty(); 385 386 // collect disconnected ref segments which are not align up to the reference 387 for (SegmentInfo segment : segments) { 388 if (!isSegmentAlign(referenceSegment, segment)) { 389 if (referenceSegment.length == 0) 390 return true; 391 392 if (segment.length == 0) { 393 nRefDiscontinuities.add(way.getNode(segment.startIndex)); 394 } else { 395 Set<Node> nodeGroup = new HashSet<>(); 396 397 for (int i = segment.startIndex; i <= segment.startIndex + segment.length; i++) { 398 nodeGroup.add(way.getNode(i)); 399 } 400 sRefDiscontinuities.add(nodeGroup); 401 } 402 } 403 } 404 405 return false; 406 } 407 408 /** 409 * Checks if parameter segments align. The {@code reference} is expected to be at least as long as the {@code candidate}. 410 * @param reference Reference segment to check against 411 * @param candidate Candidate segment 412 * @return {@code true} if the two segments ref=* numbering align 413 */ 414 private static boolean isSegmentAlign(SegmentInfo reference, SegmentInfo candidate) { 415 if (reference.direction == NumberingDirection.NONE 416 || reference.direction == candidate.direction 417 || candidate.direction == NumberingDirection.NONE) 418 return Math.abs(candidate.startIndex - reference.startIndex) == Math.abs(candidate.startRef - reference.startRef); 419 return false; 420 } 421 422 /** 423 * Detects continuous reference numbering sequences. Ignores the first and last node because 424 * ways can be connected, and the connection nodes can have different numbering. 425 * <p> 426 * If the numbering switches in the middle of the way, this can also be seen as error, 427 * because line relations would require split ways. 428 */ 429 static class RefChecker { 430 private final List<SegmentInfo> segments = new ArrayList<>(); 431 private NumberingDirection direction = NumberingDirection.NONE; 432 private Integer startIndex; 433 private Integer previousRef; 434 435 RefChecker(final Way way) { 436 run(way); 437 } 438 439 private void run(Way way) { 440 final int wayLength = way.getNodesCount(); 441 442 // first and last node skipped 443 for (int i = 1; i < wayLength - 1; i++) { 444 Node n = way.getNode(i); 445 if (!isPowerTower(n)) { 446 continue; 447 } 448 maintain(parseRef(n.get("ref")), i); 449 } 450 451 // needed for creation of the last segment 452 maintain(null, wayLength - 1); 453 } 454 455 /** 456 * Maintains class variables and constructs a new segment when necessary. 457 * @param ref recognised ref=* number 458 * @param index node index in a {@link Way} 459 */ 460 private void maintain(Integer ref, int index) { 461 if (previousRef == null && ref != null) { 462 // ref change: null -> number 463 startIndex = index; 464 } else if (previousRef != null && ref == null) { 465 // ref change: number -> null 466 segments.add(new SegmentInfo(startIndex, index - 1 - startIndex, previousRef, direction)); 467 direction = NumberingDirection.NONE; // to fix directionality 468 } else if (previousRef != null && ref != null) { 469 // ref change: number -> number 470 if (Math.abs(ref - previousRef) != 1) { 471 segments.add(new SegmentInfo(startIndex, index - 1 - startIndex, previousRef, direction)); 472 startIndex = index; 473 previousRef = ref; // to fix directionality 474 } 475 direction = detectDirection(ref, previousRef); 476 } 477 previousRef = ref; 478 } 479 480 /** 481 * Parses integer tag values. Later can be relatively easily extended or rewritten to handle 482 * complex references like 25/A, 25/B etc. 483 * @param value the value to be parsed 484 * @return parsed int or {@code null} in case of {@link NumberFormatException} 485 */ 486 private static Integer parseRef(String value) { 487 try { 488 return Integer.parseInt(value); 489 } catch (NumberFormatException ignore) { 490 Logging.trace("The " + RefChecker.class + " couldn't parse ref=" + value + ", consider rewriting the parser"); 491 return null; 492 } 493 } 494 495 /** 496 * Detects numbering direction. The parameters should follow way direction. 497 * @param ref last known reference value 498 * @param previousRef reference value before {@code ref} 499 * @return recognised direction 500 */ 501 private static NumberingDirection detectDirection(int ref, int previousRef) { 502 if (ref > previousRef) 503 return NumberingDirection.SAME; 504 else if (ref < previousRef) 505 return NumberingDirection.OPPOSITE; 506 return NumberingDirection.NONE; 507 } 508 509 /** 510 * Calculates the longest segment. 511 * @return the longest segment, or the lowest index if there are more than one with same length and direction, 512 * or {@code null} if there are more than one with same length and different direction 513 */ 514 SegmentInfo getLongestSegment() { 515 final Set<NumberingDirection> directions = EnumSet.noneOf(NumberingDirection.class); 516 int longestLength = -1; 517 int counter = 0; 518 SegmentInfo longest = null; 519 520 for (SegmentInfo segment : segments) { 521 if (segment.length > longestLength) { 522 longestLength = segment.length; 523 longest = segment; 524 counter = 0; 525 directions.clear(); 526 directions.add(segment.direction); 527 } else if (segment.length == longestLength) { 528 counter++; 529 directions.add(segment.direction); 530 } 531 } 532 533 // there are multiple segments with the same longest length and their directions don't match 534 if (counter > 0 && directions.size() > 1) 535 return null; 536 537 return longest; 538 } 539 540 /** 541 * @return the detected segments 542 */ 543 List<SegmentInfo> getSegments() { 544 return segments; 545 } 546 } 547 548 private static boolean isRelatedToPower(Way way) { 549 if (way.hasTag("power") || way.hasTag("building")) 550 return true; 551 for (OsmPrimitive ref : way.getReferrers()) { 552 if (ref instanceof Relation && ref.isMultipolygon() && (ref.hasTag("power") || ref.hasTag("building"))) { 553 for (RelationMember rm : ((Relation) ref).getMembers()) { 554 if (way == rm.getMember()) 555 return true; 556 } 557 } 558 } 559 return false; 560 } 561 562 /** 563 * Determines if the current node connected to a line which usually used inside power stations. 564 * @param n node to check 565 * @param w parent way of {@code n} 566 * @return {@code true} if {@code n} connected to power=line + line=* 567 */ 568 private static boolean isConnectedToStationLine(Node n, Way w) { 569 for (OsmPrimitive p : n.getReferrers()) { 570 if (p instanceof Way && !p.equals(w) && isPowerLine((Way) p) && p.hasKey("line")) 571 return true; 572 } 573 return false; 574 } 575 576 /** 577 * Checks if the way continues as a power=minor_line. 578 * @param way Way to be checked 579 * @return {@code true} if the way continues as a power=minor_line 580 */ 581 private static boolean isContinuesAsMinorLine(Way way) { 582 return way.firstNode().referrers(Way.class).filter(referrer -> !way.equals(referrer)).anyMatch(PowerLines::isMinorLine) || 583 way.lastNode().referrers(Way.class).filter(referrer -> !way.equals(referrer)).anyMatch(PowerLines::isMinorLine); 584 } 585 586 /** 587 * Checks if the given primitive denotes a power=minor_line. 588 * @param p primitive to be checked 589 * @return {@code true} if the given primitive denotes a power=minor_line 590 */ 591 private static boolean isMinorLine(OsmPrimitive p) { 592 return p.hasTag("power", "minor_line"); 593 } 594 595 /** 596 * Check if primitive has a tag that marks it as a water area or boundary of a water area. 597 * @param p the primitive 598 * @return {@code true} if primitive has a tag that marks it as a water area or boundary of a water area 599 */ 600 private static boolean concernsWaterArea(OsmPrimitive p) { 601 return p.hasTag("water", "river", "lake") || p.hasKey("waterway") || p.hasTag("natural", "coastline"); 602 } 603 604 /** 605 * Checks if the given node is inside a power station. 606 * @param n Node to be checked 607 * @return true if the given node is inside a power station 608 */ 140 609 protected final boolean isInPowerStation(Node n) { 141 610 for (OsmPrimitive station : powerStations) { 142 611 List<List<Node>> nodesLists = new ArrayList<>(); … … public class PowerLines extends Test { 171 640 /** 172 641 * Determines if the specified primitive denotes a power station. 173 642 * @param p The primitive to be tested 174 * @return {@code true} if power key is set and equal to station/sub_station/plant643 * @return {@code true} if power key is set and equal to generator/substation/plant 175 644 */ 176 645 protected static final boolean isPowerStation(OsmPrimitive p) { 177 646 return isPowerIn(p, POWER_STATION_TAGS) || isBuildingIn(p, BUILDING_STATION_TAGS); 178 647 } 179 648 180 649 /** 181 * Determines if the specified node denotes a power tower/pole.650 * Determines if the specified node denotes a power support feature. 182 651 * @param n The node to be tested 183 * @return {@code true} if power key is set and equal to tower/pole652 * @return {@code true} if power key is set and equal to pole/tower/portal/catenary_mast 184 653 */ 185 654 protected static final boolean isPowerTower(Node n) { 186 655 return isPowerIn(n, POWER_TOWER_TAGS); … … public class PowerLines extends Test { 189 658 /** 190 659 * Determines if the specified node denotes a power infrastructure allowed on a power line. 191 660 * @param n The node to be tested 192 * @return True if power key is set and equal to switch/tranformer/busbar/generator 661 * @return {@code true} if power key is set and equal to compensator/converter/generator/insulator 662 * /switch/switchgear/terminal/transformer 193 663 */ 194 protected static final boolean isPower Allowed(Node n) {195 return isPowerIn(n, POWER_ ALLOWED_TAGS);664 protected static final boolean isPowerInfrastructure(Node n) { 665 return isPowerIn(n, POWER_INFRASTRUCTURE_TAGS); 196 666 } 197 667 198 668 /** … … public class PowerLines extends Test { 215 685 return p.hasTag("building", values); 216 686 } 217 687 218 private void clearCollections() { 688 @Override 689 public void clear() { 690 super.clear(); 219 691 powerStations.clear(); 220 692 badConnections.clear(); 221 missingTowerOrPole.clear(); 693 missingTags.clear(); 694 missingNodes.clear(); 695 wrongLineType.clear(); 696 refDiscontinuities.clear(); 697 segmentRefDiscontinuities.clear(); 698 datasetWaterways.clear(); 222 699 } 223 700 } -
src/org/openstreetmap/josm/gui/dialogs/InspectPrimitiveDataText.java
diff --git a/src/org/openstreetmap/josm/gui/dialogs/InspectPrimitiveDataText.java b/src/org/openstreetmap/josm/gui/dialogs/InspectPrimitiveDataText.java index 4ef48e264a..1b7c23a101 100644
a b import org.openstreetmap.josm.data.projection.proj.TransverseMercator; 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}. … … public class InspectPrimitiveDataText { 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/Geometry.java
diff --git a/src/org/openstreetmap/josm/tools/Geometry.java b/src/org/openstreetmap/josm/tools/Geometry.java index 6eca937e72..ed1475bd79 100644
a b public final class Geometry { 303 303 double y3 = p3.getY(); 304 304 double x4 = p4.getX(); 305 305 double y4 = p4.getY(); 306 double[] en = getSegmentSegmentIntersection(x1, y1, x2, y2, x3, y3, x4, y4); 307 if (en != null && en.length == 2) { 308 return new EastNorth(en[0], en[1]); 309 } 310 return null; 311 } 312 313 /** 314 * Finds the intersection of two line segments. 315 * @param p1 the coordinates of the start point of the first specified line segment 316 * @param p2 the coordinates of the end point of the first specified line segment 317 * @param p3 the coordinates of the start point of the second specified line segment 318 * @param p4 the coordinates of the end point of the second specified line segment 319 * @return LatLon null if no intersection was found, the LatLon coordinates of the intersection otherwise 320 * @since xxx 321 */ 322 public static ILatLon getSegmentSegmentIntersection(ILatLon p1, ILatLon p2, ILatLon p3, ILatLon p4) { 323 CheckParameterUtil.ensureThat(p1.isLatLonKnown(), () -> p1 + " invalid"); 324 CheckParameterUtil.ensureThat(p2.isLatLonKnown(), () -> p2 + " invalid"); 325 CheckParameterUtil.ensureThat(p3.isLatLonKnown(), () -> p3 + " invalid"); 326 CheckParameterUtil.ensureThat(p4.isLatLonKnown(), () -> p4 + " invalid"); 327 328 double x1 = p1.lon(); 329 double y1 = p1.lat(); 330 double x2 = p2.lon(); 331 double y2 = p2.lat(); 332 double x3 = p3.lon(); 333 double y3 = p3.lat(); 334 double x4 = p4.lon(); 335 double y4 = p4.lat(); 336 double[] en = getSegmentSegmentIntersection(x1, y1, x2, y2, x3, y3, x4, y4); 337 if (en != null && en.length == 2) { 338 return new LatLon(en[1], en[0]); 339 } 340 return null; 341 } 342 343 /** 344 * Get the segment segment intersection of two line segments 345 * @param x1 The x coordinate of the first point (first segment) 346 * @param y1 The y coordinate of the first point (first segment) 347 * @param x2 The x coordinate of the second point (first segment) 348 * @param y2 The y coordinate of the second point (first segment) 349 * @param x3 The x coordinate of the third point (second segment) 350 * @param y3 The y coordinate of the third point (second segment) 351 * @param x4 The x coordinate of the fourth point (second segment) 352 * @param y4 The y coordinate of the fourth point (second segment) 353 * @return {@code null} if no intersection was found, otherwise [x, y] 354 */ 355 private static double[] getSegmentSegmentIntersection(double x1, double y1, double x2, double y2, double x3, double y3, 356 double x4, double y4) { 306 357 307 358 //TODO: do this locally. 308 359 //TODO: remove this check after careful testing … … public final class Geometry { 333 384 if (u > -1e-8 && u < 1+1e-8 && v > -1e-8 && v < 1+1e-8) { 334 385 if (u < 0) u = 0; 335 386 if (u > 1) u = 1.0; 336 return new EastNorth(x1+a1*u, y1+a2*u);387 return new double[] {x1+a1*u, y1+a2*u}; 337 388 } else { 338 389 return null; 339 390 } -
src/org/openstreetmap/josm/tools/Utils.java
diff --git a/src/org/openstreetmap/josm/tools/Utils.java b/src/org/openstreetmap/josm/tools/Utils.java index 9e96ab80e0..0b02662020 100644
a b public final class Utils { 226 226 if (array != null) { 227 227 return Arrays.copyOf(array, array.length); 228 228 } 229 return array;229 return null; 230 230 } 231 231 232 232 /** … … public final class Utils { 239 239 if (array != null) { 240 240 return Arrays.copyOf(array, array.length); 241 241 } 242 return array;242 return null; 243 243 } 244 244 245 245 /** … … public final class Utils { 252 252 if (array != null) { 253 253 return Arrays.copyOf(array, array.length); 254 254 } 255 return array;255 return null; 256 256 } 257 257 258 258 /** … … public final class Utils { 265 265 if (array != null) { 266 266 return Arrays.copyOf(array, array.length); 267 267 } 268 return array;268 return null; 269 269 } 270 270 271 271 /** … … public final class Utils { 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. -
test/unit/org/openstreetmap/josm/data/osm/WayTest.java
diff --git a/test/unit/org/openstreetmap/josm/data/osm/WayTest.java b/test/unit/org/openstreetmap/josm/data/osm/WayTest.java index 269d5d01f2..3dfa4a8e90 100644
a b class WayTest { 134 134 void testLoadIAE() { 135 135 assertThrows(IllegalArgumentException.class, () -> new Way().load(new NodeData())); 136 136 } 137 138 @Test 139 void getLongestSegmentLength() { 140 DataSet ds = new DataSet(); 141 Node n1 = new Node(1); 142 Node n2 = new Node(2); 143 Node n3 = new Node(3); 144 Node n4 = new Node(4); 145 n1.setCoor(new LatLon(0.01, 0.01)); 146 n2.setCoor(new LatLon(0.02, 0.02)); 147 n3.setCoor(new LatLon(0.03, 0.03)); 148 n4.setCoor(new LatLon(0.05, 0.05)); 149 ds.addPrimitive(n1); 150 ds.addPrimitive(n2); 151 ds.addPrimitive(n3); 152 ds.addPrimitive(n4); 153 Way way = new Way(1); 154 ds.addPrimitive(way); 155 156 assertEquals(0.0, way.getLongestSegmentLength()); 157 way.setNodes(Arrays.asList(n1, n2, n2, n3, n4)); 158 159 assertEquals(3148.5902810874577, way.getLongestSegmentLength()); 160 } 137 161 } -
test/unit/org/openstreetmap/josm/tools/UtilsTest.java
diff --git a/test/unit/org/openstreetmap/josm/tools/UtilsTest.java b/test/unit/org/openstreetmap/josm/tools/UtilsTest.java index 4260bdbc3d..9588b8f46c 100644
a b class UtilsTest { 250 250 */ 251 251 @Test 252 252 void testJoinAsHtmlUnorderedList() { 253 List<? extends Object> items = Arrays.asList("1", Integer.valueOf(2));253 List<?> items = Arrays.asList("1", 2); 254 254 assertEquals("<ul><li>1</li><li>2</li></ul>", Utils.joinAsHtmlUnorderedList(items)); 255 255 assertEquals("<ul></ul>", Utils.joinAsHtmlUnorderedList(Collections.emptyList())); 256 256 } … … class UtilsTest { 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 }
