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