Changeset 18553 in josm for trunk


Ignore:
Timestamp:
2022-09-08T17:19:20+02:00 (2 years ago)
Author:
taylor.smock
Message:

Fix #20716: Search for missing power line support features (patch by gaben, modified)

Way:

  • Avoid Arrays.stream in hasIncompleteNodes. This decreases CPU cost by 90%+ and makes it free from a memory allocation standpoint.
  • Add method to get segment lengths (meters)

CrossingWays:

  • Avoid Node#getEastNorth calls
  • Add a getSegments call that takes ILatLon

PowerLines:

  • Check for inconsistent support node reference numbering
  • Check for ways with unusually long segments without node supports
  • Check for ways where line types might be misused

ValUtil:

  • Avoid unnecessary calls to Node#getEastNorth
  • Add getSegmentCells for ILatLon

InspectPrimitiveDataText:

  • Add average segment length statistic

Geometry:

  • Add getSegmentSegmentIntersection for ILatLon to avoid new EastNorth objects

Utils:

  • Add getStandardDeviation methods
Location:
trunk
Files:
1 added
9 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/org/openstreetmap/josm/data/osm/Way.java

    r18513 r18553  
    1111import java.util.Set;
    1212import java.util.stream.Collectors;
     13import java.util.stream.DoubleStream;
    1314import java.util.stream.IntStream;
    1415
     
    609610    @Override
    610611    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;
    612629    }
    613630
     
    649666
    650667    /**
     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    /**
    651678     * Replies the length of the longest segment of the way, in metres, as computed by {@link ILatLon#greatCircleDistance}.
    652679     * @return The length of the segment, in metres
     
    654681     */
    655682    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();
    657692        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);
    664697            }
    665698            lastN = n;
    666699        }
    667         return length;
     700        return builder.build();
    668701    }
    669702
  • trunk/src/org/openstreetmap/josm/data/validation/tests/CrossingWays.java

    r18323 r18553  
    1616
    1717import org.openstreetmap.josm.data.coor.EastNorth;
     18import org.openstreetmap.josm.data.coor.ILatLon;
    1819import org.openstreetmap.josm.data.osm.OsmPrimitive;
    1920import org.openstreetmap.josm.data.osm.OsmUtils;
     
    354355        for (int i = 0; i < nodesSize - 1; i++) {
    355356            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()) {
    359358                Logging.warn("Crossing ways test skipped " + es1);
    360359                continue;
    361360            }
    362             for (List<WaySegment> segments : getSegments(cellSegments, en1, en2)) {
     361            for (List<WaySegment> segments : getSegments(cellSegments, es1.getFirstNode(), es1.getSecondNode())) {
    363362                for (WaySegment es2 : segments) {
    364363                    List<Way> prims;
     
    416415
    417416    /**
     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    /**
    418432     * Find ways which are crossing without sharing a node.
    419433     * @param w way that is to be checked
  • trunk/src/org/openstreetmap/josm/data/validation/tests/PowerLines.java

    r18324 r18553  
    44import static org.openstreetmap.josm.tools.I18n.tr;
    55
     6import java.awt.geom.Point2D;
    67import java.util.ArrayList;
    78import java.util.Arrays;
    89import java.util.Collection;
    9 import java.util.LinkedHashSet;
     10import java.util.Collections;
     11import java.util.EnumSet;
     12import java.util.HashMap;
     13import java.util.HashSet;
    1014import java.util.List;
     15import java.util.Map;
    1116import java.util.Set;
    1217
     18import org.openstreetmap.josm.data.coor.ILatLon;
    1319import org.openstreetmap.josm.data.osm.Node;
    1420import org.openstreetmap.josm.data.osm.OsmPrimitive;
     
    1622import org.openstreetmap.josm.data.osm.RelationMember;
    1723import org.openstreetmap.josm.data.osm.Way;
     24import org.openstreetmap.josm.data.osm.WaySegment;
    1825import org.openstreetmap.josm.data.osm.visitor.paint.relations.Multipolygon;
    1926import org.openstreetmap.josm.data.osm.visitor.paint.relations.Multipolygon.JoinedWay;
    2027import org.openstreetmap.josm.data.osm.visitor.paint.relations.MultipolygonCache;
     28import org.openstreetmap.josm.data.preferences.sources.ValidatorPrefHelper;
    2129import org.openstreetmap.josm.data.validation.Severity;
    2230import org.openstreetmap.josm.data.validation.Test;
    2331import org.openstreetmap.josm.data.validation.TestError;
    2432import org.openstreetmap.josm.gui.progress.ProgressMonitor;
     33import org.openstreetmap.josm.spi.preferences.Config;
    2534import org.openstreetmap.josm.tools.Geometry;
     35import org.openstreetmap.josm.tools.Logging;
     36import org.openstreetmap.josm.tools.Utils;
    2637
    2738/**
    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.
    3047 */
    3148public 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;
    3556    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();
    3663
    3764    /** 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);
    3966    /** 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");
    4168    /** 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");
    4370    /** 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");
    4572    /** 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<>();
    5285    private final List<OsmPrimitive> powerStations = new ArrayList<>();
    5386
     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
    5491    /**
    5592     * Constructs a new {@code PowerLines} test.
    5693     */
    5794    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"));
    7597    }
    7698
     
    80102        boolean connectedToUnrelated = false;
    81103        for (Way parent : n.getParentWays()) {
    82             if (parent.hasTag("power", "line", "minor_line", "cable"))
     104            if (parent.hasTag(POWER, "line", MINOR_LINE, "cable"))
    83105                nodeInLineOrCable = true;
    84             else if (!isRelatedToPower(parent)) {
     106            else if (!isRelatedToPower(parent))
    85107                connectedToUnrelated = true;
    86             }
    87108        }
    88109        if (nodeInLineOrCable && connectedToUnrelated)
     
    90111    }
    91112
     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
    92573    private static boolean isRelatedToPower(Way way) {
    93         if (way.hasTag("power") || way.hasTag("building"))
     574        if (way.hasTag(POWER) || way.hasTag(BUILDING))
    94575            return true;
    95576        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))) {
    97578                for (RelationMember rm : ((Relation) ref).getMembers()) {
    98579                    if (way == rm.getMember())
     
    104585    }
    105586
    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     */
    140634    protected final boolean isInPowerStation(Node n) {
    141635        for (OsmPrimitive station : powerStations) {
     
    165659     * @return {@code true} if power key is set and equal to line/minor_line
    166660     */
    167     protected static final boolean isPowerLine(Way w) {
     661    protected static boolean isPowerLine(Way w) {
    168662        return isPowerIn(w, POWER_LINE_TAGS);
    169663    }
     
    172666     * Determines if the specified primitive denotes a power station.
    173667     * @param p The primitive to be tested
    174      * @return {@code true} if power key is set and equal to station/sub_station/plant
    175      */
    176     protected static final boolean 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) {
    177671        return isPowerIn(p, POWER_STATION_TAGS) || isBuildingIn(p, BUILDING_STATION_TAGS);
    178672    }
    179673
    180674    /**
    181      * Determines if the specified node denotes a power tower/pole.
     675     * Determines if the specified node denotes a power support feature.
    182676     * @param n The node to be tested
    183      * @return {@code true} if power key is set and equal to tower/pole
    184      */
    185     protected static final boolean 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) {
    186680        return isPowerIn(n, POWER_TOWER_TAGS);
    187681    }
     
    190684     * Determines if the specified node denotes a power infrastructure allowed on a power line.
    191685     * @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);
    196691    }
    197692
     
    203698     */
    204699    private static boolean isPowerIn(OsmPrimitive p, Collection<String> values) {
    205         return p.hasTag("power", values);
     700        return p.hasTag(POWER, values);
    206701    }
    207702
     
    213708     */
    214709    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();
    219721        powerStations.clear();
    220         badConnections.clear();
    221         missingTowerOrPole.clear();
     722        refDiscontinuities.clear();
     723        segmentRefDiscontinuities.clear();
     724        wrongLineType.clear();
    222725    }
    223726}
  • trunk/src/org/openstreetmap/josm/data/validation/util/ValUtil.java

    r16628 r18553  
    1111
    1212import org.openstreetmap.josm.data.coor.EastNorth;
     13import org.openstreetmap.josm.data.coor.ILatLon;
    1314import org.openstreetmap.josm.data.osm.Node;
    1415import org.openstreetmap.josm.data.osm.Way;
     16import org.openstreetmap.josm.data.projection.ProjectionRegistry;
    1517import org.openstreetmap.josm.data.validation.OsmValidator;
    1618import org.openstreetmap.josm.tools.CheckParameterUtil;
     
    4547        double griddetail = OsmValidator.getGridDetail();
    4648
     49        final EastNorth en1 = n1.getEastNorth();
     50        final EastNorth en2 = n2.getEastNorth();
    4751        // First, round coordinates
    4852        // 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);
    5357        // CHECKSTYLE.ON: SingleSpaceSeparator
    5458
     
    6771        // Then floor coordinates, in case the way is in the border of the cell.
    6872        // 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);
    7377        // CHECKSTYLE.ON: SingleSpaceSeparator
    7478
     
    100104     */
    101105    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) {
    102121        CheckParameterUtil.ensureParameterNotNull(n1, "n1");
    103122        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);
    105125    }
    106126
  • trunk/src/org/openstreetmap/josm/gui/dialogs/InspectPrimitiveDataText.java

    r17760 r18553  
    3333import org.openstreetmap.josm.tools.Geometry;
    3434import org.openstreetmap.josm.tools.Pair;
     35import org.openstreetmap.josm.tools.Utils;
    3536
    3637/**
     
    175176                    ProjectionRegistry.getProjection().eastNorth2latlon(Geometry.getCentroid(((IWay<?>) o).getNodes()))));
    176177            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));
    180189            }
    181190            if (o instanceof Way && ((Way) o).concernsArea() && ((Way) o).isClosed()) {
  • trunk/src/org/openstreetmap/josm/tools/Geometry.java

    r18494 r18553  
    288288     * @param p4 the coordinates of the end point of the second specified line segment
    289289     * @return EastNorth null if no intersection was found, the EastNorth coordinates of the intersection otherwise
     290     * @see #getSegmentSegmentIntersection(ILatLon, ILatLon, ILatLon, ILatLon)
    290291     */
    291292    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        }
    297300
    298301        double x1 = p1.getX();
     
    304307        double x4 = p4.getX();
    305308        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) {
    306366
    307367        //TODO: do this locally.
     
    334394                if (u < 0) u = 0;
    335395                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};
    337397            } else {
    338398                return null;
  • trunk/src/org/openstreetmap/josm/tools/Utils.java

    r18208 r18553  
    487487     */
    488488    public static String md5Hex(String data) {
    489         MessageDigest md = null;
     489        MessageDigest md;
    490490        try {
    491491            md = MessageDigest.getInstance("MD5");
     
    13021302            return distance > 0 && distance <= 2;
    13031303        }
     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);
    13041342    }
    13051343
     
    17051743    public static Date getJavaExpirationDate() {
    17061744        try {
    1707             Object value = null;
     1745            Object value;
    17081746            Class<?> c = Class.forName("com.sun.deploy.config.BuiltInProperties");
    17091747            try {
  • trunk/test/unit/org/openstreetmap/josm/data/osm/WayTest.java

    r17275 r18553  
    88
    99import java.util.Arrays;
     10import java.util.Collections;
    1011import java.util.HashSet;
    1112
     
    4950        Way way = new Way(1);
    5051        assertFalse(way.getBBox().isValid());
    51         way.setNodes(Arrays.asList(n1));
     52        way.setNodes(Collections.singletonList(n1));
    5253        assertFalse(way.getBBox().isValid());
    53         way.setNodes(Arrays.asList(n2));
     54        way.setNodes(Collections.singletonList(n2));
    5455        assertTrue(way.getBBox().isValid());
    5556        way.setNodes(Arrays.asList(n1, n2));
     
    116117        assertEquals(Arrays.asList(n1, n2, n1), way.getNodes());
    117118        way.setNodes(Arrays.asList(n1, n2, n3, n4, n1));
    118         way.removeNodes(new HashSet<>(Arrays.asList(n1)));
     119        way.removeNodes(new HashSet<>(Collections.singletonList(n1)));
    119120        assertEquals(Arrays.asList(n2, n3, n4, n2), way.getNodes());
    120121    }
     
    135136        assertThrows(IllegalArgumentException.class, () -> new Way().load(new NodeData()));
    136137    }
     138
     139    @Test
     140    void getLongestSegmentLength() {
     141        DataSet ds = new DataSet();
     142        Node n1 = new Node(1);
     143        Node n2 = new Node(2);
     144        Node n3 = new Node(3);
     145        Node n4 = new Node(4);
     146        n1.setCoor(new LatLon(0.01, 0.01));
     147        n2.setCoor(new LatLon(0.02, 0.02));
     148        n3.setCoor(new LatLon(0.03, 0.03));
     149        n4.setCoor(new LatLon(0.05, 0.05));
     150        ds.addPrimitive(n1);
     151        ds.addPrimitive(n2);
     152        ds.addPrimitive(n3);
     153        ds.addPrimitive(n4);
     154        Way way = new Way(1);
     155        ds.addPrimitive(way);
     156
     157        assertEquals(0.0, way.getLongestSegmentLength());
     158        way.setNodes(Arrays.asList(n1, n2, n2, n3, n4));
     159
     160        assertEquals(3148.5902810874577, way.getLongestSegmentLength());
     161    }
    137162}
  • trunk/test/unit/org/openstreetmap/josm/tools/UtilsTest.java

    r18037 r18553  
    128128    @Test
    129129    void testPositionListString() {
    130         assertEquals("1", Utils.getPositionListString(Arrays.asList(1)));
     130        assertEquals("1", Utils.getPositionListString(Collections.singletonList(1)));
    131131        assertEquals("1-2", Utils.getPositionListString(Arrays.asList(1, 2)));
    132132        assertEquals("1-3", Utils.getPositionListString(Arrays.asList(1, 2, 3)));
     
    251251    @Test
    252252    void testJoinAsHtmlUnorderedList() {
    253         List<? extends Object> items = Arrays.asList("1", Integer.valueOf(2));
     253        List<?> items = Arrays.asList("1", 2);
    254254        assertEquals("<ul><li>1</li><li>2</li></ul>", Utils.joinAsHtmlUnorderedList(items));
    255255        assertEquals("<ul></ul>", Utils.joinAsHtmlUnorderedList(Collections.emptyList()));
     
    532532        assertEquals("Hello World", output);
    533533    }
     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    }
    534547}
Note: See TracChangeset for help on using the changeset viewer.