// License: GPL. For details, see LICENSE file. package org.openstreetmap.josm.data.validation.tests; import static org.openstreetmap.josm.tools.I18n.tr; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; import org.openstreetmap.josm.data.osm.Node; import org.openstreetmap.josm.data.osm.OsmPrimitive; import org.openstreetmap.josm.data.osm.Relation; import org.openstreetmap.josm.data.osm.Way; import org.openstreetmap.josm.data.osm.visitor.paint.relations.Multipolygon; import org.openstreetmap.josm.data.osm.visitor.paint.relations.Multipolygon.JoinedWay; import org.openstreetmap.josm.data.osm.visitor.paint.relations.MultipolygonCache; import org.openstreetmap.josm.data.validation.Severity; import org.openstreetmap.josm.data.validation.Test; import org.openstreetmap.josm.data.validation.TestError; import org.openstreetmap.josm.gui.progress.ProgressMonitor; import org.openstreetmap.josm.tools.Geometry; /** * Checks for nodes in power lines/minor_lines that do not have a power=tower/pole tag.<br> * See #7812 for discussions about this test. */ public class PowerLines extends Test { /** Test identifier */ protected static final int POWER_LINES = 2501; /** Values for {@code power} key interpreted as power lines */ static final Collection<String> POWER_LINE_TAGS = Arrays.asList("line", "minor_line"); /** Values for {@code power} key interpreted as power towers */ static final Collection<String> POWER_TOWER_TAGS = Arrays.asList("tower", "pole"); /** Values for {@code power} key interpreted as power stations */ static final Collection<String> POWER_STATION_TAGS = Arrays.asList("station", "sub_station", "substation", "plant", "generator"); /** Values for {@code building} key interpreted as power stations */ static final Collection<String> BUILDING_STATION_TAGS = Arrays.asList("transformer_tower"); /** Values for {@code power} key interpreted as allowed power items */ static final Collection<String> POWER_ALLOWED_TAGS = Arrays.asList("switch", "transformer", "busbar", "generator", "switchgear", "portal", "terminal", "insulator"); private final List<TestError> potentialErrors = new ArrayList<>(); private final List<OsmPrimitive> powerStations = new ArrayList<>(); /** * Constructs a new {@code PowerLines} test. */ public PowerLines() { super(tr("Power lines"), tr("Checks for nodes in power lines that do not have a power=tower/pole tag.")); } @Override public void visit(Way w) { if (w.isUsable()) { if (isPowerLine(w) && !w.hasTag("location", "underground")) { for (Node n : w.getNodes()) { if (!isPowerTower(n) && !isPowerAllowed(n) && IN_DOWNLOADED_AREA.test(n) && (!w.isFirstLastNode(n) || !isPowerStation(n))) { potentialErrors.add(TestError.builder(this, Severity.WARNING, POWER_LINES) .message(tr("Missing power tower/pole within power line")) .primitives(n) .build()); } } } else if (w.isClosed() && isPowerStation(w)) { powerStations.add(w); } } } @Override public void visit(Relation r) { if (r.isMultipolygon() && isPowerStation(r)) { powerStations.add(r); } } @Override public void startTest(ProgressMonitor progressMonitor) { super.startTest(progressMonitor); powerStations.clear(); potentialErrors.clear(); } @Override public void endTest() { for (TestError e : potentialErrors) { e.getPrimitives().stream() .map(Node.class::cast) .filter(n -> !isInPowerStation(n)) .findAny() .ifPresent(ignore -> errors.add(e)); } potentialErrors.clear(); super.endTest(); } protected final boolean isInPowerStation(Node n) { for (OsmPrimitive station : powerStations) { List<List<Node>> nodesLists = new ArrayList<>(); if (station instanceof Way) { nodesLists.add(((Way) station).getNodes()); } else if (station instanceof Relation) { Multipolygon polygon = MultipolygonCache.getInstance().get((Relation) station); if (polygon != null) { for (JoinedWay outer : Multipolygon.joinWays(polygon.getOuterWays())) { nodesLists.add(outer.getNodes()); } } } for (List<Node> nodes : nodesLists) { if (Geometry.nodeInsidePolygon(n, nodes)) { return true; } } } return false; } /** * Determines if the specified way denotes a power line. * @param w The way to be tested * @return {@code true} if power key is set and equal to line/minor_line */ protected static final boolean isPowerLine(Way w) { return isPowerIn(w, POWER_LINE_TAGS); } /** * Determines if the specified primitive denotes a power station. * @param p The primitive to be tested * @return {@code true} if power key is set and equal to station/sub_station/plant */ protected static final boolean isPowerStation(OsmPrimitive p) { return isPowerIn(p, POWER_STATION_TAGS) || isBuildingIn(p, BUILDING_STATION_TAGS); } /** * Determines if the specified node denotes a power tower/pole. * @param n The node to be tested * @return {@code true} if power key is set and equal to tower/pole */ protected static final boolean isPowerTower(Node n) { return isPowerIn(n, POWER_TOWER_TAGS); } /** * Determines if the specified node denotes a power infrastructure allowed on a power line. * @param n The node to be tested * @return True if power key is set and equal to switch/tranformer/busbar/generator */ protected static final boolean isPowerAllowed(Node n) { return isPowerIn(n, POWER_ALLOWED_TAGS); } /** * Helper function to check if power tag is a certain value. * @param p The primitive to be tested * @param values List of possible values * @return {@code true} if power key is set and equal to possible values */ private static boolean isPowerIn(OsmPrimitive p, Collection<String> values) { return p.hasTag("power", values); } /** * Helper function to check if building tag is a certain value. * @param p The primitive to be tested * @param values List of possible values * @return {@code true} if power key is set and equal to possible values */ private static boolean isBuildingIn(OsmPrimitive p, Collection<String> values) { return p.hasTag("building", values); } }