Changeset 18553 in josm for trunk/src/org/openstreetmap
- Timestamp:
- 2022-09-08T17:19:20+02:00 (2 years ago)
- Location:
- trunk/src/org/openstreetmap/josm
- Files:
-
- 7 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/org/openstreetmap/josm/data/osm/Way.java
r18513 r18553 11 11 import java.util.Set; 12 12 import java.util.stream.Collectors; 13 import java.util.stream.DoubleStream; 13 14 import java.util.stream.IntStream; 14 15 … … 609 610 @Override 610 611 public boolean hasIncompleteNodes() { 611 return Arrays.stream(nodes).anyMatch(Node::isIncomplete); 612 /* 613 * Ideally, we would store this as a flag, but a node may become 614 * incomplete under some circumstances without being able to notify the 615 * way to recalculate the flag. 616 * 617 * When profiling #20716 on Mesa County, CO (overpass download), the 618 * Arrays.stream method was fairly expensive. When switching to the for 619 * loop, the CPU samples for hasIncompleteNodes went from ~150k samples 620 * to ~8.5k samples (94% improvement) and the memory allocations for 621 * hasIncompleteNodes went from ~15.6 GB to 0. 622 */ 623 for (Node node : nodes) { 624 if (node.isIncomplete()) { 625 return true; 626 } 627 } 628 return false; 612 629 } 613 630 … … 649 666 650 667 /** 668 * Replies the segment lengths of the way as computed by {@link ILatLon#greatCircleDistance}. 669 * 670 * @return The segment lengths of a way in metres, following way direction 671 * @since 18553 672 */ 673 public double[] getSegmentLengths() { 674 return this.segmentLengths().toArray(); 675 } 676 677 /** 651 678 * Replies the length of the longest segment of the way, in metres, as computed by {@link ILatLon#greatCircleDistance}. 652 679 * @return The length of the segment, in metres … … 654 681 */ 655 682 public double getLongestSegmentLength() { 656 double length = 0; 683 return this.segmentLengths().max().orElse(0); 684 } 685 686 /** 687 * Get the segment lengths as a stream 688 * @return The stream of segment lengths (ordered) 689 */ 690 private DoubleStream segmentLengths() { 691 DoubleStream.Builder builder = DoubleStream.builder(); 657 692 Node lastN = null; 658 for (Node n:nodes) { 659 if (lastN != null && lastN.isLatLonKnown() && n.isLatLonKnown()) { 660 double l = n.greatCircleDistance(lastN); 661 if (l > length) { 662 length = l; 663 } 693 for (Node n : nodes) { 694 if (lastN != null && n.isLatLonKnown() && lastN.isLatLonKnown()) { 695 double distance = n.greatCircleDistance(lastN); 696 builder.accept(distance); 664 697 } 665 698 lastN = n; 666 699 } 667 return length;700 return builder.build(); 668 701 } 669 702 -
trunk/src/org/openstreetmap/josm/data/validation/tests/CrossingWays.java
r18323 r18553 16 16 17 17 import org.openstreetmap.josm.data.coor.EastNorth; 18 import org.openstreetmap.josm.data.coor.ILatLon; 18 19 import org.openstreetmap.josm.data.osm.OsmPrimitive; 19 20 import org.openstreetmap.josm.data.osm.OsmUtils; … … 354 355 for (int i = 0; i < nodesSize - 1; i++) { 355 356 final WaySegment es1 = new WaySegment(w, i); 356 final EastNorth en1 = es1.getFirstNode().getEastNorth(); 357 final EastNorth en2 = es1.getSecondNode().getEastNorth(); 358 if (en1 == null || en2 == null) { 357 if (!es1.getFirstNode().isLatLonKnown() || !es1.getSecondNode().isLatLonKnown()) { 359 358 Logging.warn("Crossing ways test skipped " + es1); 360 359 continue; 361 360 } 362 for (List<WaySegment> segments : getSegments(cellSegments, e n1, en2)) {361 for (List<WaySegment> segments : getSegments(cellSegments, es1.getFirstNode(), es1.getSecondNode())) { 363 362 for (WaySegment es2 : segments) { 364 363 List<Way> prims; … … 416 415 417 416 /** 417 * Returns all the cells this segment crosses. Each cell contains the list 418 * of segments already processed 419 * @param cellSegments map with already collected way segments 420 * @param n1 The first EastNorth 421 * @param n2 The second EastNorth 422 * @return A list with all the cells the segment crosses 423 * @since 18553 424 */ 425 public static List<List<WaySegment>> getSegments(Map<Point2D, List<WaySegment>> cellSegments, ILatLon n1, ILatLon n2) { 426 return ValUtil.getSegmentCells(n1, n2, OsmValidator.getGridDetail()).stream() 427 .map(cell -> cellSegments.computeIfAbsent(cell, k -> new ArrayList<>())) 428 .collect(Collectors.toList()); 429 } 430 431 /** 418 432 * Find ways which are crossing without sharing a node. 419 433 * @param w way that is to be checked -
trunk/src/org/openstreetmap/josm/data/validation/tests/PowerLines.java
r18324 r18553 4 4 import static org.openstreetmap.josm.tools.I18n.tr; 5 5 6 import java.awt.geom.Point2D; 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.HashMap; 13 import java.util.HashSet; 10 14 import java.util.List; 15 import java.util.Map; 11 16 import java.util.Set; 12 17 18 import org.openstreetmap.josm.data.coor.ILatLon; 13 19 import org.openstreetmap.josm.data.osm.Node; 14 20 import org.openstreetmap.josm.data.osm.OsmPrimitive; … … 16 22 import org.openstreetmap.josm.data.osm.RelationMember; 17 23 import org.openstreetmap.josm.data.osm.Way; 24 import org.openstreetmap.josm.data.osm.WaySegment; 18 25 import org.openstreetmap.josm.data.osm.visitor.paint.relations.Multipolygon; 19 26 import org.openstreetmap.josm.data.osm.visitor.paint.relations.Multipolygon.JoinedWay; 20 27 import org.openstreetmap.josm.data.osm.visitor.paint.relations.MultipolygonCache; 28 import org.openstreetmap.josm.data.preferences.sources.ValidatorPrefHelper; 21 29 import org.openstreetmap.josm.data.validation.Severity; 22 30 import org.openstreetmap.josm.data.validation.Test; 23 31 import org.openstreetmap.josm.data.validation.TestError; 24 32 import org.openstreetmap.josm.gui.progress.ProgressMonitor; 33 import org.openstreetmap.josm.spi.preferences.Config; 25 34 import org.openstreetmap.josm.tools.Geometry; 35 import org.openstreetmap.josm.tools.Logging; 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 33 /** Test identifier */ 34 protected static final int POWER_LINES = 2501; 49 // Common strings 50 private static final String MINOR_LINE = "minor_line"; 51 private static final String BUILDING = "building"; 52 private static final String POWER = "power"; 53 54 // Test identifiers 55 protected static final int POWER_SUPPORT = 2501; 35 56 protected static final int POWER_CONNECTION = 2502; 57 protected static final int POWER_SEGMENT_LENGTH = 2503; 58 protected static final int POWER_LOCAL_REF_CONTINUITY = 2504; 59 protected static final int POWER_WAY_REF_CONTINUITY = 2505; 60 protected static final int POWER_LINE_TYPE = 2506; 61 62 protected static final String PREFIX = ValidatorPrefHelper.PREFIX + "." + PowerLines.class.getSimpleName(); 36 63 37 64 /** Values for {@code power} key interpreted as power lines */ 38 static final Collection<String> POWER_LINE_TAGS = Arrays.asList("line", "minor_line");65 static final Collection<String> POWER_LINE_TAGS = Arrays.asList("line", MINOR_LINE); 39 66 /** Values for {@code power} key interpreted as power towers */ 40 static final Collection<String> POWER_TOWER_TAGS = Arrays.asList(" tower", "pole");67 static final Collection<String> POWER_TOWER_TAGS = Arrays.asList("catenary_mast", "pole", "portal", "tower"); 41 68 /** 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");69 static final Collection<String> POWER_STATION_TAGS = Arrays.asList("generator", "plant", "substation"); 43 70 /** Values for {@code building} key interpreted as power stations */ 44 static final Collection<String> BUILDING_STATION_TAGS = Arrays.asList("transformer_tower");71 static final Collection<String> BUILDING_STATION_TAGS = Collections.singletonList("transformer_tower"); 45 72 /** 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"); 48 49 private final Set<Node> badConnections = new LinkedHashSet<>(); 50 private final Set<Node> missingTowerOrPole = new LinkedHashSet<>(); 51 73 static final Collection<String> POWER_INFRASTRUCTURE_TAGS = Arrays.asList("compensator", "connection", "converter", 74 "generator", "insulator", "switch", "switchgear", "terminal", "transformer"); 75 76 private double hillyCompensation; 77 private double hillyThreshold; 78 private final Set<Node> badConnections = new HashSet<>(); 79 private final Set<Node> missingTags = new HashSet<>(); 80 private final Set<Way> wrongLineType = new HashSet<>(); 81 private final Set<WaySegment> missingNodes = new HashSet<>(); 82 private final Set<OsmPrimitive> refDiscontinuities = new HashSet<>(); 83 84 private final List<Set<Node>> segmentRefDiscontinuities = new ArrayList<>(); 52 85 private final List<OsmPrimitive> powerStations = new ArrayList<>(); 53 86 87 private final Collection<Way> foundPowerLines = new HashSet<>(); 88 /** All waterway segments, grouped by cells */ 89 private final Map<Point2D, List<WaySegment>> cellSegmentsWater = new HashMap<>(32); 90 54 91 /** 55 92 * Constructs a new {@code PowerLines} test. 56 93 */ 57 94 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 } 95 super(tr("Power lines"), tr("Checks if power line missing a support node and " + 96 "for nodes in power lines that do not have a power=tower/pole tag")); 75 97 } 76 98 … … 80 102 boolean connectedToUnrelated = false; 81 103 for (Way parent : n.getParentWays()) { 82 if (parent.hasTag( "power", "line", "minor_line", "cable"))104 if (parent.hasTag(POWER, "line", MINOR_LINE, "cable")) 83 105 nodeInLineOrCable = true; 84 else if (!isRelatedToPower(parent)) {106 else if (!isRelatedToPower(parent)) 85 107 connectedToUnrelated = true; 86 }87 108 } 88 109 if (nodeInLineOrCable && connectedToUnrelated) … … 90 111 } 91 112 113 @Override 114 public void visit(Way w) { 115 if (!isPrimitiveUsable(w)) return; 116 117 if (isPowerLine(w) && !w.hasKey("line") && !w.hasTag("location", "underground") && w.isUsable()) { 118 foundPowerLines.add(w); 119 } else if (w.isClosed() && isPowerStation(w)) { 120 powerStations.add(w); 121 } else if (concernsWaterArea(w)) { 122 this.addWaterWaySegments(w); 123 } 124 } 125 126 /** 127 * Add segments to the appropriate cells 128 * @param w The way to add segments from 129 */ 130 private void addWaterWaySegments(Way w) { 131 for (int i = 0; i < w.getNodesCount() - 1; i++) { 132 final WaySegment es1 = new WaySegment(w, i); 133 CrossingWays.getSegments(this.cellSegmentsWater, es1.getFirstNode(), es1.getSecondNode()).forEach(list -> list.add(es1)); 134 } 135 } 136 137 @Override 138 public void visit(Relation r) { 139 if (r.isMultipolygon() && isPowerStation(r)) { 140 powerStations.add(r); 141 } else if (concernsWaterArea(r)) { 142 r.getMemberPrimitives(Way.class).forEach(this::addWaterWaySegments); 143 } 144 } 145 146 @Override 147 public void startTest(ProgressMonitor progressMonitor) { 148 super.startTest(progressMonitor); 149 hillyCompensation = Config.getPref().getDouble(PREFIX + ".hilly_compensation", 0.2); 150 hillyThreshold = Config.getPref().getDouble(PREFIX + ".hilly_threshold", 4.0); 151 } 152 153 @Override 154 public void endTest() { 155 // Do the actual checks 156 for (Way w : this.foundPowerLines) { 157 powerlineChecks(w); 158 } 159 // Then return the errors 160 for (Node n : missingTags) { 161 if (!isInPowerStation(n)) { 162 errors.add(TestError.builder(this, Severity.WARNING, POWER_SUPPORT) 163 // the "missing tag" grouping can become broken if the MapCSS message get reworded 164 .message(tr("missing tag"), tr("node without power=*")) 165 .primitives(n) 166 .build()); 167 } 168 } 169 170 for (Node n : badConnections) { 171 errors.add(TestError.builder(this, Severity.WARNING, POWER_CONNECTION) 172 .message(tr("Node connects a power line or cable with an object " 173 + "which is not related to the power infrastructure")) 174 .primitives(n) 175 .build()); 176 } 177 178 for (WaySegment s : missingNodes) { 179 errors.add(TestError.builder(this, Severity.WARNING, POWER_SEGMENT_LENGTH) 180 .message(tr("Possibly missing line support node within power line")) 181 .primitives(s.getFirstNode(), s.getSecondNode()) 182 .highlightWaySegments(new HashSet<>(Collections.singleton(s))) 183 .build()); 184 } 185 186 for (OsmPrimitive p : refDiscontinuities) { 187 if (p instanceof Way) 188 errors.add(TestError.builder(this, Severity.WARNING, POWER_WAY_REF_CONTINUITY) 189 .message(tr("Mixed reference numbering")) 190 .primitives(p) 191 .build()); 192 } 193 194 final String discontinuityMsg = tr("Reference numbering don''t match majority of way''s nodes"); 195 196 for (OsmPrimitive p : refDiscontinuities) { 197 if (p instanceof Node) 198 errors.add(TestError.builder(this, Severity.WARNING, POWER_LOCAL_REF_CONTINUITY) 199 .message(discontinuityMsg) 200 .primitives(p) 201 .build()); 202 } 203 204 for (Set<Node> nodes : segmentRefDiscontinuities) { 205 errors.add(TestError.builder(this, Severity.WARNING, POWER_LOCAL_REF_CONTINUITY) 206 .message(discontinuityMsg) 207 .primitives(nodes) 208 .build()); 209 } 210 211 for (Way w : wrongLineType) { 212 errors.add(TestError.builder(this, Severity.WARNING, POWER_LINE_TYPE) 213 .message(tr("Possibly wrong power line type used")) 214 .primitives(w) 215 .build()); 216 } 217 218 super.endTest(); 219 } 220 221 /** 222 * The base powerline checks 223 * @param w The powerline to check 224 */ 225 private void powerlineChecks(Way w) { 226 final int segmentCount = w.getNodesCount() - 1; 227 final double mean = w.getLength() / segmentCount; 228 final double stdDev = Utils.getStandardDeviation(w.getSegmentLengths(), mean); 229 final boolean isContinuesAsMinorLine = isContinuesAsMinorLine(w); 230 boolean isCrossingWater = false; 231 int poleCount = 0; 232 int towerCount = 0; 233 Node prevNode = w.firstNode(); 234 235 double baseThreshold = w.hasTag(POWER, "line") ? 1.6 : 1.8; 236 if (mean / stdDev < hillyThreshold) { 237 //compensate for possibly hilly areas where towers can't be put anywhere 238 baseThreshold += hillyCompensation; 239 } 240 241 for (Node n : w.getNodes()) { 242 243 /// handle power station line connections (e.g. power=line + line=*) 244 if (isConnectedToStationLine(n, w) || n.hasTag(POWER, "connection")) { 245 prevNode = n; 246 continue; // skip, it would be false positive 247 } 248 249 /// handle missing power line support tags (e.g. tower) 250 if (!isPowerTower(n) && !isPowerInfrastructure(n) && IN_DOWNLOADED_AREA.test(n) 251 && (!w.isFirstLastNode(n) || !isPowerStation(n))) 252 missingTags.add(n); 253 254 /// handle missing nodes 255 double segmentLen = n.greatCircleDistance(prevNode); 256 final Set<Way> crossingWaterWays = new HashSet<>(8); 257 final Set<ILatLon> crossingPositions = new HashSet<>(8); 258 findCrossings(this.cellSegmentsWater, w, crossingWaterWays, crossingPositions); 259 260 if (!crossingWaterWays.isEmpty()) { 261 double compensation = calculateIntersectingLen(prevNode, crossingPositions); 262 segmentLen -= compensation; 263 } 264 265 if (segmentCount > 4 266 && segmentLen > mean * baseThreshold 267 && !isPowerInfrastructure(n) 268 && IN_DOWNLOADED_AREA.test(n)) 269 missingNodes.add(WaySegment.forNodePair(w, prevNode, n)); 270 271 /// handle wrong line types 272 if (!crossingWaterWays.isEmpty()) 273 isCrossingWater = true; 274 275 if (n.hasTag(POWER, "pole")) 276 poleCount++; 277 else if (n.hasTag(POWER, "tower", "portal")) 278 towerCount++; 279 280 prevNode = n; 281 } 282 283 /// handle ref=* numbering discontinuities 284 if (detectDiscontinuity(w, refDiscontinuities, segmentRefDiscontinuities)) 285 refDiscontinuities.add(w); 286 287 /// handle wrong line types 288 if (((poleCount > towerCount && w.hasTag(POWER, "line")) 289 || (poleCount < towerCount && w.hasTag(POWER, MINOR_LINE) 290 && !isCrossingWater 291 && !isContinuesAsMinorLine)) 292 && IN_DOWNLOADED_AREA.test(w)) 293 wrongLineType.add(w); 294 295 } 296 297 /** 298 * The summarized length (in metres) of a way where a power line hangs over a water area. 299 * @param ref Reference point 300 * @param crossingNodes Crossing nodes, unordered 301 * @return The summarized length (in metres) of a way where a power line hangs over a water area 302 */ 303 private static double calculateIntersectingLen(Node ref, Set<ILatLon> crossingNodes) { 304 double min = Double.POSITIVE_INFINITY; 305 double max = Double.NEGATIVE_INFINITY; 306 307 for (ILatLon coor : crossingNodes) { 308 309 if (ref != null && coor != null) { 310 double dist = ref.greatCircleDistance(coor); 311 312 if (dist < min) 313 min = dist; 314 if (dist > max) 315 max = dist; 316 } 317 } 318 return max - min; 319 } 320 321 /** 322 * Searches for way intersections, which intersect the {@code pair} attribute. 323 * @param ways collection of ways to search for crossings 324 * @param parent parent powerline way to find crossings for 325 * @param crossingWays found crossing ways 326 * @param crossingPositions collection of the crossing positions 327 * @implNote Inspired by {@code utilsplugin2/selection/NodeWayUtils.java#addWaysIntersectingWay()} 328 */ 329 private static void findCrossings(Map<Point2D, List<WaySegment>> ways, Way parent, Set<Way> crossingWays, 330 Set<ILatLon> crossingPositions) { 331 int nodesSize = parent.getNodesCount(); 332 for (int i = 0; i < nodesSize - 1; i++) { 333 final WaySegment es1 = new WaySegment(parent, i); 334 if (!es1.getFirstNode().isLatLonKnown() || !es1.getSecondNode().isLatLonKnown()) { 335 Logging.warn("PowerLines crossing ways test section skipped " + es1); 336 continue; 337 } 338 for (List<WaySegment> segments : CrossingWays.getSegments(ways, es1.getFirstNode(), es1.getSecondNode())) { 339 for (WaySegment segment : segments) { 340 if (es1.intersects(segment)) { 341 final ILatLon ll = Geometry.getSegmentSegmentIntersection(es1.getFirstNode(), es1.getSecondNode(), 342 segment.getFirstNode(), segment.getSecondNode()); 343 if (ll != null) { 344 crossingWays.add(es1.getWay()); 345 crossingPositions.add(ll); 346 } 347 } 348 } 349 } 350 } 351 } 352 353 /** Power line support features ref=* numbering direction. */ 354 private enum NumberingDirection { 355 /** No direction */ 356 NONE, 357 /** Numbering follows way direction */ 358 SAME, 359 /** Numbering goes opposite way direction */ 360 OPPOSITE 361 } 362 363 /** Helper class for reference numbering test. Used for storing continuous reference segment info. */ 364 private static class SegmentInfo { 365 /** Node index, follows way direction */ 366 private final int startIndex; 367 /** ref=* value at {@link SegmentInfo#startIndex} */ 368 private final int startRef; 369 /** Segment length */ 370 private final int length; 371 /** Segment direction */ 372 private final NumberingDirection direction; 373 374 SegmentInfo(int startIndex, int length, int ref, NumberingDirection direction) { 375 this.startIndex = startIndex; 376 this.length = length; 377 this.direction = direction; 378 379 if (direction == NumberingDirection.SAME) 380 this.startRef = ref - length; 381 else 382 this.startRef = ref + length; 383 384 if (length == 0 && direction != NumberingDirection.NONE) { 385 throw new IllegalArgumentException("When the segment length is zero, the direction should be NONE"); 386 } 387 } 388 389 @Override 390 public String toString() { 391 return String.format("SegmentInfo{startIndex=%d, startRef=%d, length=%d, direction=%s}", 392 startIndex, startRef, length, direction); 393 } 394 } 395 396 /** 397 * Detects ref=* numbering discontinuities in the given way. 398 * @param way checked way 399 * @param nRefDiscontinuities single node ref=* discontinuities 400 * @param sRefDiscontinuities continuous node ref=* discontinuities 401 * @return {@code true} if warning needs to be issued for the whole way 402 */ 403 static boolean detectDiscontinuity(Way way, Set<OsmPrimitive> nRefDiscontinuities, List<Set<Node>> sRefDiscontinuities) { 404 final RefChecker checker = new RefChecker(way); 405 final List<SegmentInfo> segments = checker.getSegments(); 406 final SegmentInfo referenceSegment = checker.getLongestSegment(); 407 408 if (referenceSegment == null) 409 return !segments.isEmpty(); 410 411 // collect disconnected ref segments which are not align up to the reference 412 for (SegmentInfo segment : segments) { 413 if (!isSegmentAlign(referenceSegment, segment)) { 414 if (referenceSegment.length == 0) 415 return true; 416 417 if (segment.length == 0) { 418 nRefDiscontinuities.add(way.getNode(segment.startIndex)); 419 } else { 420 Set<Node> nodeGroup = new HashSet<>(); 421 422 for (int i = segment.startIndex; i <= segment.startIndex + segment.length; i++) { 423 nodeGroup.add(way.getNode(i)); 424 } 425 sRefDiscontinuities.add(nodeGroup); 426 } 427 } 428 } 429 430 return false; 431 } 432 433 /** 434 * Checks if parameter segments align. The {@code reference} is expected to be at least as long as the {@code candidate}. 435 * @param reference Reference segment to check against 436 * @param candidate Candidate segment 437 * @return {@code true} if the two segments ref=* numbering align 438 */ 439 private static boolean isSegmentAlign(SegmentInfo reference, SegmentInfo candidate) { 440 if (reference.direction == NumberingDirection.NONE 441 || reference.direction == candidate.direction 442 || candidate.direction == NumberingDirection.NONE) 443 return Math.abs(candidate.startIndex - reference.startIndex) == Math.abs(candidate.startRef - reference.startRef); 444 return false; 445 } 446 447 /** 448 * Detects continuous reference numbering sequences. Ignores the first and last node because 449 * ways can be connected, and the connection nodes can have different numbering. 450 * <p> 451 * If the numbering switches in the middle of the way, this can also be seen as error, 452 * because line relations would require split ways. 453 */ 454 static class RefChecker { 455 private final List<SegmentInfo> segments = new ArrayList<>(); 456 private NumberingDirection direction = NumberingDirection.NONE; 457 private Integer startIndex; 458 private Integer previousRef; 459 460 RefChecker(final Way way) { 461 run(way); 462 } 463 464 private void run(Way way) { 465 final int wayLength = way.getNodesCount(); 466 467 // first and last node skipped 468 for (int i = 1; i < wayLength - 1; i++) { 469 Node n = way.getNode(i); 470 if (!isPowerTower(n)) { 471 continue; 472 } 473 maintain(parseRef(n.get("ref")), i); 474 } 475 476 // needed for creation of the last segment 477 maintain(null, wayLength - 1); 478 } 479 480 /** 481 * Maintains class variables and constructs a new segment when necessary. 482 * @param ref recognised ref=* number 483 * @param index node index in a {@link Way} 484 */ 485 private void maintain(Integer ref, int index) { 486 if (previousRef == null && ref != null) { 487 // ref change: null -> number 488 startIndex = index; 489 } else if (previousRef != null && ref == null) { 490 // ref change: number -> null 491 segments.add(new SegmentInfo(startIndex, index - 1 - startIndex, previousRef, direction)); 492 direction = NumberingDirection.NONE; // to fix directionality 493 } else if (previousRef != null) { 494 // ref change: number -> number 495 if (Math.abs(ref - previousRef) != 1) { 496 segments.add(new SegmentInfo(startIndex, index - 1 - startIndex, previousRef, direction)); 497 startIndex = index; 498 previousRef = ref; // to fix directionality 499 } 500 direction = detectDirection(ref, previousRef); 501 } 502 previousRef = ref; 503 } 504 505 /** 506 * Parses integer tag values. Later can be relatively easily extended or rewritten to handle 507 * complex references like 25/A, 25/B etc. 508 * @param value the value to be parsed 509 * @return parsed int or {@code null} in case of {@link NumberFormatException} 510 */ 511 private static Integer parseRef(String value) { 512 try { 513 return Integer.parseInt(value); 514 } catch (NumberFormatException ignore) { 515 Logging.trace("The " + RefChecker.class + " couldn't parse ref=" + value + ", consider rewriting the parser"); 516 return null; 517 } 518 } 519 520 /** 521 * Detects numbering direction. The parameters should follow way direction. 522 * @param ref last known reference value 523 * @param previousRef reference value before {@code ref} 524 * @return recognised direction 525 */ 526 private static NumberingDirection detectDirection(int ref, int previousRef) { 527 if (ref > previousRef) 528 return NumberingDirection.SAME; 529 else if (ref < previousRef) 530 return NumberingDirection.OPPOSITE; 531 return NumberingDirection.NONE; 532 } 533 534 /** 535 * Calculates the longest segment. 536 * @return the longest segment, or the lowest index if there are more than one with same length and direction, 537 * or {@code null} if there are more than one with same length and different direction 538 */ 539 SegmentInfo getLongestSegment() { 540 final Set<NumberingDirection> directions = EnumSet.noneOf(NumberingDirection.class); 541 int longestLength = -1; 542 int counter = 0; 543 SegmentInfo longest = null; 544 545 for (SegmentInfo segment : segments) { 546 if (segment.length > longestLength) { 547 longestLength = segment.length; 548 longest = segment; 549 counter = 0; 550 directions.clear(); 551 directions.add(segment.direction); 552 } else if (segment.length == longestLength) { 553 counter++; 554 directions.add(segment.direction); 555 } 556 } 557 558 // there are multiple segments with the same longest length and their directions don't match 559 if (counter > 0 && directions.size() > 1) 560 return null; 561 562 return longest; 563 } 564 565 /** 566 * @return the detected segments 567 */ 568 List<SegmentInfo> getSegments() { 569 return segments; 570 } 571 } 572 92 573 private static boolean isRelatedToPower(Way way) { 93 if (way.hasTag( "power") || way.hasTag("building"))574 if (way.hasTag(POWER) || way.hasTag(BUILDING)) 94 575 return true; 95 576 for (OsmPrimitive ref : way.getReferrers()) { 96 if (ref instanceof Relation && ref.isMultipolygon() && (ref.hasTag( "power") || ref.hasTag("building"))) {577 if (ref instanceof Relation && ref.isMultipolygon() && (ref.hasTag(POWER) || ref.hasTag(BUILDING))) { 97 578 for (RelationMember rm : ((Relation) ref).getMembers()) { 98 579 if (way == rm.getMember()) … … 104 585 } 105 586 106 @Override 107 public void visit(Relation r) { 108 if (r.isMultipolygon() && isPowerStation(r)) { 109 powerStations.add(r); 110 } 111 } 112 113 @Override 114 public void startTest(ProgressMonitor progressMonitor) { 115 super.startTest(progressMonitor); 116 clearCollections(); 117 } 118 119 @Override 120 public void endTest() { 121 for (Node n : missingTowerOrPole) { 122 if (!isInPowerStation(n)) { 123 errors.add(TestError.builder(this, Severity.WARNING, POWER_LINES) 124 .message(tr("Missing power tower/pole/connection within power line")) 125 .primitives(n) 126 .build()); 127 } 128 } 129 130 for (Node n : badConnections) { 131 errors.add(TestError.builder(this, Severity.WARNING, POWER_CONNECTION) 132 .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()); 135 } 136 clearCollections(); 137 super.endTest(); 138 } 139 587 /** 588 * Determines if the current node connected to a line which usually used inside power stations. 589 * @param n node to check 590 * @param w parent way of {@code n} 591 * @return {@code true} if {@code n} connected to power=line + line=* 592 */ 593 private static boolean isConnectedToStationLine(Node n, Way w) { 594 for (OsmPrimitive p : n.getReferrers()) { 595 if (p instanceof Way && !p.equals(w) && isPowerLine((Way) p) && p.hasKey("line")) 596 return true; 597 } 598 return false; 599 } 600 601 /** 602 * Checks if the way continues as a power=minor_line. 603 * @param way Way to be checked 604 * @return {@code true} if the way continues as a power=minor_line 605 */ 606 private static boolean isContinuesAsMinorLine(Way way) { 607 return way.firstNode().referrers(Way.class).filter(referrer -> !way.equals(referrer)).anyMatch(PowerLines::isMinorLine) || 608 way.lastNode().referrers(Way.class).filter(referrer -> !way.equals(referrer)).anyMatch(PowerLines::isMinorLine); 609 } 610 611 /** 612 * Checks if the given primitive denotes a power=minor_line. 613 * @param p primitive to be checked 614 * @return {@code true} if the given primitive denotes a power=minor_line 615 */ 616 private static boolean isMinorLine(OsmPrimitive p) { 617 return p.hasTag(POWER, MINOR_LINE); 618 } 619 620 /** 621 * Check if primitive has a tag that marks it as a water area or boundary of a water area. 622 * @param p the primitive 623 * @return {@code true} if primitive has a tag that marks it as a water area or boundary of a water area 624 */ 625 private static boolean concernsWaterArea(OsmPrimitive p) { 626 return p.hasTag("water", "river", "lake") || p.hasKey("waterway") || p.hasTag("natural", "coastline"); 627 } 628 629 /** 630 * Checks if the given node is inside a power station. 631 * @param n Node to be checked 632 * @return true if the given node is inside a power station 633 */ 140 634 protected final boolean isInPowerStation(Node n) { 141 635 for (OsmPrimitive station : powerStations) { … … 165 659 * @return {@code true} if power key is set and equal to line/minor_line 166 660 */ 167 protected static finalboolean isPowerLine(Way w) {661 protected static boolean isPowerLine(Way w) { 168 662 return isPowerIn(w, POWER_LINE_TAGS); 169 663 } … … 172 666 * Determines if the specified primitive denotes a power station. 173 667 * @param p The primitive to be tested 174 * @return {@code true} if power key is set and equal to station/sub_station/plant175 */ 176 protected static finalboolean isPowerStation(OsmPrimitive p) {668 * @return {@code true} if power key is set and equal to generator/substation/plant 669 */ 670 protected static boolean isPowerStation(OsmPrimitive p) { 177 671 return isPowerIn(p, POWER_STATION_TAGS) || isBuildingIn(p, BUILDING_STATION_TAGS); 178 672 } 179 673 180 674 /** 181 * Determines if the specified node denotes a power tower/pole.675 * Determines if the specified node denotes a power support feature. 182 676 * @param n The node to be tested 183 * @return {@code true} if power key is set and equal to tower/pole184 */ 185 protected static finalboolean isPowerTower(Node n) {677 * @return {@code true} if power key is set and equal to pole/tower/portal/catenary_mast 678 */ 679 protected static boolean isPowerTower(Node n) { 186 680 return isPowerIn(n, POWER_TOWER_TAGS); 187 681 } … … 190 684 * Determines if the specified node denotes a power infrastructure allowed on a power line. 191 685 * @param n The node to be tested 192 * @return True if power key is set and equal to switch/tranformer/busbar/generator 193 */ 194 protected static final boolean isPowerAllowed(Node n) { 195 return isPowerIn(n, POWER_ALLOWED_TAGS); 686 * @return {@code true} if power key is set and equal to compensator/converter/generator/insulator 687 * /switch/switchgear/terminal/transformer 688 */ 689 protected static boolean isPowerInfrastructure(Node n) { 690 return isPowerIn(n, POWER_INFRASTRUCTURE_TAGS); 196 691 } 197 692 … … 203 698 */ 204 699 private static boolean isPowerIn(OsmPrimitive p, Collection<String> values) { 205 return p.hasTag( "power", values);700 return p.hasTag(POWER, values); 206 701 } 207 702 … … 213 708 */ 214 709 private static boolean isBuildingIn(OsmPrimitive p, Collection<String> values) { 215 return p.hasTag("building", values); 216 } 217 218 private void clearCollections() { 710 return p.hasTag(BUILDING, values); 711 } 712 713 @Override 714 public void clear() { 715 super.clear(); 716 badConnections.clear(); 717 cellSegmentsWater.clear(); 718 foundPowerLines.clear(); 719 missingNodes.clear(); 720 missingTags.clear(); 219 721 powerStations.clear(); 220 badConnections.clear(); 221 missingTowerOrPole.clear(); 722 refDiscontinuities.clear(); 723 segmentRefDiscontinuities.clear(); 724 wrongLineType.clear(); 222 725 } 223 726 } -
trunk/src/org/openstreetmap/josm/data/validation/util/ValUtil.java
r16628 r18553 11 11 12 12 import org.openstreetmap.josm.data.coor.EastNorth; 13 import org.openstreetmap.josm.data.coor.ILatLon; 13 14 import org.openstreetmap.josm.data.osm.Node; 14 15 import org.openstreetmap.josm.data.osm.Way; 16 import org.openstreetmap.josm.data.projection.ProjectionRegistry; 15 17 import org.openstreetmap.josm.data.validation.OsmValidator; 16 18 import org.openstreetmap.josm.tools.CheckParameterUtil; … … 45 47 double griddetail = OsmValidator.getGridDetail(); 46 48 49 final EastNorth en1 = n1.getEastNorth(); 50 final EastNorth en2 = n2.getEastNorth(); 47 51 // First, round coordinates 48 52 // CHECKSTYLE.OFF: SingleSpaceSeparator 49 long x0 = Math.round( n1.getEastNorth().east() * griddetail);50 long y0 = Math.round( n1.getEastNorth().north() * griddetail);51 long x1 = Math.round( n2.getEastNorth().east() * griddetail);52 long y1 = Math.round( n2.getEastNorth().north() * griddetail);53 long x0 = Math.round(en1.east() * griddetail); 54 long y0 = Math.round(en1.north() * griddetail); 55 long x1 = Math.round(en2.east() * griddetail); 56 long y1 = Math.round(en2.north() * griddetail); 53 57 // CHECKSTYLE.ON: SingleSpaceSeparator 54 58 … … 67 71 // Then floor coordinates, in case the way is in the border of the cell. 68 72 // CHECKSTYLE.OFF: SingleSpaceSeparator 69 x0 = (long) Math.floor( n1.getEastNorth().east() * griddetail);70 y0 = (long) Math.floor( n1.getEastNorth().north() * griddetail);71 x1 = (long) Math.floor( n2.getEastNorth().east() * griddetail);72 y1 = (long) Math.floor( n2.getEastNorth().north() * griddetail);73 x0 = (long) Math.floor(en1.east() * griddetail); 74 y0 = (long) Math.floor(en1.north() * griddetail); 75 x1 = (long) Math.floor(en2.east() * griddetail); 76 y1 = (long) Math.floor(en2.north() * griddetail); 73 77 // CHECKSTYLE.ON: SingleSpaceSeparator 74 78 … … 100 104 */ 101 105 public static List<Point2D> getSegmentCells(Node n1, Node n2, double gridDetail) { 106 return getSegmentCells((ILatLon) n1, n2, gridDetail); 107 } 108 109 /** 110 * Returns the coordinates of all cells in a grid that a line between 2 nodes intersects with. 111 * 112 * @param n1 The first latlon. 113 * @param n2 The second latlon. 114 * @param gridDetail The detail of the grid. Bigger values give smaller 115 * cells, but a bigger number of them. 116 * @return A list with the coordinates of all cells 117 * @throws IllegalArgumentException if n1 or n2 is {@code null} or without coordinates 118 * @since 18553 119 */ 120 public static List<Point2D> getSegmentCells(ILatLon n1, ILatLon n2, double gridDetail) { 102 121 CheckParameterUtil.ensureParameterNotNull(n1, "n1"); 103 122 CheckParameterUtil.ensureParameterNotNull(n1, "n2"); 104 return getSegmentCells(n1.getEastNorth(), n2.getEastNorth(), gridDetail); 123 return getSegmentCells(n1.getEastNorth(ProjectionRegistry.getProjection()), n2.getEastNorth(ProjectionRegistry.getProjection()), 124 gridDetail); 105 125 } 106 126 -
trunk/src/org/openstreetmap/josm/gui/dialogs/InspectPrimitiveDataText.java
r17760 r18553 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 /** … … 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()) { -
trunk/src/org/openstreetmap/josm/tools/Geometry.java
r18494 r18553 288 288 * @param p4 the coordinates of the end point of the second specified line segment 289 289 * @return EastNorth null if no intersection was found, the EastNorth coordinates of the intersection otherwise 290 * @see #getSegmentSegmentIntersection(ILatLon, ILatLon, ILatLon, ILatLon) 290 291 */ 291 292 public static EastNorth getSegmentSegmentIntersection(EastNorth p1, EastNorth p2, EastNorth p3, EastNorth p4) { 292 293 CheckParameterUtil.ensureThat(p1.isValid(), () -> p1 + " invalid"); 294 CheckParameterUtil.ensureThat(p2.isValid(), () -> p2 + " invalid"); 295 CheckParameterUtil.ensureThat(p3.isValid(), () -> p3 + " invalid"); 296 CheckParameterUtil.ensureThat(p4.isValid(), () -> p4 + " invalid"); 293 // see the ILatLon version for an explanation why the checks are in the if statement 294 if (!(p1.isValid() && p2.isValid() && p3.isValid() && p4.isValid())) { 295 CheckParameterUtil.ensureThat(p1.isValid(), () -> p1 + " invalid"); 296 CheckParameterUtil.ensureThat(p2.isValid(), () -> p2 + " invalid"); 297 CheckParameterUtil.ensureThat(p3.isValid(), () -> p3 + " invalid"); 298 CheckParameterUtil.ensureThat(p4.isValid(), () -> p4 + " invalid"); 299 } 297 300 298 301 double x1 = p1.getX(); … … 304 307 double x4 = p4.getX(); 305 308 double y4 = p4.getY(); 309 double[] en = getSegmentSegmentIntersection(x1, y1, x2, y2, x3, y3, x4, y4); 310 if (en != null && en.length == 2) { 311 return new EastNorth(en[0], en[1]); 312 } 313 return null; 314 } 315 316 /** 317 * Finds the intersection of two line segments. 318 * @param p1 the coordinates of the start point of the first specified line segment 319 * @param p2 the coordinates of the end point of the first specified line segment 320 * @param p3 the coordinates of the start point of the second specified line segment 321 * @param p4 the coordinates of the end point of the second specified line segment 322 * @return LatLon null if no intersection was found, the LatLon coordinates of the intersection otherwise 323 * @see #getSegmentSegmentIntersection(EastNorth, EastNorth, EastNorth, EastNorth) 324 * @since 18553 325 */ 326 public static ILatLon getSegmentSegmentIntersection(ILatLon p1, ILatLon p2, ILatLon p3, ILatLon p4) { 327 // Avoid lambda creation if at all possible -- this pretty much removes all memory allocations 328 // from this method (11.4 GB to 0) when testing #20716 with Mesa County, CO (overpass download). 329 // There was also a 2/3 decrease in CPU samples for the method. 330 if (!(p1.isLatLonKnown() && p2.isLatLonKnown() && p3.isLatLonKnown() && p4.isLatLonKnown())) { 331 CheckParameterUtil.ensureThat(p1.isLatLonKnown(), () -> p1 + " invalid"); 332 CheckParameterUtil.ensureThat(p2.isLatLonKnown(), () -> p2 + " invalid"); 333 CheckParameterUtil.ensureThat(p3.isLatLonKnown(), () -> p3 + " invalid"); 334 CheckParameterUtil.ensureThat(p4.isLatLonKnown(), () -> p4 + " invalid"); 335 } 336 337 double x1 = p1.lon(); 338 double y1 = p1.lat(); 339 double x2 = p2.lon(); 340 double y2 = p2.lat(); 341 double x3 = p3.lon(); 342 double y3 = p3.lat(); 343 double x4 = p4.lon(); 344 double y4 = p4.lat(); 345 double[] en = getSegmentSegmentIntersection(x1, y1, x2, y2, x3, y3, x4, y4); 346 if (en != null && en.length == 2) { 347 return new LatLon(en[1], en[0]); 348 } 349 return null; 350 } 351 352 /** 353 * Get the segment segment intersection of two line segments 354 * @param x1 The x coordinate of the first point (first segment) 355 * @param y1 The y coordinate of the first point (first segment) 356 * @param x2 The x coordinate of the second point (first segment) 357 * @param y2 The y coordinate of the second point (first segment) 358 * @param x3 The x coordinate of the third point (second segment) 359 * @param y3 The y coordinate of the third point (second segment) 360 * @param x4 The x coordinate of the fourth point (second segment) 361 * @param y4 The y coordinate of the fourth point (second segment) 362 * @return {@code null} if no intersection was found, otherwise [x, y] 363 */ 364 private static double[] getSegmentSegmentIntersection(double x1, double y1, double x2, double y2, double x3, double y3, 365 double x4, double y4) { 306 366 307 367 //TODO: do this locally. … … 334 394 if (u < 0) u = 0; 335 395 if (u > 1) u = 1.0; 336 return new EastNorth(x1+a1*u, y1+a2*u);396 return new double[] {x1+a1*u, y1+a2*u}; 337 397 } else { 338 398 return null; -
trunk/src/org/openstreetmap/josm/tools/Utils.java
r18208 r18553 487 487 */ 488 488 public static String md5Hex(String data) { 489 MessageDigest md = null;489 MessageDigest md; 490 490 try { 491 491 md = MessageDigest.getInstance("MD5"); … … 1302 1302 return distance > 0 && distance <= 2; 1303 1303 } 1304 } 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 18553 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 18553 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); 1304 1342 } 1305 1343 … … 1705 1743 public static Date getJavaExpirationDate() { 1706 1744 try { 1707 Object value = null;1745 Object value; 1708 1746 Class<?> c = Class.forName("com.sun.deploy.config.BuiltInProperties"); 1709 1747 try {
Note:
See TracChangeset
for help on using the changeset viewer.