Ticket #20716: josm_20716_power_v8.patch

File josm_20716_power_v8.patch, 31.3 KB (added by gaben, 3 years ago)
  • src/org/openstreetmap/josm/data/osm/Node.java

    IDEA additional info:
    Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
    <+>UTF-8
    diff --git a/src/org/openstreetmap/josm/data/osm/Node.java b/src/org/openstreetmap/josm/data/osm/Node.java
    a b  
    232232     * Merges the technical and semantical attributes from <code>other</code> onto this.
    233233     *
    234234     * Both this and other must be new, or both must be assigned an OSM ID. If both this and <code>other</code>
    235      * have an assigend OSM id, the IDs have to be the same.
     235     * have an assigned OSM id, the IDs have to be the same.
    236236     *
    237237     * @param other the other primitive. Must not be null.
    238238     * @throws IllegalArgumentException if other is null.
  • src/org/openstreetmap/josm/data/osm/TagMap.java

    IDEA additional info:
    Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
    <+>UTF-8
    diff --git a/src/org/openstreetmap/josm/data/osm/TagMap.java b/src/org/openstreetmap/josm/data/osm/TagMap.java
    a b  
    4040         */
    4141        private final String[] tags;
    4242        /**
    43          * Current tag index. Always a multiple of 2.
     43         * Current tag index. Always multiple of 2.
    4444         */
    4545        private int currentIndex;
    4646
  • 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     * Calculates 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.CachingProperty;
     26import org.openstreetmap.josm.data.preferences.DoubleProperty;
     27import org.openstreetmap.josm.data.preferences.sources.ValidatorPrefHelper;
    2128import org.openstreetmap.josm.data.validation.Severity;
    2229import org.openstreetmap.josm.data.validation.Test;
    2330import org.openstreetmap.josm.data.validation.TestError;
    2431import org.openstreetmap.josm.gui.progress.ProgressMonitor;
    2532import org.openstreetmap.josm.tools.Geometry;
     33import org.openstreetmap.josm.tools.Logging;
     34import org.openstreetmap.josm.tools.Pair;
    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 CachingProperty<Double> HILLY_COMPENSATION = new DoubleProperty(
     57            ValidatorPrefHelper.PREFIX + ".powerlines.hilly_compensation",
     58            0.2).cached();
     59    protected static final CachingProperty<Double> HILLY_THRESHOLD = new DoubleProperty(
     60            ValidatorPrefHelper.PREFIX + ".powerlines.hilly_threshold",
     61            4.0).cached();
    3662
    3763    /** Values for {@code power} key interpreted as power lines */
    3864    static final Collection<String> POWER_LINE_TAGS = Arrays.asList("line", "minor_line");
    3965    /** Values for {@code power} key interpreted as power towers */
    40     static final Collection<String> POWER_TOWER_TAGS = Arrays.asList("tower", "pole");
     66    static final Collection<String> POWER_TOWER_TAGS = Arrays.asList("catenary_mast", "pole", "portal", "tower");
    4167    /** 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");
     68    static final Collection<String> POWER_STATION_TAGS = Arrays.asList("generator", "plant", "substation");
    4369    /** Values for {@code building} key interpreted as power stations */
    4470    static final Collection<String> BUILDING_STATION_TAGS = Arrays.asList("transformer_tower");
    4571    /** 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");
     72    static final Collection<String> POWER_INFRASTRUCTURE_TAGS = Arrays.asList("compensator", "converter",
     73            "generator", "insulator", "switch", "switchgear", "terminal", "transformer");
    4874
    49     private final Set<Node> badConnections = new LinkedHashSet<>();
    50     private final Set<Node> missingTowerOrPole = new LinkedHashSet<>();
     75    private final Set<Node> badConnections = new HashSet<>();
     76    private final Set<Node> missingTags = new HashSet<>();
     77    private final Set<Way> wrongLineType = new HashSet<>();
     78    private final Set<OsmPrimitive> refDiscontinuities = new HashSet<>();
     79    private final List<Set<Node>> segmentRefDiscontinuities = new ArrayList<>();
     80    private final Set<WaySegment> missingNodes = new HashSet<>();
    5181
    5282    private final List<OsmPrimitive> powerStations = new ArrayList<>();
    5383
     84    private final Collection<Way> datasetWaterways = new HashSet<>(64);
     85
    5486    /**
    5587     * Constructs a new {@code PowerLines} test.
    5688     */
    5789    public PowerLines() {
    58         super(tr("Power lines"), tr("Checks for nodes in power lines that do not have a power=tower/pole 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         }
     90        super(tr("Power lines"), tr("Checks if power line missing a support node and " +
     91                "for nodes in power lines that do not have a power=tower/pole tag"));
     92    }
     93
     94    /**
     95     * Power line support features ref=* numbering direction.
     96     */
     97    private enum NumberingDirection {
     98        /** No direction */
     99        NONE,
     100        /** Numbering follows way direction */
     101        SAME,
     102        /** Numbering goes opposite way direction */
     103        OPPOSITE
    75104    }
    76105
    77106    @Override
     
    89118            badConnections.add(n);
    90119    }
    91120
    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;
     121    @Override
     122    public void visit(Way w) {
     123        if (!isPrimitiveUsable(w)) return;
     124
     125        if (isPowerLine(w) && !w.hasKey("line") && !w.hasTag("location", "underground")) {
     126            final int segmentCount = w.getNodesCount() - 1;
     127            final double mean = w.getLength() / segmentCount;
     128            final double stdDev = calculateStdDev(w.getSegmentLengths(), mean);
     129            int poleCount = 0;
     130            int towerCount = 0;
     131            Node prevNode = w.firstNode();
     132
     133            double baseThreshold = w.hasTag("power", "line") ? 1.6 : 1.8;
     134            if (mean / stdDev < HILLY_THRESHOLD.get()) {
     135                //compensate for possibly hilly areas where towers can't be put anywhere
     136                baseThreshold += HILLY_COMPENSATION.get();
     137            }
     138
     139            for (int i = 1; i < w.getRealNodesCount(); i++) {
     140                final Node n = w.getNode(i);
     141
     142                /// handle power station line connections (eg. power=line + line=*)
     143                if (isConnectedToStationLine(n, w)) {
     144                    prevNode = n;
     145                    continue;   // skip, it would be false positive
    100146                }
     147
     148                /// handle missing power line support tags (e.g. tower)
     149                if (!isPowerTower(n) && !isPowerInfrastructure(n) && IN_DOWNLOADED_AREA.test(n)
     150                        && (!w.isFirstLastNode(n) || !isPowerStation(n)))
     151                    missingTags.add(n);
     152
     153                /// handle missing nodes
     154                double segmentLen = n.getCoor().greatCircleDistance(prevNode.getCoor());
     155                final Pair<Node, Node> pair = Pair.create(prevNode, n);
     156                final Set<Way> crossingWaterWays = new HashSet<>(8);
     157                final Set<Node> crossingPositions = new HashSet<>();
     158                findCrossings(datasetWaterways, w, pair, crossingWaterWays, crossingPositions);
     159
     160                if (!crossingWaterWays.isEmpty()) {
     161                    double compensation = calculateIntersectingLen(prevNode, crossingPositions);
     162                    segmentLen -= compensation;
     163                }
     164
     165                if (segmentCount > 4
     166                        && segmentLen > mean * baseThreshold
     167                        && !isPowerInfrastructure(n)
     168                        && IN_DOWNLOADED_AREA.test(n))
     169                    missingNodes.add(WaySegment.forNodePair(w, prevNode, n));
     170
     171                /// handle wrong line types
     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                wrongLineType.add(w);
     188
     189        } else if (w.isClosed() && isPowerStation(w)) {
     190            powerStations.add(w);
    102191        }
    103         return false;
    104192    }
    105193
    106194    @Override
     
    113201    @Override
    114202    public void startTest(ProgressMonitor progressMonitor) {
    115203        super.startTest(progressMonitor);
    116         clearCollections();
     204        // the test run can take a bit of time, show detailed progress
     205        setShowElements(true);
     206
     207        // collect all waterways
     208        getLayerManager()
     209                .getActiveDataSet()
     210                .getWays()
     211                .parallelStream()
     212                .filter(way ->
     213                        way.hasTag("water", "river", "lake") ||
     214                                way.hasKey("waterway") ||
     215                                way.hasTag("natural", "coastline") ||
     216                                way.referrers(Relation.class)
     217                                        .anyMatch(relation ->
     218                                                relation.hasTag("water", "river", "lake") ||
     219                                                        relation.hasKey("waterway") ||
     220                                                        relation.hasTag("natural", "coastline")
     221                                        ))
     222                .forEach(datasetWaterways::add);
    117223    }
    118224
    119225    @Override
    120226    public void endTest() {
    121         for (Node n : missingTowerOrPole) {
     227        for (Node n : missingTags) {
    122228            if (!isInPowerStation(n)) {
    123                 errors.add(TestError.builder(this, Severity.WARNING, POWER_LINES)
    124                         .message(tr("Missing power tower/pole within power line"))
     229                errors.add(TestError.builder(this, Severity.WARNING, POWER_SUPPORT)
     230                        // the "missing tag" grouping can become broken if the MapCSS message get reworded
     231                        .message(tr("missing tag"), tr("node without power=*"))
    125232                        .primitives(n)
    126233                        .build());
    127234            }
     
    130237        for (Node n : badConnections) {
    131238            errors.add(TestError.builder(this, Severity.WARNING, POWER_CONNECTION)
    132239                    .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());
     240                            + "which is not related to the power infrastructure"))
     241                    .primitives(n)
     242                    .build());
     243        }
     244
     245        for (WaySegment s : missingNodes) {
     246            errors.add(TestError.builder(this, Severity.WARNING, POWER_SEGMENT_LENGTH)
     247                    .message(tr("Possibly missing line support node within power line"))
     248                    .primitives(s.getFirstNode(), s.getSecondNode())
     249                    .highlightWaySegments(new HashSet<>(Collections.singleton(s)))
     250                    .build());
    135251        }
    136         clearCollections();
     252
     253        for (OsmPrimitive p : refDiscontinuities) {
     254            if (p instanceof Way)
     255                errors.add(TestError.builder(this, Severity.WARNING, POWER_WAY_REF_CONTINUITY)
     256                        .message(tr("Mixed reference numbering"))
     257                        .primitives(p)
     258                        .build());
     259        }
     260
     261        final String discontinuityMsg = tr("Reference numbering don''t match majority of way''s nodes");
     262
     263        for (OsmPrimitive p : refDiscontinuities) {
     264            if (p instanceof Node)
     265                errors.add(TestError.builder(this, Severity.WARNING, POWER_LOCAL_REF_CONTINUITY)
     266                        .message(discontinuityMsg)
     267                        .primitives(p)
     268                        .build());
     269        }
     270
     271        for (Set<Node> nodes : segmentRefDiscontinuities) {
     272            errors.add(TestError.builder(this, Severity.WARNING, POWER_LOCAL_REF_CONTINUITY)
     273                    .message(discontinuityMsg)
     274                    .primitives(nodes)
     275                    .build());
     276        }
     277
     278        for (Way w : wrongLineType) {
     279            errors.add(TestError.builder(this, Severity.WARNING, POWER_LINE_TYPE)
     280                    .message(tr("Possibly wrong power line type used"))
     281                    .primitives(w)
     282                    .build());
     283        }
     284
    137285        super.endTest();
    138286    }
    139287
     288    /**
     289     * Calculates the standard deviation from the given values.
     290     * @param values An array of values
     291     * @param mean Precalculated average value of the array
     292     * @return standard deviation of the given array
     293     */
     294    protected static double calculateStdDev(double[] values, double mean) {
     295        double standardDeviation = 0;
     296
     297        for (double length : values) {
     298            standardDeviation += Math.pow(length - mean, 2);
     299        }
     300
     301        return Math.sqrt(standardDeviation / values.length);
     302    }
     303
     304    /**
     305     * The summarized length (in metres) of a way where a power line hangs over a water area.
     306     * @param ref Reference point
     307     * @param crossingNodes Crossing nodes, unordered
     308     * @return The summarized length (in metres) of a way where a power line hangs over a water area
     309     */
     310    private static double calculateIntersectingLen(Node ref, Set<Node> crossingNodes) {
     311        double min = Double.POSITIVE_INFINITY;
     312        double max = Double.NEGATIVE_INFINITY;
     313
     314        for (Node n : crossingNodes) {
     315            double dist = ref.getCoor().greatCircleDistance(n.getCoor());
     316
     317            if (dist < min)
     318                min = dist;
     319            if (dist > max)
     320                max = dist;
     321        }
     322        return max - min;
     323    }
     324
     325    /**
     326     * Searches for way intersections, which intersect the {@code pair} attribute.
     327     * @param ways collection of ways to search
     328     * @param parent parent way for {@code pair} param
     329     * @param pair {@link Node} pair among which search for another way
     330     * @param crossingWays found crossing ways
     331     * @param crossingPositions collection of the crossing positions
     332     * @implNote Inspired by {@code utilsplugin2/selection/NodeWayUtils.java#addWaysIntersectingWay()}
     333     */
     334    private static void findCrossings(Collection<Way> ways, Way parent, Pair<Node, Node> pair, Set<Way> crossingWays,
     335                                      Set<Node> crossingPositions) {
     336        for (Way way : ways) {
     337            if (way.isUsable()
     338                    && !crossingWays.contains(way)
     339                    && way.getBBox().intersects(parent.getBBox())) {
     340                for (Pair<Node, Node> pair2 : way.getNodePairs(false)) {
     341                    EastNorth eastNorth = Geometry.getSegmentSegmentIntersection(
     342                            pair.a.getEastNorth(), pair.b.getEastNorth(),
     343                            pair2.a.getEastNorth(), pair2.b.getEastNorth());
     344                    if (eastNorth != null) {
     345                        crossingPositions.add(new Node(eastNorth));
     346                        crossingWays.add(way);
     347                    }
     348                }
     349            }
     350        }
     351    }
     352
     353    /**
     354     * Helper class for reference numbering test. Used for storing continuous reference segment info.
     355     */
     356    private static class SegmentInfo {
     357        /** Follows ways direction */
     358        private final int firstNodeIndex;
     359        private final int length;
     360        private final int startingRef;
     361        private final NumberingDirection direction;
     362
     363        SegmentInfo(int firstNodeIndex, int length, int ref, NumberingDirection direction) {
     364            this.firstNodeIndex = firstNodeIndex;
     365            this.length = length;
     366            this.direction = direction;
     367
     368            if (direction == NumberingDirection.SAME)
     369                this.startingRef = ref - length;
     370            else
     371                this.startingRef = ref + length;
     372        }
     373
     374        @Override
     375        public String toString() {
     376            return String.format("SegmentInfo{firstNodeIndex=%d, length=%d, startingRef=%d, direction=%s}",
     377                    firstNodeIndex, length, startingRef, direction);
     378        }
     379    }
     380
     381    /**
     382     * Detects ref=* numbering discontinuities in the given way.
     383     * @param way checked way
     384     * @param nRefDiscontinuities single node ref=* discontinuities
     385     * @param sRefDiscontinuities continuous node ref=* discontinuities
     386     * @return {@code true} if warning needs to be issued for the whole way
     387     */
     388    static boolean detectDiscontinuity(Way way, Set<OsmPrimitive> nRefDiscontinuities, List<Set<Node>> sRefDiscontinuities) {
     389        final ArrayList<SegmentInfo> segments = new RefChecker(way).getSegments();
     390        final Set<NumberingDirection> directions = new HashSet<>(4);
     391        SegmentInfo referenceSegment = null;
     392        int longestSegmentLen = 0;
     393        int counter = 0;
     394
     395        // calculate longest segment
     396        for (SegmentInfo segment : segments) {
     397            if (segment.length > longestSegmentLen) {
     398                longestSegmentLen = segment.length;
     399                referenceSegment = segment;
     400                directions.clear();
     401                directions.add(segment.direction);
     402                counter = 0;
     403            } else if (segment.length == longestSegmentLen) {
     404                counter++;
     405                directions.add(segment.direction);
     406            }
     407        }
     408
     409        // there are multiple segments with the same longest length and their directions don't match
     410        if (counter > 0 && directions.size() > 1)
     411            return true;
     412
     413        // if reference is null, but we have segments, select one randomly
     414        if (referenceSegment == null && !segments.isEmpty())
     415            referenceSegment = segments.iterator().next();
     416
     417        // collect disconnected ref segments which are not align up to the reference
     418        for (SegmentInfo segment : segments) {
     419            if (!isSegmentAlign(referenceSegment, segment)) {
     420                if (referenceSegment.length == 0)
     421                    return true;
     422
     423                if (segment.length == 0) {
     424                    nRefDiscontinuities.add(way.getNode(segment.firstNodeIndex));
     425                } else {
     426                    final Set<Node> nodeGroup = new HashSet<>();
     427
     428                    for (int i = segment.firstNodeIndex; i <= segment.firstNodeIndex + segment.length; i++) {
     429                        nodeGroup.add(way.getNode(i));
     430                    }
     431                    sRefDiscontinuities.add(nodeGroup);
     432                }
     433            }
     434        }
     435
     436        return false;
     437    }
     438
     439    /**
     440     * Checks if parameter segments align.
     441     * @param reference Reference segment to check against
     442     * @param candidate Candidate segment
     443     * @return {@code true} if the two segments ref=* numbering align
     444     */
     445    private static boolean isSegmentAlign(SegmentInfo reference, SegmentInfo candidate) {
     446        return candidate.firstNodeIndex - reference.firstNodeIndex == candidate.startingRef - reference.startingRef;
     447    }
     448
     449    /**
     450     * Detects continuous reference numbering sequences. Ignores the first and last node because
     451     * ways can be connected, and the connection nodes can have different numbering.
     452     * <p>
     453     * If the numbering switches in the middle of the way, this can also be seen as error,
     454     * because line relations would require split ways.
     455     */
     456    static class RefChecker {
     457        final ArrayList<SegmentInfo> segments = new ArrayList<>();
     458
     459        NumberingDirection direction = NumberingDirection.NONE;
     460        Integer startIndex = null;
     461        Integer previousRef = null;
     462
     463        RefChecker(final Way way) {
     464            run(way);
     465        }
     466
     467        private void run(Way way) {
     468            final int wayLength = way.getNodesCount();
     469
     470            // first and last node skipped
     471            for (int i = 1; i < wayLength - 1; i++) {
     472                Node n = way.getNode(i);
     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        private 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         * Maintains the class variables a constructs a new segment when necessary.
     491         * @param ref recognised ref=* number
     492         * @param index {@link Node} index in a way
     493         */
     494        private void maintain(Integer ref, int index) {
     495            if (this.startIndex == null) {
     496                startIndex = index;
     497            }
     498
     499            if (ref == null) {
     500                if (previousRef == null) {
     501                    reset();
     502                    return;
     503                }
     504
     505                segments.add(new SegmentInfo(startIndex, index - 1 - startIndex, previousRef, direction));
     506                reset();
     507                return;
     508            }
     509
     510            if (previousRef == null) {
     511                previousRef = ref;
     512                return;
     513            }
     514
     515            if (Math.abs(ref - previousRef) != 1) {
     516                segments.add(new SegmentInfo(startIndex, index - 1 - startIndex, previousRef, direction));
     517                startIndex = index;
     518            }
     519
     520            if (ref > previousRef)
     521                direction = NumberingDirection.SAME;
     522            else if (ref < previousRef)
     523                direction = NumberingDirection.OPPOSITE;
     524
     525            previousRef = ref;
     526        }
     527
     528        private void reset() {
     529            this.direction = NumberingDirection.NONE;
     530            this.startIndex = null;
     531            this.previousRef = null;
     532        }
     533
     534        ArrayList<SegmentInfo> getSegments() {
     535            return segments;
     536        }
     537    }
     538
     539    private static boolean isRelatedToPower(Way way) {
     540        if (way.hasTag("power") || way.hasTag("building"))
     541            return true;
     542        for (OsmPrimitive ref : way.getReferrers()) {
     543            if (ref instanceof Relation && ref.isMultipolygon() && (ref.hasTag("power") || ref.hasTag("building"))) {
     544                for (RelationMember rm : ((Relation) ref).getMembers()) {
     545                    if (way == rm.getMember())
     546                        return true;
     547                }
     548            }
     549        }
     550        return false;
     551    }
     552
     553    /**
     554     * Determines if the current node connected to a line which used usually used inside power stations.
     555     * @param n node to check
     556     * @param w parent way of {@code n}
     557     * @return {@code true} if {@code n} connected to power=line + line=*
     558     */
     559    private static boolean isConnectedToStationLine(Node n, Way w) {
     560        for (OsmPrimitive p : n.getReferrers()) {
     561            if (p instanceof Way && !p.equals(w) && isPowerLine((Way) p) && p.hasKey("line"))
     562                return true;
     563        }
     564        return false;
     565    }
     566
     567    /**
     568     * Checks if the given node is inside a power station.
     569     * @param n Node to be checked
     570     */
    140571    protected final boolean isInPowerStation(Node n) {
    141572        for (OsmPrimitive station : powerStations) {
    142573            List<List<Node>> nodesLists = new ArrayList<>();
     
    171602    /**
    172603     * Determines if the specified primitive denotes a power station.
    173604     * @param p The primitive to be tested
    174      * @return {@code true} if power key is set and equal to station/sub_station/plant
     605     * @return {@code true} if power key is set and equal to generator/substation/plant
    175606     */
    176607    protected static final boolean isPowerStation(OsmPrimitive p) {
    177608        return isPowerIn(p, POWER_STATION_TAGS) || isBuildingIn(p, BUILDING_STATION_TAGS);
    178609    }
    179610
    180611    /**
    181      * Determines if the specified node denotes a power tower/pole.
     612     * Determines if the specified node denotes a power support feature.
    182613     * @param n The node to be tested
    183      * @return {@code true} if power key is set and equal to tower/pole
     614     * @return {@code true} if power key is set and equal to pole/tower/portal/catenary_mast
    184615     */
    185616    protected static final boolean isPowerTower(Node n) {
    186617        return isPowerIn(n, POWER_TOWER_TAGS);
     
    189620    /**
    190621     * Determines if the specified node denotes a power infrastructure allowed on a power line.
    191622     * @param n The node to be tested
    192      * @return True if power key is set and equal to switch/tranformer/busbar/generator
     623     * @return {@code true} if power key is set and equal to compensator/converter/generator/insulator
     624     * /switch/switchgear/terminal/transformer
    193625     */
    194     protected static final boolean isPowerAllowed(Node n) {
    195         return isPowerIn(n, POWER_ALLOWED_TAGS);
     626    protected static final boolean isPowerInfrastructure(Node n) {
     627        return isPowerIn(n, POWER_INFRASTRUCTURE_TAGS);
    196628    }
    197629
    198630    /**
     
    215647        return p.hasTag("building", values);
    216648    }
    217649
    218     private void clearCollections() {
     650    @Override
     651    public void clear() {
     652        super.clear();
    219653        powerStations.clear();
    220654        badConnections.clear();
    221         missingTowerOrPole.clear();
     655        missingTags.clear();
     656        missingNodes.clear();
     657        wrongLineType.clear();
     658        refDiscontinuities.clear();
     659        segmentRefDiscontinuities.clear();
     660        datasetWaterways.clear();
    222661    }
    223662}
  • src/org/openstreetmap/josm/data/validation/Test.java

    IDEA additional info:
    Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
    <+>UTF-8
    diff --git a/src/org/openstreetmap/josm/data/validation/Test.java b/src/org/openstreetmap/josm/data/validation/Test.java
    a b  
    371371        errors.clear();
    372372    }
    373373
     374    /**
     375     * Sets the validator progress bar elements counter visibility.
     376     * @param b True if element counter shown
     377     */
    374378    protected void setShowElements(boolean b) {
    375379        showElementCount = b;
    376380    }