001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.data.validation.tests;
003
004import static org.openstreetmap.josm.tools.I18n.tr;
005
006import java.util.ArrayList;
007import java.util.Arrays;
008import java.util.Collection;
009import java.util.List;
010
011import org.openstreetmap.josm.data.osm.Node;
012import org.openstreetmap.josm.data.osm.OsmPrimitive;
013import org.openstreetmap.josm.data.osm.Relation;
014import org.openstreetmap.josm.data.osm.Way;
015import org.openstreetmap.josm.data.osm.visitor.paint.relations.Multipolygon;
016import org.openstreetmap.josm.data.osm.visitor.paint.relations.Multipolygon.JoinedWay;
017import org.openstreetmap.josm.data.osm.visitor.paint.relations.MultipolygonCache;
018import org.openstreetmap.josm.data.validation.Severity;
019import org.openstreetmap.josm.data.validation.Test;
020import org.openstreetmap.josm.data.validation.TestError;
021import org.openstreetmap.josm.gui.progress.ProgressMonitor;
022import org.openstreetmap.josm.tools.Geometry;
023
024/**
025 * Checks for nodes in power lines/minor_lines that do not have a power=tower/pole tag.<br>
026 * See #7812 for discussions about this test.
027 */
028public class PowerLines extends Test {
029
030    /** Test identifier */
031    protected static final int POWER_LINES = 2501;
032
033    /** Values for {@code power} key interpreted as power lines */
034    static final Collection<String> POWER_LINE_TAGS = Arrays.asList("line", "minor_line");
035    /** Values for {@code power} key interpreted as power towers */
036    static final Collection<String> POWER_TOWER_TAGS = Arrays.asList("tower", "pole");
037    /** Values for {@code power} key interpreted as power stations */
038    static final Collection<String> POWER_STATION_TAGS = Arrays.asList("station", "sub_station", "substation", "plant", "generator");
039    /** Values for {@code building} key interpreted as power stations */
040    static final Collection<String> BUILDING_STATION_TAGS = Arrays.asList("transformer_tower");
041    /** Values for {@code power} key interpreted as allowed power items */
042    static final Collection<String> POWER_ALLOWED_TAGS = Arrays.asList("switch", "transformer", "busbar", "generator", "switchgear",
043            "portal", "terminal", "insulator");
044
045    private final List<TestError> potentialErrors = new ArrayList<>();
046
047    private final List<OsmPrimitive> powerStations = new ArrayList<>();
048
049    /**
050     * Constructs a new {@code PowerLines} test.
051     */
052    public PowerLines() {
053        super(tr("Power lines"), tr("Checks for nodes in power lines that do not have a power=tower/pole tag."));
054    }
055
056    @Override
057    public void visit(Way w) {
058        if (w.isUsable()) {
059            if (isPowerLine(w) && !w.hasTag("location", "underground")) {
060                for (Node n : w.getNodes()) {
061                    if (!isPowerTower(n) && !isPowerAllowed(n) && IN_DOWNLOADED_AREA.test(n)
062                        && (!w.isFirstLastNode(n) || !isPowerStation(n))) {
063                        potentialErrors.add(TestError.builder(this, Severity.WARNING, POWER_LINES)
064                                .message(tr("Missing power tower/pole within power line"))
065                                .primitives(n)
066                                .build());
067                    }
068                }
069            } else if (w.isClosed() && isPowerStation(w)) {
070                powerStations.add(w);
071            }
072        }
073    }
074
075    @Override
076    public void visit(Relation r) {
077        if (r.isMultipolygon() && isPowerStation(r)) {
078            powerStations.add(r);
079        }
080    }
081
082    @Override
083    public void startTest(ProgressMonitor progressMonitor) {
084        super.startTest(progressMonitor);
085        powerStations.clear();
086        potentialErrors.clear();
087    }
088
089    @Override
090    public void endTest() {
091        for (TestError e : potentialErrors) {
092            e.getPrimitives().stream()
093                    .map(Node.class::cast)
094                    .filter(n -> !isInPowerStation(n))
095                    .findAny()
096                    .ifPresent(ignore -> errors.add(e));
097        }
098        potentialErrors.clear();
099        powerStations.clear();
100        super.endTest();
101    }
102
103    protected final boolean isInPowerStation(Node n) {
104        for (OsmPrimitive station : powerStations) {
105            List<List<Node>> nodesLists = new ArrayList<>();
106            if (station instanceof Way) {
107                nodesLists.add(((Way) station).getNodes());
108            } else if (station instanceof Relation) {
109                Multipolygon polygon = MultipolygonCache.getInstance().get((Relation) station);
110                if (polygon != null) {
111                    for (JoinedWay outer : Multipolygon.joinWays(polygon.getOuterWays())) {
112                        nodesLists.add(outer.getNodes());
113                    }
114                }
115            }
116            for (List<Node> nodes : nodesLists) {
117                if (Geometry.nodeInsidePolygon(n, nodes)) {
118                    return true;
119                }
120            }
121        }
122        return false;
123    }
124
125    /**
126     * Determines if the specified way denotes a power line.
127     * @param w The way to be tested
128     * @return {@code true} if power key is set and equal to line/minor_line
129     */
130    protected static final boolean isPowerLine(Way w) {
131        return isPowerIn(w, POWER_LINE_TAGS);
132    }
133
134    /**
135     * Determines if the specified primitive denotes a power station.
136     * @param p The primitive to be tested
137     * @return {@code true} if power key is set and equal to station/sub_station/plant
138     */
139    protected static final boolean isPowerStation(OsmPrimitive p) {
140        return isPowerIn(p, POWER_STATION_TAGS) || isBuildingIn(p, BUILDING_STATION_TAGS);
141    }
142
143    /**
144     * Determines if the specified node denotes a power tower/pole.
145     * @param n The node to be tested
146     * @return {@code true} if power key is set and equal to tower/pole
147     */
148    protected static final boolean isPowerTower(Node n) {
149        return isPowerIn(n, POWER_TOWER_TAGS);
150    }
151
152    /**
153     * Determines if the specified node denotes a power infrastructure allowed on a power line.
154     * @param n The node to be tested
155     * @return True if power key is set and equal to switch/tranformer/busbar/generator
156     */
157    protected static final boolean isPowerAllowed(Node n) {
158        return isPowerIn(n, POWER_ALLOWED_TAGS);
159    }
160
161    /**
162     * Helper function to check if power tag is a certain value.
163     * @param p The primitive to be tested
164     * @param values List of possible values
165     * @return {@code true} if power key is set and equal to possible values
166     */
167    private static boolean isPowerIn(OsmPrimitive p, Collection<String> values) {
168        return p.hasTag("power", values);
169    }
170
171    /**
172     * Helper function to check if building tag is a certain value.
173     * @param p The primitive to be tested
174     * @param values List of possible values
175     * @return {@code true} if power key is set and equal to possible values
176     */
177    private static boolean isBuildingIn(OsmPrimitive p, Collection<String> values) {
178        return p.hasTag("building", values);
179    }
180}