Ticket #20716: josm_20716_power_v9.patch

File josm_20716_power_v9.patch, 33.1 KB (added by gaben, 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  
    650650        return length;
    651651    }
    652652
     653    /**
     654     * Replies the segment lengths of the way as computed by {@link LatLon#greatCircleDistance}.
     655     * @return The segment lengths of a way in metres, following way direction
     656     * @since xxx
     657     */
     658    public double[] getSegmentLengths() {
     659        double[] segmentLengths = new double[nodes.length - 1];
     660        Node prevNode = this.firstNode();
     661
     662        for (int i = 1; i < nodes.length; i++) {
     663            Node n = nodes[i];
     664
     665            double distance = n.getCoor().greatCircleDistance(prevNode.getCoor());
     666            segmentLengths[i - 1] = distance;
     667            prevNode = n;
     668        }
     669
     670        return segmentLengths;
     671    }
     672
    653673    /**
    654674     * Replies the length of the longest segment of the way, in metres, as computed by {@link LatLon#greatCircleDistance}.
    655675     * @return The length of the segment, in metres
  • src/org/openstreetmap/josm/data/validation/tests/PowerLines.java

    IDEA additional info:
    Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
    <+>UTF-8
    diff --git a/src/org/openstreetmap/josm/data/validation/tests/PowerLines.java b/src/org/openstreetmap/josm/data/validation/tests/PowerLines.java
    a b  
    11// License: GPL. For details, see LICENSE file.
    22package org.openstreetmap.josm.data.validation.tests;
    33
     4import static org.openstreetmap.josm.gui.MainApplication.getLayerManager;
    45import static org.openstreetmap.josm.tools.I18n.tr;
    56
    67import java.util.ArrayList;
    78import java.util.Arrays;
    89import java.util.Collection;
    9 import java.util.LinkedHashSet;
     10import java.util.Collections;
     11import java.util.HashSet;
    1012import java.util.List;
    1113import java.util.Set;
    1214
     15import org.openstreetmap.josm.data.coor.EastNorth;
    1316import org.openstreetmap.josm.data.osm.Node;
    1417import org.openstreetmap.josm.data.osm.OsmPrimitive;
    1518import org.openstreetmap.josm.data.osm.Relation;
    1619import org.openstreetmap.josm.data.osm.RelationMember;
    1720import org.openstreetmap.josm.data.osm.Way;
     21import org.openstreetmap.josm.data.osm.WaySegment;
    1822import org.openstreetmap.josm.data.osm.visitor.paint.relations.Multipolygon;
    1923import org.openstreetmap.josm.data.osm.visitor.paint.relations.Multipolygon.JoinedWay;
    2024import org.openstreetmap.josm.data.osm.visitor.paint.relations.MultipolygonCache;
     25import org.openstreetmap.josm.data.preferences.sources.ValidatorPrefHelper;
    2126import org.openstreetmap.josm.data.validation.Severity;
    2227import org.openstreetmap.josm.data.validation.Test;
    2328import org.openstreetmap.josm.data.validation.TestError;
    2429import org.openstreetmap.josm.gui.progress.ProgressMonitor;
     30import org.openstreetmap.josm.spi.preferences.Config;
    2531import org.openstreetmap.josm.tools.Geometry;
     32import org.openstreetmap.josm.tools.Logging;
     33import org.openstreetmap.josm.tools.Pair;
     34import org.openstreetmap.josm.tools.Utils;
    2635
    2736/**
    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.
     37 * Checks for
     38 * <ul>
     39 * <li>nodes in power lines/minor_lines that do not have a power=tower/pole/portal tag
     40 * <li>nodes where the reference numbering not consistent
     41 * <li>ways where are unusually long segments without line support feature
     42 * <li>ways where the line type is possibly misused
     43 * </ul>
     44 * See #7812 and #20716 for discussions about this test.
    3045 */
    3146public class PowerLines extends Test {
    3247
    33     /** Test identifier */
    34     protected static final int POWER_LINES = 2501;
     48    // Test identifiers
     49    protected static final int POWER_SUPPORT = 2501;
    3550    protected static final int POWER_CONNECTION = 2502;
     51    protected static final int POWER_SEGMENT_LENGTH = 2503;
     52    protected static final int POWER_LOCAL_REF_CONTINUITY = 2504;
     53    protected static final int POWER_WAY_REF_CONTINUITY = 2505;
     54    protected static final int POWER_LINE_TYPE = 2506;
     55
     56    protected static final String PREFIX = ValidatorPrefHelper.PREFIX + "." + PowerLines.class.getSimpleName();
     57    protected static double hillyCompensation;
     58    protected static double hillyThreshold;
    3659
    3760    /** Values for {@code power} key interpreted as power lines */
    3861    static final Collection<String> POWER_LINE_TAGS = Arrays.asList("line", "minor_line");
    3962    /** Values for {@code power} key interpreted as power towers */
    40     static final Collection<String> POWER_TOWER_TAGS = Arrays.asList("tower", "pole");
     63    static final Collection<String> POWER_TOWER_TAGS = Arrays.asList("catenary_mast", "pole", "portal", "tower");
    4164    /** 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");
     65    static final Collection<String> POWER_STATION_TAGS = Arrays.asList("generator", "plant", "substation");
    4366    /** Values for {@code building} key interpreted as power stations */
    4467    static final Collection<String> BUILDING_STATION_TAGS = Arrays.asList("transformer_tower");
    4568    /** 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");
     69    static final Collection<String> POWER_INFRASTRUCTURE_TAGS = Arrays.asList("compensator", "converter", "generator",
     70            "insulator", "switch", "switchgear", "terminal", "transformer");
    4871
    49     private final Set<Node> badConnections = new LinkedHashSet<>();
    50     private final Set<Node> missingTowerOrPole = new LinkedHashSet<>();
     72    private final Set<Node> badConnections = new HashSet<>();
     73    private final Set<Node> missingTags = new HashSet<>();
     74    private final Set<Way> wrongLineType = new HashSet<>();
     75    private final Set<WaySegment> missingNodes = new HashSet<>();
     76    private final Set<OsmPrimitive> refDiscontinuities = new HashSet<>();
    5177
     78    private final List<Set<Node>> segmentRefDiscontinuities = new ArrayList<>();
    5279    private final List<OsmPrimitive> powerStations = new ArrayList<>();
    5380
     81    private final Collection<Way> datasetWaterways = new HashSet<>(64);
     82
    5483    /**
    5584     * Constructs a new {@code PowerLines} test.
    5685     */
    5786    public PowerLines() {
    58         super(tr("Power lines"), tr("Checks for nodes in power lines that do not have a power=tower/pole tag."));
     87        super(tr("Power lines"), tr("Checks if power line missing a support node and " +
     88                "for nodes in power lines that do not have a power=tower/pole tag"));
    5989    }
    6090
    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         }
     91    /** Power line support features ref=* numbering direction. */
     92    private enum NumberingDirection {
     93        /** No direction */
     94        NONE,
     95        /** Numbering follows way direction */
     96        SAME,
     97        /** Numbering goes opposite way direction */
     98        OPPOSITE
    7599    }
    76100
    77101    @Override
     
    89113            badConnections.add(n);
    90114    }
    91115
    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 (int i = 1; i < w.getRealNodesCount(); i++) {
     137                final Node n = w.getNode(i);
     138
     139                /// handle power station line connections (eg. power=line + line=*)
     140                if (isConnectedToStationLine(n, w)) {
     141                    prevNode = n;
     142                    continue;   // skip, it would be false positive
    100143                }
     144
     145                /// handle missing power line support tags (e.g. tower)
     146                if (!isPowerTower(n) && !isPowerInfrastructure(n) && IN_DOWNLOADED_AREA.test(n)
     147                        && (!w.isFirstLastNode(n) || !isPowerStation(n)))
     148                    missingTags.add(n);
     149
     150                /// handle missing nodes
     151                double segmentLen = n.getCoor().greatCircleDistance(prevNode.getCoor());
     152                final Pair<Node, Node> pair = Pair.create(prevNode, n);
     153                final Set<Way> crossingWaterWays = new HashSet<>(8);
     154                final Set<Node> crossingPositions = new HashSet<>(8);
     155                findCrossings(datasetWaterways, w, pair, crossingWaterWays, crossingPositions);
     156
     157                if (!crossingWaterWays.isEmpty()) {
     158                    double compensation = calculateIntersectingLen(prevNode, crossingPositions);
     159                    segmentLen -= compensation;
     160                }
     161
     162                if (segmentCount > 4
     163                        && segmentLen > mean * baseThreshold
     164                        && !isPowerInfrastructure(n)
     165                        && IN_DOWNLOADED_AREA.test(n))
     166                    missingNodes.add(WaySegment.forNodePair(w, prevNode, n));
     167
     168                /// handle wrong line types
     169                if (!crossingWaterWays.isEmpty())
     170                    isCrossingWater = true;
     171
     172                if (n.hasTag("power", "pole"))
     173                    poleCount++;
     174                else if (n.hasTag("power", "tower", "portal"))
     175                    towerCount++;
     176
     177                prevNode = n;
    101178            }
     179
     180            /// handle ref=* numbering discontinuities
     181            if (detectDiscontinuity(w, refDiscontinuities, segmentRefDiscontinuities))
     182                refDiscontinuities.add(w);
     183
     184            /// handle wrong line types
     185            if (((poleCount > towerCount && w.hasTag("power", "line"))
     186                    || (poleCount < towerCount && w.hasTag("power", "minor_line")
     187                    && !isCrossingWater
     188                    && !isContinuesAsMinorLine))
     189                    && IN_DOWNLOADED_AREA.test(w))
     190                wrongLineType.add(w);
     191
     192        } else if (w.isClosed() && isPowerStation(w)) {
     193            powerStations.add(w);
    102194        }
    103         return false;
    104195    }
    105196
    106197    @Override
     
    113204    @Override
    114205    public void startTest(ProgressMonitor progressMonitor) {
    115206        super.startTest(progressMonitor);
    116         clearCollections();
     207        // the test run can take a bit of time, show detailed progress
     208        setShowElements(true);
     209
     210        hillyCompensation = Config.getPref().getDouble(PREFIX + ".hilly_compensation", 0.2);
     211        hillyThreshold = Config.getPref().getDouble(PREFIX + ".hilly_threshold", 4.0);
     212
     213        // collect all waterways
     214        getLayerManager()
     215                .getActiveDataSet()
     216                .getWays()
     217                .parallelStream()
     218                .filter(way -> way.isUsable() && (concernsWaterArea(way)
     219                        || way.referrers(Relation.class).anyMatch(PowerLines::concernsWaterArea)))
     220                .forEach(datasetWaterways::add);
    117221    }
    118222
    119223    @Override
    120224    public void endTest() {
    121         for (Node n : missingTowerOrPole) {
     225        for (Node n : missingTags) {
    122226            if (!isInPowerStation(n)) {
    123                 errors.add(TestError.builder(this, Severity.WARNING, POWER_LINES)
    124                         .message(tr("Missing power tower/pole within power line"))
     227                errors.add(TestError.builder(this, Severity.WARNING, POWER_SUPPORT)
     228                        // the "missing tag" grouping can become broken if the MapCSS message get reworded
     229                        .message(tr("missing tag"), tr("node without power=*"))
    125230                        .primitives(n)
    126231                        .build());
    127232            }
     
    130235        for (Node n : badConnections) {
    131236            errors.add(TestError.builder(this, Severity.WARNING, POWER_CONNECTION)
    132237                    .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());
     238                            + "which is not related to the power infrastructure"))
     239                    .primitives(n)
     240                    .build());
     241        }
     242
     243        for (WaySegment s : missingNodes) {
     244            errors.add(TestError.builder(this, Severity.WARNING, POWER_SEGMENT_LENGTH)
     245                    .message(tr("Possibly missing line support node within power line"))
     246                    .primitives(s.getFirstNode(), s.getSecondNode())
     247                    .highlightWaySegments(new HashSet<>(Collections.singleton(s)))
     248                    .build());
    135249        }
    136         clearCollections();
     250
     251        for (OsmPrimitive p : refDiscontinuities) {
     252            if (p instanceof Way)
     253                errors.add(TestError.builder(this, Severity.WARNING, POWER_WAY_REF_CONTINUITY)
     254                        .message(tr("Mixed reference numbering"))
     255                        .primitives(p)
     256                        .build());
     257        }
     258
     259        final String discontinuityMsg = tr("Reference numbering don''t match majority of way''s nodes");
     260
     261        for (OsmPrimitive p : refDiscontinuities) {
     262            if (p instanceof Node)
     263                errors.add(TestError.builder(this, Severity.WARNING, POWER_LOCAL_REF_CONTINUITY)
     264                        .message(discontinuityMsg)
     265                        .primitives(p)
     266                        .build());
     267        }
     268
     269        for (Set<Node> nodes : segmentRefDiscontinuities) {
     270            errors.add(TestError.builder(this, Severity.WARNING, POWER_LOCAL_REF_CONTINUITY)
     271                    .message(discontinuityMsg)
     272                    .primitives(nodes)
     273                    .build());
     274        }
     275
     276        for (Way w : wrongLineType) {
     277            errors.add(TestError.builder(this, Severity.WARNING, POWER_LINE_TYPE)
     278                    .message(tr("Possibly wrong power line type used"))
     279                    .primitives(w)
     280                    .build());
     281        }
     282
    137283        super.endTest();
    138284    }
    139285
     286    /**
     287     * The summarized length (in metres) of a way where a power line hangs over a water area.
     288     * @param ref Reference point
     289     * @param crossingNodes Crossing nodes, unordered
     290     * @return The summarized length (in metres) of a way where a power line hangs over a water area
     291     */
     292    private static double calculateIntersectingLen(Node ref, Set<Node> crossingNodes) {
     293        double min = Double.POSITIVE_INFINITY;
     294        double max = Double.NEGATIVE_INFINITY;
     295
     296        for (Node n : crossingNodes) {
     297            double dist = ref.getCoor().greatCircleDistance(n.getCoor());
     298
     299            if (dist < min)
     300                min = dist;
     301            if (dist > max)
     302                max = dist;
     303        }
     304        return max - min;
     305    }
     306
     307    /**
     308     * Searches for way intersections, which intersect the {@code pair} attribute.
     309     * @param ways collection of ways to search
     310     * @param parent parent way for {@code pair} param
     311     * @param pair node pair among which search for another way
     312     * @param crossingWays found crossing ways
     313     * @param crossingPositions collection of the crossing positions
     314     * @implNote Inspired by {@code utilsplugin2/selection/NodeWayUtils.java#addWaysIntersectingWay()}
     315     */
     316    private static void findCrossings(Collection<Way> ways, Way parent, Pair<Node, Node> pair, Set<Way> crossingWays,
     317                                      Set<Node> crossingPositions) {
     318        for (Way way : ways) {
     319            if (way.isUsable()
     320                    && !crossingWays.contains(way)
     321                    && way.getBBox().intersects(parent.getBBox())) {
     322                for (Pair<Node, Node> pair2 : way.getNodePairs(false)) {
     323                    EastNorth eastNorth = Geometry.getSegmentSegmentIntersection(
     324                            pair.a.getEastNorth(), pair.b.getEastNorth(),
     325                            pair2.a.getEastNorth(), pair2.b.getEastNorth());
     326                    if (eastNorth != null) {
     327                        crossingPositions.add(new Node(eastNorth));
     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 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 ArrayList<SegmentInfo> segments = checker.getSegments();
     378        final SegmentInfo referenceSegment = checker.getLongestSegment();
     379
     380        if (referenceSegment == null && !segments.isEmpty())
     381            return true;
     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 ArrayList<SegmentInfo> segments = new ArrayList<>();
     428        private NumberingDirection direction = NumberingDirection.NONE;
     429        private Integer startIndex = null;
     430        private Integer previousRef = null;
     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                maintain(parseRef(n.get("ref")), i);
     443            }
     444
     445            // needed for creation of the last segment
     446            maintain(null, wayLength - 1);
     447        }
     448
     449        /**
     450         * Maintains class variables and constructs a new segment when necessary.
     451         * @param ref   recognised ref=* number
     452         * @param index {@link Node} index in a way
     453         */
     454        private void maintain(Integer ref, int index) {
     455            if (previousRef == null && ref != null) {
     456                // ref change: null -> number
     457                startIndex = index;
     458            } else if (previousRef != null && ref == null) {
     459                // ref change: number -> null
     460                segments.add(new SegmentInfo(startIndex, index - 1 - startIndex, previousRef, direction));
     461                direction = NumberingDirection.NONE;    // to fix directionality
     462            } else if (ref != null && previousRef != null) {
     463                // ref change: number -> number
     464                if (Math.abs(ref - previousRef) != 1) {
     465                    segments.add(new SegmentInfo(startIndex, index - 1 - startIndex, previousRef, direction));
     466                    startIndex = index;
     467                    previousRef = ref;                  // to fix directionality
     468                }
     469                direction = detectDirection(ref, previousRef);
     470            }
     471            previousRef = ref;
     472        }
     473
     474        /**
     475         * Parses integer tag values. Later can be relatively easily extended or rewritten to handle
     476         * complex references like 25/A, 25/B etc.
     477         * @param value the value to be parsed
     478         * @return parsed int or {@code null} in case of {@link NumberFormatException}
     479         */
     480        private static Integer parseRef(String value) {
     481            try {
     482                return Integer.parseInt(value);
     483            } catch (NumberFormatException ignore) {
     484                Logging.trace("The PowerLines.RefChecker couldn't parse ref=" + value + ", consider rewriting the parser");
     485                return null;
     486            }
     487        }
     488
     489        /**
     490         * Detects numbering direction. The parameters should follow way direction.
     491         * @param ref         last known reference value
     492         * @param previousRef reference value before {@code ref}
     493         * @return recognised direction
     494         */
     495        private static NumberingDirection detectDirection(int ref, int previousRef) {
     496            if (ref > previousRef)
     497                return NumberingDirection.SAME;
     498            else if (ref < previousRef)
     499                return NumberingDirection.OPPOSITE;
     500            return NumberingDirection.NONE;
     501        }
     502
     503        /**
     504         * Calculates the longest segment.
     505         * @return the longest segment, or the lowest index if there are more than one with same length and direction,
     506         * or {@code null} if there are more than one with same length and different direction
     507         */
     508        SegmentInfo getLongestSegment() {
     509            final Set<NumberingDirection> directions = new HashSet<>(4);
     510            int longestLength = -1;
     511            int counter = 0;
     512            SegmentInfo longest = null;
     513
     514            for (SegmentInfo segment : segments) {
     515                if (segment.length > longestLength) {
     516                    longestLength = segment.length;
     517                    longest = segment;
     518                    counter = 0;
     519                    directions.clear();
     520                    directions.add(segment.direction);
     521                } else if (segment.length == longestLength) {
     522                    counter++;
     523                    directions.add(segment.direction);
     524                }
     525            }
     526
     527            // there are multiple segments with the same longest length and their directions don't match
     528            if (counter > 0 && directions.size() > 1)
     529                return null;
     530
     531            return longest;
     532        }
     533
     534        /**
     535         * @return the detected segments
     536         */
     537        ArrayList<SegmentInfo> getSegments() {
     538            return segments;
     539        }
     540    }
     541
     542    private static boolean isRelatedToPower(Way way) {
     543        if (way.hasTag("power") || way.hasTag("building"))
     544            return true;
     545        for (OsmPrimitive ref : way.getReferrers()) {
     546            if (ref instanceof Relation && ref.isMultipolygon() && (ref.hasTag("power") || ref.hasTag("building"))) {
     547                for (RelationMember rm : ((Relation) ref).getMembers()) {
     548                    if (way == rm.getMember())
     549                        return true;
     550                }
     551            }
     552        }
     553        return false;
     554    }
     555
     556    /**
     557     * Determines if the current node connected to a line which used usually used inside power stations.
     558     * @param n node to check
     559     * @param w parent way of {@code n}
     560     * @return {@code true} if {@code n} connected to power=line + line=*
     561     */
     562    private static boolean isConnectedToStationLine(Node n, Way w) {
     563        for (OsmPrimitive p : n.getReferrers()) {
     564            if (p instanceof Way && !p.equals(w) && isPowerLine((Way) p) && p.hasKey("line"))
     565                return true;
     566        }
     567        return false;
     568    }
     569
     570    /**
     571     * Checks if the way continues as a power=minor_line.
     572     * @param way Way to be checked
     573     * @return {@code true} if the way continues as a power=minor_line
     574     */
     575    private static boolean isContinuesAsMinorLine(Way way) {
     576        return way.firstNode().referrers(Way.class).filter(referrer -> !way.equals(referrer)).anyMatch(PowerLines::isMinorLine) ||
     577                way.lastNode().referrers(Way.class).filter(referrer -> !way.equals(referrer)).anyMatch(PowerLines::isMinorLine);
     578    }
     579
     580    /**
     581     * Checks if the given primitive denotes a power=minor_line.
     582     * @param p primitive to be checked
     583     * @return {@code true} if the given primitive denotes a power=minor_line
     584     */
     585    private static boolean isMinorLine(OsmPrimitive p) {
     586        return p.hasTag("power", "minor_line");
     587    }
     588
     589    /**
     590     * Check if primitive has a tag that marks it as a water area or boundary of a water area.
     591     * @param p the primitive
     592     * @return {@code true} if primitive has a tag that marks it as a water area or boundary of a water area
     593     */
     594    private static boolean concernsWaterArea(OsmPrimitive p) {
     595        return p.hasTag("water", "river", "lake") || p.hasKey("waterway") || p.hasTag("natural", "coastline");
     596    }
     597
     598    /**
     599     * Checks if the given node is inside a power station.
     600     * @param n Node to be checked
     601     */
    140602    protected final boolean isInPowerStation(Node n) {
    141603        for (OsmPrimitive station : powerStations) {
    142604            List<List<Node>> nodesLists = new ArrayList<>();
     
    171633    /**
    172634     * Determines if the specified primitive denotes a power station.
    173635     * @param p The primitive to be tested
    174      * @return {@code true} if power key is set and equal to station/sub_station/plant
     636     * @return {@code true} if power key is set and equal to generator/substation/plant
    175637     */
    176638    protected static final boolean isPowerStation(OsmPrimitive p) {
    177639        return isPowerIn(p, POWER_STATION_TAGS) || isBuildingIn(p, BUILDING_STATION_TAGS);
    178640    }
    179641
    180642    /**
    181      * Determines if the specified node denotes a power tower/pole.
     643     * Determines if the specified node denotes a power support feature.
    182644     * @param n The node to be tested
    183      * @return {@code true} if power key is set and equal to tower/pole
     645     * @return {@code true} if power key is set and equal to pole/tower/portal/catenary_mast
    184646     */
    185647    protected static final boolean isPowerTower(Node n) {
    186648        return isPowerIn(n, POWER_TOWER_TAGS);
     
    189651    /**
    190652     * Determines if the specified node denotes a power infrastructure allowed on a power line.
    191653     * @param n The node to be tested
    192      * @return True if power key is set and equal to switch/tranformer/busbar/generator
     654     * @return {@code true} if power key is set and equal to compensator/converter/generator/insulator
     655     * /switch/switchgear/terminal/transformer
    193656     */
    194     protected static final boolean isPowerAllowed(Node n) {
    195         return isPowerIn(n, POWER_ALLOWED_TAGS);
     657    protected static final boolean isPowerInfrastructure(Node n) {
     658        return isPowerIn(n, POWER_INFRASTRUCTURE_TAGS);
    196659    }
    197660
    198661    /**
     
    215678        return p.hasTag("building", values);
    216679    }
    217680
    218     private void clearCollections() {
     681    @Override
     682    public void clear() {
     683        super.clear();
    219684        powerStations.clear();
    220685        badConnections.clear();
    221         missingTowerOrPole.clear();
     686        missingTags.clear();
     687        missingNodes.clear();
     688        wrongLineType.clear();
     689        refDiscontinuities.clear();
     690        segmentRefDiscontinuities.clear();
     691        datasetWaterways.clear();
    222692    }
    223693}
  • 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  
    13031303        }
    13041304    }
    13051305
     1306    /**
     1307     * Calculates the <a href="https://en.wikipedia.org/wiki/Standard_deviation">standard deviation</a>.
     1308     * @param values an array of values
     1309     * @return standard deviation of the given array
     1310     * @see #getStandardDeviation(double[], double)
     1311     */
     1312    public static double getStandardDeviation(double[] values) {
     1313        return getStandardDeviation(values, Double.NaN);
     1314    }
     1315
     1316    /**
     1317     * Calculates the <a href="https://en.wikipedia.org/wiki/Standard_deviation">standard deviation</a> with the given
     1318     * mean value.
     1319     * @param values an array of values
     1320     * @param mean precalculated average value of the array
     1321     * @return standard deviation of the given array
     1322     * @see #getStandardDeviation(double[])
     1323     */
     1324    public static double getStandardDeviation(double[] values, double mean) {
     1325        double standardDeviation = 0;
     1326
     1327        if (Double.isNaN(mean)) {
     1328            mean = Arrays.stream(values).average().orElse(0);
     1329        }
     1330
     1331        for (double length : values) {
     1332            standardDeviation += Math.pow(length - mean, 2);
     1333        }
     1334
     1335        return Math.sqrt(standardDeviation / values.length);
     1336    }
     1337
    13061338    /**
    13071339     * A ForkJoinWorkerThread that will always inherit caller permissions,
    13081340     * unlike JDK's InnocuousForkJoinWorkerThread, used if a security manager exists.