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}