Ticket #20716: josm_20716_power_v10.patch

File josm_20716_power_v10.patch, 32.6 KB (added by GerdP, 4 years ago)

v9 with some minor changes

  • src/org/openstreetmap/josm/data/osm/Way.java

     
    651651    }
    652652
    653653    /**
     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
     673    /**
    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
    656676     * @since 8320
  • src/org/openstreetmap/josm/data/validation/tests/PowerLines.java

     
    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.EnumSet;
     12import java.util.HashSet;
    1013import java.util.List;
    1114import java.util.Set;
    1215
     16import org.openstreetmap.josm.data.coor.EastNorth;
    1317import org.openstreetmap.josm.data.osm.Node;
    1418import org.openstreetmap.josm.data.osm.OsmPrimitive;
    1519import org.openstreetmap.josm.data.osm.Relation;
    1620import org.openstreetmap.josm.data.osm.RelationMember;
    1721import org.openstreetmap.josm.data.osm.Way;
     22import org.openstreetmap.josm.data.osm.WaySegment;
    1823import org.openstreetmap.josm.data.osm.visitor.paint.relations.Multipolygon;
    1924import org.openstreetmap.josm.data.osm.visitor.paint.relations.Multipolygon.JoinedWay;
    2025import org.openstreetmap.josm.data.osm.visitor.paint.relations.MultipolygonCache;
     26import org.openstreetmap.josm.data.preferences.sources.ValidatorPrefHelper;
    2127import org.openstreetmap.josm.data.validation.Severity;
    2228import org.openstreetmap.josm.data.validation.Test;
    2329import org.openstreetmap.josm.data.validation.TestError;
    2430import org.openstreetmap.josm.gui.progress.ProgressMonitor;
     31import org.openstreetmap.josm.spi.preferences.Config;
    2532import org.openstreetmap.josm.tools.Geometry;
     33import org.openstreetmap.josm.tools.Logging;
     34import org.openstreetmap.josm.tools.Pair;
     35import org.openstreetmap.josm.tools.Utils;
    2636
    2737/**
    28  * Checks for nodes in power lines/minor_lines that do not have a power=tower/pole tag.<br>
    29  * See #7812 for discussions about this test.
     38 * Checks for
     39 * <ul>
     40 * <li>nodes in power lines/minor_lines that do not have a power=tower/pole/portal tag
     41 * <li>nodes where the reference numbering not consistent
     42 * <li>ways where are unusually long segments without line support feature
     43 * <li>ways where the line type is possibly misused
     44 * </ul>
     45 * See #7812 and #20716 for discussions about this test.
    3046 */
    3147public class PowerLines extends Test {
    3248
    33     /** Test identifier */
    34     protected static final int POWER_LINES = 2501;
     49    // Test identifiers
     50    protected static final int POWER_SUPPORT = 2501;
    3551    protected static final int POWER_CONNECTION = 2502;
     52    protected static final int POWER_SEGMENT_LENGTH = 2503;
     53    protected static final int POWER_LOCAL_REF_CONTINUITY = 2504;
     54    protected static final int POWER_WAY_REF_CONTINUITY = 2505;
     55    protected static final int POWER_LINE_TYPE = 2506;
    3656
     57    protected static final String PREFIX = ValidatorPrefHelper.PREFIX + "." + PowerLines.class.getSimpleName();
     58    private double hillyCompensation;
     59    private double hillyThreshold;
     60
    3761    /** Values for {@code power} key interpreted as power lines */
    3862    static final Collection<String> POWER_LINE_TAGS = Arrays.asList("line", "minor_line");
    3963    /** Values for {@code power} key interpreted as power towers */
    40     static final Collection<String> POWER_TOWER_TAGS = Arrays.asList("tower", "pole");
     64    static final Collection<String> POWER_TOWER_TAGS = Arrays.asList("catenary_mast", "pole", "portal", "tower");
    4165    /** Values for {@code power} key interpreted as power stations */
    42     static final Collection<String> POWER_STATION_TAGS = Arrays.asList("station", "sub_station", "substation", "plant", "generator");
     66    static final Collection<String> POWER_STATION_TAGS = Arrays.asList("generator", "plant", "substation");
    4367    /** Values for {@code building} key interpreted as power stations */
    4468    static final Collection<String> BUILDING_STATION_TAGS = Arrays.asList("transformer_tower");
    4569    /** 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");
     70    static final Collection<String> POWER_INFRASTRUCTURE_TAGS = Arrays.asList("compensator", "converter", "generator",
     71            "insulator", "switch", "switchgear", "terminal", "transformer");
    4872
    49     private final Set<Node> badConnections = new LinkedHashSet<>();
    50     private final Set<Node> missingTowerOrPole = new LinkedHashSet<>();
     73    private final Set<Node> badConnections = new HashSet<>();
     74    private final Set<Node> missingTags = new HashSet<>();
     75    private final Set<Way> wrongLineType = new HashSet<>();
     76    private final Set<WaySegment> missingNodes = new HashSet<>();
     77    private final Set<OsmPrimitive> refDiscontinuities = new HashSet<>();
    5178
     79    private final List<Set<Node>> segmentRefDiscontinuities = new ArrayList<>();
    5280    private final List<OsmPrimitive> powerStations = new ArrayList<>();
    5381
     82    private final Collection<Way> datasetWaterways = new HashSet<>(64);
     83
    5484    /**
    5585     * Constructs a new {@code PowerLines} test.
    5686     */
    5787    public PowerLines() {
    58         super(tr("Power lines"), tr("Checks for nodes in power lines that do not have a power=tower/pole tag."));
     88        super(tr("Power lines"), tr("Checks if power line missing a support node and " +
     89                "for nodes in power lines that do not have a power=tower/pole tag"));
    5990    }
    6091
    61     @Override
    62     public void visit(Way w) {
    63         if (w.isUsable()) {
    64             if (isPowerLine(w) && !w.hasTag("location", "underground")) {
    65                 for (Node n : w.getNodes()) {
    66                     if (!isPowerTower(n) && !isPowerAllowed(n) && IN_DOWNLOADED_AREA.test(n)
    67                         && (!w.isFirstLastNode(n) || !isPowerStation(n))) {
    68                         missingTowerOrPole.add(n);
    69                     }
    70                 }
    71             } else if (w.isClosed() && isPowerStation(w)) {
    72                 powerStations.add(w);
    73             }
    74         }
     92    /** Power line support features ref=* numbering direction. */
     93    private enum NumberingDirection {
     94        /** No direction */
     95        NONE,
     96        /** Numbering follows way direction */
     97        SAME,
     98        /** Numbering goes opposite way direction */
     99        OPPOSITE
    75100    }
    76101
    77102    @Override
     
    89114            badConnections.add(n);
    90115    }
    91116
    92     private static boolean isRelatedToPower(Way way) {
    93         if (way.hasTag("power") || way.hasTag("building"))
    94             return true;
    95         for (OsmPrimitive ref : way.getReferrers()) {
    96             if (ref instanceof Relation && ref.isMultipolygon() && (ref.hasTag("power") || ref.hasTag("building"))) {
    97                 for (RelationMember rm : ((Relation) ref).getMembers()) {
    98                     if (way == rm.getMember())
    99                         return true;
     117    @Override
     118    public void visit(Way w) {
     119        if (!isPrimitiveUsable(w)) return;
     120
     121        if (isPowerLine(w) && !w.hasKey("line") && !w.hasTag("location", "underground")) {
     122            final int segmentCount = w.getNodesCount() - 1;
     123            final double mean = w.getLength() / segmentCount;
     124            final double stdDev = Utils.getStandardDeviation(w.getSegmentLengths(), mean);
     125            final boolean isContinuesAsMinorLine = isContinuesAsMinorLine(w);
     126            boolean isCrossingWater = false;
     127            int poleCount = 0;
     128            int towerCount = 0;
     129            Node prevNode = w.firstNode();
     130
     131            double baseThreshold = w.hasTag("power", "line") ? 1.6 : 1.8;
     132            if (mean / stdDev < hillyThreshold) {
     133                //compensate for possibly hilly areas where towers can't be put anywhere
     134                baseThreshold += hillyCompensation;
     135            }
     136
     137            for (int i = 0; i < w.getRealNodesCount(); i++) {
     138                final Node n = w.getNode(i);
     139
     140                /// handle power station line connections (eg. power=line + line=*)
     141                if (isConnectedToStationLine(n, w)) {
     142                    prevNode = n;
     143                    continue;   // skip, it would be false positive
    100144                }
     145
     146                /// handle missing power line support tags (e.g. tower)
     147                if (!isPowerTower(n) && !isPowerInfrastructure(n) && IN_DOWNLOADED_AREA.test(n)
     148                        && (!w.isFirstLastNode(n) || !isPowerStation(n)))
     149                    missingTags.add(n);
     150
     151                /// handle missing nodes
     152                double segmentLen = n.getCoor().greatCircleDistance(prevNode.getCoor());
     153                final Pair<Node, Node> pair = Pair.create(prevNode, n);
     154                final Set<Way> crossingWaterWays = new HashSet<>(8);
     155                final Set<Node> crossingPositions = new HashSet<>(8);
     156                findCrossings(datasetWaterways, w, pair, crossingWaterWays, crossingPositions);
     157
     158                if (!crossingWaterWays.isEmpty()) {
     159                    double compensation = calculateIntersectingLen(prevNode, crossingPositions);
     160                    segmentLen -= compensation;
     161                }
     162
     163                if (segmentCount > 4
     164                        && segmentLen > mean * baseThreshold
     165                        && !isPowerInfrastructure(n)
     166                        && IN_DOWNLOADED_AREA.test(n))
     167                    missingNodes.add(WaySegment.forNodePair(w, prevNode, n));
     168
     169                /// handle wrong line types
     170                if (!crossingWaterWays.isEmpty())
     171                    isCrossingWater = true;
     172
     173                if (n.hasTag("power", "pole"))
     174                    poleCount++;
     175                else if (n.hasTag("power", "tower", "portal"))
     176                    towerCount++;
     177
     178                prevNode = n;
    101179            }
     180
     181            /// handle ref=* numbering discontinuities
     182            if (detectDiscontinuity(w, refDiscontinuities, segmentRefDiscontinuities))
     183                refDiscontinuities.add(w);
     184
     185            /// handle wrong line types
     186            if (((poleCount > towerCount && w.hasTag("power", "line"))
     187                    || (poleCount < towerCount && w.hasTag("power", "minor_line")
     188                    && !isCrossingWater
     189                    && !isContinuesAsMinorLine))
     190                    && IN_DOWNLOADED_AREA.test(w))
     191                wrongLineType.add(w);
     192
     193        } else if (w.isClosed() && isPowerStation(w)) {
     194            powerStations.add(w);
    102195        }
    103         return false;
    104196    }
    105197
    106198    @Override
     
    113205    @Override
    114206    public void startTest(ProgressMonitor progressMonitor) {
    115207        super.startTest(progressMonitor);
    116         clearCollections();
     208        // the test run can take a bit of time, show detailed progress
     209        setShowElements(true);
     210
     211        hillyCompensation = Config.getPref().getDouble(PREFIX + ".hilly_compensation", 0.2);
     212        hillyThreshold = Config.getPref().getDouble(PREFIX + ".hilly_threshold", 4.0);
     213
     214        // collect all waterways
     215        getLayerManager()
     216                .getActiveDataSet()
     217                .getWays()
     218                .parallelStream()
     219                .filter(way -> way.isUsable() && (concernsWaterArea(way)
     220                        || way.referrers(Relation.class).anyMatch(PowerLines::concernsWaterArea)))
     221                .forEach(datasetWaterways::add);
    117222    }
    118223
    119224    @Override
    120225    public void endTest() {
    121         for (Node n : missingTowerOrPole) {
     226        for (Node n : missingTags) {
    122227            if (!isInPowerStation(n)) {
    123                 errors.add(TestError.builder(this, Severity.WARNING, POWER_LINES)
    124                         .message(tr("Missing power tower/pole within power line"))
     228                errors.add(TestError.builder(this, Severity.WARNING, POWER_SUPPORT)
     229                        // the "missing tag" grouping can become broken if the MapCSS message get reworded
     230                        .message(tr("missing tag"), tr("node without power=*"))
    125231                        .primitives(n)
    126232                        .build());
    127233            }
     
    130236        for (Node n : badConnections) {
    131237            errors.add(TestError.builder(this, Severity.WARNING, POWER_CONNECTION)
    132238                    .message(tr("Node connects a power line or cable with an object "
    133                             + "which is not related to the power infrastructure."))
    134                     .primitives(n).build());
     239                            + "which is not related to the power infrastructure"))
     240                    .primitives(n)
     241                    .build());
    135242        }
    136         clearCollections();
     243
     244        for (WaySegment s : missingNodes) {
     245            errors.add(TestError.builder(this, Severity.WARNING, POWER_SEGMENT_LENGTH)
     246                    .message(tr("Possibly missing line support node within power line"))
     247                    .primitives(s.getFirstNode(), s.getSecondNode())
     248                    .highlightWaySegments(new HashSet<>(Collections.singleton(s)))
     249                    .build());
     250        }
     251
     252        for (OsmPrimitive p : refDiscontinuities) {
     253            if (p instanceof Way)
     254                errors.add(TestError.builder(this, Severity.WARNING, POWER_WAY_REF_CONTINUITY)
     255                        .message(tr("Mixed reference numbering"))
     256                        .primitives(p)
     257                        .build());
     258        }
     259
     260        final String discontinuityMsg = tr("Reference numbering don''t match majority of way''s nodes");
     261
     262        for (OsmPrimitive p : refDiscontinuities) {
     263            if (p instanceof Node)
     264                errors.add(TestError.builder(this, Severity.WARNING, POWER_LOCAL_REF_CONTINUITY)
     265                        .message(discontinuityMsg)
     266                        .primitives(p)
     267                        .build());
     268        }
     269
     270        for (Set<Node> nodes : segmentRefDiscontinuities) {
     271            errors.add(TestError.builder(this, Severity.WARNING, POWER_LOCAL_REF_CONTINUITY)
     272                    .message(discontinuityMsg)
     273                    .primitives(nodes)
     274                    .build());
     275        }
     276
     277        for (Way w : wrongLineType) {
     278            errors.add(TestError.builder(this, Severity.WARNING, POWER_LINE_TYPE)
     279                    .message(tr("Possibly wrong power line type used"))
     280                    .primitives(w)
     281                    .build());
     282        }
     283
    137284        super.endTest();
    138285    }
    139286
     287    /**
     288     * The summarized length (in metres) of a way where a power line hangs over a water area.
     289     * @param ref Reference point
     290     * @param crossingNodes Crossing nodes, unordered
     291     * @return The summarized length (in metres) of a way where a power line hangs over a water area
     292     */
     293    private static double calculateIntersectingLen(Node ref, Set<Node> crossingNodes) {
     294        double min = Double.POSITIVE_INFINITY;
     295        double max = Double.NEGATIVE_INFINITY;
     296
     297        for (Node n : crossingNodes) {
     298            double dist = ref.getCoor().greatCircleDistance(n.getCoor());
     299
     300            if (dist < min)
     301                min = dist;
     302            if (dist > max)
     303                max = dist;
     304        }
     305        return max - min;
     306    }
     307
     308    /**
     309     * Searches for way intersections, which intersect the {@code pair} attribute.
     310     * @param ways collection of ways to search
     311     * @param parent parent way for {@code pair} param
     312     * @param pair node pair among which search for another way
     313     * @param crossingWays found crossing ways
     314     * @param crossingPositions collection of the crossing positions
     315     * @implNote Inspired by {@code utilsplugin2/selection/NodeWayUtils.java#addWaysIntersectingWay()}
     316     */
     317    private static void findCrossings(Collection<Way> ways, Way parent, Pair<Node, Node> pair, Set<Way> crossingWays,
     318                                      Set<Node> crossingPositions) {
     319        for (Way way : ways) {
     320            if (way.isUsable()
     321                    && !crossingWays.contains(way)
     322                    && way.getBBox().intersects(parent.getBBox())) {
     323                for (Pair<Node, Node> pair2 : way.getNodePairs(false)) {
     324                    EastNorth eastNorth = Geometry.getSegmentSegmentIntersection(
     325                            pair.a.getEastNorth(), pair.b.getEastNorth(),
     326                            pair2.a.getEastNorth(), pair2.b.getEastNorth());
     327                    if (eastNorth != null) {
     328                        crossingPositions.add(new Node(eastNorth));
     329                        crossingWays.add(way);
     330                    }
     331                }
     332            }
     333        }
     334    }
     335
     336    /** Helper class for reference numbering test. Used for storing continuous reference segment info. */
     337    private static class SegmentInfo {
     338        /** Node index, follows way direction */
     339        private final int startIndex;
     340        /** ref=* value at {@link SegmentInfo#startIndex} */
     341        private final int startRef;
     342        /** Segment length */
     343        private final int length;
     344        /** Segment direction */
     345        private final NumberingDirection direction;
     346
     347        SegmentInfo(int startIndex, int length, int ref, NumberingDirection direction) {
     348            this.startIndex = startIndex;
     349            this.length = length;
     350            this.direction = direction;
     351
     352            if (direction == NumberingDirection.SAME)
     353                this.startRef = ref - length;
     354            else
     355                this.startRef = ref + length;
     356
     357            if (length == 0 && direction != NumberingDirection.NONE) {
     358                throw new IllegalArgumentException("When segment length is zero, the direction should be NONE");
     359            }
     360        }
     361
     362        @Override
     363        public String toString() {
     364            return String.format("SegmentInfo{startIndex=%d, startRef=%d, length=%d, direction=%s}",
     365                    startIndex, startRef, length, direction);
     366        }
     367    }
     368
     369    /**
     370     * Detects ref=* numbering discontinuities in the given way.
     371     * @param way checked way
     372     * @param nRefDiscontinuities single node ref=* discontinuities
     373     * @param sRefDiscontinuities continuous node ref=* discontinuities
     374     * @return {@code true} if warning needs to be issued for the whole way
     375     */
     376    static boolean detectDiscontinuity(Way way, Set<OsmPrimitive> nRefDiscontinuities, List<Set<Node>> sRefDiscontinuities) {
     377        final RefChecker checker = new RefChecker(way);
     378        final ArrayList<SegmentInfo> segments = checker.getSegments();
     379        final SegmentInfo referenceSegment = checker.getLongestSegment();
     380
     381        if (referenceSegment == null)
     382            return !segments.isEmpty();
     383
     384        // collect disconnected ref segments which are not align up to the reference
     385        for (SegmentInfo segment : segments) {
     386            if (!isSegmentAlign(referenceSegment, segment)) {
     387                if (referenceSegment.length == 0)
     388                    return true;
     389
     390                if (segment.length == 0) {
     391                    nRefDiscontinuities.add(way.getNode(segment.startIndex));
     392                } else {
     393                    Set<Node> nodeGroup = new HashSet<>();
     394
     395                    for (int i = segment.startIndex; i <= segment.startIndex + segment.length; i++) {
     396                        nodeGroup.add(way.getNode(i));
     397                    }
     398                    sRefDiscontinuities.add(nodeGroup);
     399                }
     400            }
     401        }
     402
     403        return false;
     404    }
     405
     406    /**
     407     * Checks if parameter segments align. The {@code reference} is expected to be at least as long as the {@code candidate}.
     408     * @param reference Reference segment to check against
     409     * @param candidate Candidate segment
     410     * @return {@code true} if the two segments ref=* numbering align
     411     */
     412    private static boolean isSegmentAlign(SegmentInfo reference, SegmentInfo candidate) {
     413        if (reference.direction == NumberingDirection.NONE
     414                || reference.direction == candidate.direction
     415                || candidate.direction == NumberingDirection.NONE)
     416            return Math.abs(candidate.startIndex - reference.startIndex) == Math.abs(candidate.startRef - reference.startRef);
     417        return false;
     418    }
     419
     420    /**
     421     * Detects continuous reference numbering sequences. Ignores the first and last node because
     422     * ways can be connected, and the connection nodes can have different numbering.
     423     * <p>
     424     * If the numbering switches in the middle of the way, this can also be seen as error,
     425     * because line relations would require split ways.
     426     */
     427    static class RefChecker {
     428        private final ArrayList<SegmentInfo> segments = new ArrayList<>();
     429        private NumberingDirection direction = NumberingDirection.NONE;
     430        private Integer startIndex;
     431        private Integer previousRef;
     432
     433        RefChecker(final Way way) {
     434            run(way);
     435        }
     436
     437        private void run(Way way) {
     438            final int wayLength = way.getNodesCount();
     439
     440            // first and last node skipped
     441            for (int i = 1; i < wayLength - 1; i++) {
     442                Node n = way.getNode(i);
     443                maintain(parseRef(n.get("ref")), i);
     444            }
     445
     446            // needed for creation of the last segment
     447            maintain(null, wayLength - 1);
     448        }
     449
     450        /**
     451         * Maintains class variables and constructs a new segment when necessary.
     452         * @param ref   recognised ref=* number
     453         * @param index {@link Node} index in a way
     454         */
     455        private void maintain(Integer ref, int index) {
     456            if (previousRef == null && ref != null) {
     457                // ref change: null -> number
     458                startIndex = index;
     459            } else if (previousRef != null && ref == null) {
     460                // ref change: number -> null
     461                segments.add(new SegmentInfo(startIndex, index - 1 - startIndex, previousRef, direction));
     462                direction = NumberingDirection.NONE;    // to fix directionality
     463            } else if (ref != null && previousRef != null) {
     464                // ref change: number -> number
     465                if (Math.abs(ref - previousRef) != 1) {
     466                    segments.add(new SegmentInfo(startIndex, index - 1 - startIndex, previousRef, direction));
     467                    startIndex = index;
     468                    previousRef = ref;                  // to fix directionality
     469                }
     470                direction = detectDirection(ref, previousRef);
     471            }
     472            previousRef = ref;
     473        }
     474
     475        /**
     476         * Parses integer tag values. Later can be relatively easily extended or rewritten to handle
     477         * complex references like 25/A, 25/B etc.
     478         * @param value the value to be parsed
     479         * @return parsed int or {@code null} in case of {@link NumberFormatException}
     480         */
     481        private static Integer parseRef(String value) {
     482            try {
     483                return Integer.parseInt(value);
     484            } catch (NumberFormatException ignore) {
     485                Logging.trace("The PowerLines.RefChecker couldn't parse ref=" + value + ", consider rewriting the parser");
     486                return null;
     487            }
     488        }
     489
     490        /**
     491         * Detects numbering direction. The parameters should follow way direction.
     492         * @param ref         last known reference value
     493         * @param previousRef reference value before {@code ref}
     494         * @return recognised direction
     495         */
     496        private static NumberingDirection detectDirection(int ref, int previousRef) {
     497            if (ref > previousRef)
     498                return NumberingDirection.SAME;
     499            else if (ref < previousRef)
     500                return NumberingDirection.OPPOSITE;
     501            return NumberingDirection.NONE;
     502        }
     503
     504        /**
     505         * Calculates the longest segment.
     506         * @return the longest segment, or the lowest index if there are more than one with same length and direction,
     507         * or {@code null} if there are more than one with same length and different direction
     508         */
     509        SegmentInfo getLongestSegment() {
     510            final Set<NumberingDirection> directions = EnumSet.noneOf(NumberingDirection.class);
     511            int longestLength = -1;
     512            int counter = 0;
     513            SegmentInfo longest = null;
     514
     515            for (SegmentInfo segment : segments) {
     516                if (segment.length > longestLength) {
     517                    longestLength = segment.length;
     518                    longest = segment;
     519                    counter = 0;
     520                    directions.clear();
     521                    directions.add(segment.direction);
     522                } else if (segment.length == longestLength) {
     523                    counter++;
     524                    directions.add(segment.direction);
     525                }
     526            }
     527
     528            // there are multiple segments with the same longest length and their directions don't match
     529            if (counter > 0 && directions.size() > 1)
     530                return null;
     531
     532            return longest;
     533        }
     534
     535        /**
     536         * @return the detected segments
     537         */
     538        ArrayList<SegmentInfo> getSegments() {
     539            return segments;
     540        }
     541    }
     542
     543    private static boolean isRelatedToPower(Way way) {
     544        if (way.hasTag("power") || way.hasTag("building"))
     545            return true;
     546        for (OsmPrimitive ref : way.getReferrers()) {
     547            if (ref instanceof Relation && ref.isMultipolygon() && (ref.hasTag("power") || ref.hasTag("building"))) {
     548                for (RelationMember rm : ((Relation) ref).getMembers()) {
     549                    if (way == rm.getMember())
     550                        return true;
     551                }
     552            }
     553        }
     554        return false;
     555    }
     556
     557    /**
     558     * Determines if the current node connected to a line which used usually used inside power stations.
     559     * @param n node to check
     560     * @param w parent way of {@code n}
     561     * @return {@code true} if {@code n} connected to power=line + line=*
     562     */
     563    private static boolean isConnectedToStationLine(Node n, Way w) {
     564        for (OsmPrimitive p : n.getReferrers()) {
     565            if (p instanceof Way && !p.equals(w) && isPowerLine((Way) p) && p.hasKey("line"))
     566                return true;
     567        }
     568        return false;
     569    }
     570
     571    /**
     572     * Checks if the way continues as a power=minor_line.
     573     * @param way Way to be checked
     574     * @return {@code true} if the way continues as a power=minor_line
     575     */
     576    private static boolean isContinuesAsMinorLine(Way way) {
     577        return way.firstNode().referrers(Way.class).filter(referrer -> !way.equals(referrer)).anyMatch(PowerLines::isMinorLine) ||
     578                way.lastNode().referrers(Way.class).filter(referrer -> !way.equals(referrer)).anyMatch(PowerLines::isMinorLine);
     579    }
     580
     581    /**
     582     * Checks if the given primitive denotes a power=minor_line.
     583     * @param p primitive to be checked
     584     * @return {@code true} if the given primitive denotes a power=minor_line
     585     */
     586    private static boolean isMinorLine(OsmPrimitive p) {
     587        return p.hasTag("power", "minor_line");
     588    }
     589
     590    /**
     591     * Check if primitive has a tag that marks it as a water area or boundary of a water area.
     592     * @param p the primitive
     593     * @return {@code true} if primitive has a tag that marks it as a water area or boundary of a water area
     594     */
     595    private static boolean concernsWaterArea(OsmPrimitive p) {
     596        return p.hasTag("water", "river", "lake") || p.hasKey("waterway") || p.hasTag("natural", "coastline");
     597    }
     598
     599    /**
     600     * Checks if the given node is inside a power station.
     601     * @param n Node to be checked
     602     * @return true if the given node is inside a power station
     603     */
    140604    protected final boolean isInPowerStation(Node n) {
    141605        for (OsmPrimitive station : powerStations) {
    142606            List<List<Node>> nodesLists = new ArrayList<>();
     
    171635    /**
    172636     * Determines if the specified primitive denotes a power station.
    173637     * @param p The primitive to be tested
    174      * @return {@code true} if power key is set and equal to station/sub_station/plant
     638     * @return {@code true} if power key is set and equal to generator/substation/plant
    175639     */
    176640    protected static final boolean isPowerStation(OsmPrimitive p) {
    177641        return isPowerIn(p, POWER_STATION_TAGS) || isBuildingIn(p, BUILDING_STATION_TAGS);
     
    178642    }
    179643
    180644    /**
    181      * Determines if the specified node denotes a power tower/pole.
     645     * Determines if the specified node denotes a power support feature.
    182646     * @param n The node to be tested
    183      * @return {@code true} if power key is set and equal to tower/pole
     647     * @return {@code true} if power key is set and equal to pole/tower/portal/catenary_mast
    184648     */
    185649    protected static final boolean isPowerTower(Node n) {
    186650        return isPowerIn(n, POWER_TOWER_TAGS);
     
    189653    /**
    190654     * Determines if the specified node denotes a power infrastructure allowed on a power line.
    191655     * @param n The node to be tested
    192      * @return True if power key is set and equal to switch/tranformer/busbar/generator
     656     * @return {@code true} if power key is set and equal to compensator/converter/generator/insulator
     657     * /switch/switchgear/terminal/transformer
    193658     */
    194     protected static final boolean isPowerAllowed(Node n) {
    195         return isPowerIn(n, POWER_ALLOWED_TAGS);
     659    protected static final boolean isPowerInfrastructure(Node n) {
     660        return isPowerIn(n, POWER_INFRASTRUCTURE_TAGS);
    196661    }
    197662
    198663    /**
     
    215680        return p.hasTag("building", values);
    216681    }
    217682
    218     private void clearCollections() {
     683    @Override
     684    public void clear() {
     685        super.clear();
    219686        powerStations.clear();
    220687        badConnections.clear();
    221         missingTowerOrPole.clear();
     688        missingTags.clear();
     689        missingNodes.clear();
     690        wrongLineType.clear();
     691        refDiscontinuities.clear();
     692        segmentRefDiscontinuities.clear();
     693        datasetWaterways.clear();
    222694    }
    223695}
  • src/org/openstreetmap/josm/tools/Utils.java

     
    13041304    }
    13051305
    13061306    /**
     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     * @since xxx
     1312     */
     1313    public static double getStandardDeviation(double[] values) {
     1314        return getStandardDeviation(values, Double.NaN);
     1315    }
     1316
     1317    /**
     1318     * Calculates the <a href="https://en.wikipedia.org/wiki/Standard_deviation">standard deviation</a> 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
     1323     * @see #getStandardDeviation(double[])
     1324     * @since xxx
     1325     */
     1326    public static double getStandardDeviation(double[] values, double mean) {
     1327        double standardDeviation = 0;
     1328
     1329        if (Double.isNaN(mean)) {
     1330            mean = Arrays.stream(values).average().orElse(0);
     1331        }
     1332
     1333        for (double length : values) {
     1334            standardDeviation += Math.pow(length - mean, 2);
     1335        }
     1336
     1337        return Math.sqrt(standardDeviation / values.length);
     1338    }
     1339
     1340    /**
    13071341     * A ForkJoinWorkerThread that will always inherit caller permissions,
    13081342     * unlike JDK's InnocuousForkJoinWorkerThread, used if a security manager exists.
    13091343     */