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.LinkedHashSet; 010import java.util.List; 011import java.util.Set; 012 013import org.openstreetmap.josm.data.osm.Node; 014import org.openstreetmap.josm.data.osm.OsmPrimitive; 015import org.openstreetmap.josm.data.osm.Relation; 016import org.openstreetmap.josm.data.osm.RelationMember; 017import org.openstreetmap.josm.data.osm.Way; 018import org.openstreetmap.josm.data.osm.visitor.paint.relations.Multipolygon; 019import org.openstreetmap.josm.data.osm.visitor.paint.relations.Multipolygon.JoinedWay; 020import org.openstreetmap.josm.data.osm.visitor.paint.relations.MultipolygonCache; 021import org.openstreetmap.josm.data.validation.Severity; 022import org.openstreetmap.josm.data.validation.Test; 023import org.openstreetmap.josm.data.validation.TestError; 024import org.openstreetmap.josm.gui.progress.ProgressMonitor; 025import org.openstreetmap.josm.tools.Geometry; 026 027/** 028 * Checks for nodes in power lines/minor_lines that do not have a power=tower/pole tag.<br> 029 * See #7812 for discussions about this test. 030 */ 031public class PowerLines extends Test { 032 033 /** Test identifier */ 034 protected static final int POWER_LINES = 2501; 035 protected static final int POWER_CONNECTION = 2502; 036 037 /** Values for {@code power} key interpreted as power lines */ 038 static final Collection<String> POWER_LINE_TAGS = Arrays.asList("line", "minor_line"); 039 /** Values for {@code power} key interpreted as power towers */ 040 static final Collection<String> POWER_TOWER_TAGS = Arrays.asList("tower", "pole"); 041 /** Values for {@code power} key interpreted as power stations */ 042 static final Collection<String> POWER_STATION_TAGS = Arrays.asList("station", "sub_station", "substation", "plant", "generator"); 043 /** Values for {@code building} key interpreted as power stations */ 044 static final Collection<String> BUILDING_STATION_TAGS = Arrays.asList("transformer_tower"); 045 /** Values for {@code power} key interpreted as allowed power items */ 046 static final Collection<String> POWER_ALLOWED_TAGS = Arrays.asList("switch", "transformer", "busbar", "generator", "switchgear", 047 "portal", "terminal", "insulator", "connection"); 048 049 private final Set<Node> badConnections = new LinkedHashSet<>(); 050 private final Set<Node> missingTowerOrPole = new LinkedHashSet<>(); 051 052 private final List<OsmPrimitive> powerStations = new ArrayList<>(); 053 054 /** 055 * Constructs a new {@code PowerLines} test. 056 */ 057 public PowerLines() { 058 super(tr("Power lines"), tr("Checks for nodes in power lines that do not have a power=tower/pole/connection tag.")); 059 } 060 061 @Override 062 public void visit(Way w) { 063 if (w.isUsable()) { 064 if (isPowerLine(w) && !w.hasTag("location", "underground")) { 065 for (Node n : w.getNodes()) { 066 if (!isPowerTower(n) && !isPowerAllowed(n) && IN_DOWNLOADED_AREA.test(n) 067 && (!w.isFirstLastNode(n) || !isPowerStation(n))) { 068 missingTowerOrPole.add(n); 069 } 070 } 071 } else if (w.isClosed() && isPowerStation(w)) { 072 powerStations.add(w); 073 } 074 } 075 } 076 077 @Override 078 public void visit(Node n) { 079 boolean nodeInLineOrCable = false; 080 boolean connectedToUnrelated = false; 081 for (Way parent : n.getParentWays()) { 082 if (parent.hasTag("power", "line", "minor_line", "cable")) 083 nodeInLineOrCable = true; 084 else if (!isRelatedToPower(parent)) { 085 connectedToUnrelated = true; 086 } 087 } 088 if (nodeInLineOrCable && connectedToUnrelated) 089 badConnections.add(n); 090 } 091 092 private static boolean isRelatedToPower(Way way) { 093 if (way.hasTag("power") || way.hasTag("building")) 094 return true; 095 for (OsmPrimitive ref : way.getReferrers()) { 096 if (ref instanceof Relation && ref.isMultipolygon() && (ref.hasTag("power") || ref.hasTag("building"))) { 097 for (RelationMember rm : ((Relation) ref).getMembers()) { 098 if (way == rm.getMember()) 099 return true; 100 } 101 } 102 } 103 return false; 104 } 105 106 @Override 107 public void visit(Relation r) { 108 if (r.isMultipolygon() && isPowerStation(r)) { 109 powerStations.add(r); 110 } 111 } 112 113 @Override 114 public void startTest(ProgressMonitor progressMonitor) { 115 super.startTest(progressMonitor); 116 clearCollections(); 117 } 118 119 @Override 120 public void endTest() { 121 for (Node n : missingTowerOrPole) { 122 if (!isInPowerStation(n)) { 123 errors.add(TestError.builder(this, Severity.WARNING, POWER_LINES) 124 .message(tr("Missing power tower/pole/connection within power line")) 125 .primitives(n) 126 .build()); 127 } 128 } 129 130 for (Node n : badConnections) { 131 errors.add(TestError.builder(this, Severity.WARNING, POWER_CONNECTION) 132 .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()); 135 } 136 clearCollections(); 137 super.endTest(); 138 } 139 140 protected final boolean isInPowerStation(Node n) { 141 for (OsmPrimitive station : powerStations) { 142 List<List<Node>> nodesLists = new ArrayList<>(); 143 if (station instanceof Way) { 144 nodesLists.add(((Way) station).getNodes()); 145 } else if (station instanceof Relation) { 146 Multipolygon polygon = MultipolygonCache.getInstance().get((Relation) station); 147 if (polygon != null) { 148 for (JoinedWay outer : Multipolygon.joinWays(polygon.getOuterWays())) { 149 nodesLists.add(outer.getNodes()); 150 } 151 } 152 } 153 for (List<Node> nodes : nodesLists) { 154 if (Geometry.nodeInsidePolygon(n, nodes)) { 155 return true; 156 } 157 } 158 } 159 return false; 160 } 161 162 /** 163 * Determines if the specified way denotes a power line. 164 * @param w The way to be tested 165 * @return {@code true} if power key is set and equal to line/minor_line 166 */ 167 protected static final boolean isPowerLine(Way w) { 168 return isPowerIn(w, POWER_LINE_TAGS); 169 } 170 171 /** 172 * Determines if the specified primitive denotes a power station. 173 * @param p The primitive to be tested 174 * @return {@code true} if power key is set and equal to station/sub_station/plant 175 */ 176 protected static final boolean isPowerStation(OsmPrimitive p) { 177 return isPowerIn(p, POWER_STATION_TAGS) || isBuildingIn(p, BUILDING_STATION_TAGS); 178 } 179 180 /** 181 * Determines if the specified node denotes a power tower/pole. 182 * @param n The node to be tested 183 * @return {@code true} if power key is set and equal to tower/pole 184 */ 185 protected static final boolean isPowerTower(Node n) { 186 return isPowerIn(n, POWER_TOWER_TAGS); 187 } 188 189 /** 190 * Determines if the specified node denotes a power infrastructure allowed on a power line. 191 * @param n The node to be tested 192 * @return True if power key is set and equal to switch/tranformer/busbar/generator 193 */ 194 protected static final boolean isPowerAllowed(Node n) { 195 return isPowerIn(n, POWER_ALLOWED_TAGS); 196 } 197 198 /** 199 * Helper function to check if power tag is a certain value. 200 * @param p The primitive to be tested 201 * @param values List of possible values 202 * @return {@code true} if power key is set and equal to possible values 203 */ 204 private static boolean isPowerIn(OsmPrimitive p, Collection<String> values) { 205 return p.hasTag("power", values); 206 } 207 208 /** 209 * Helper function to check if building tag is a certain value. 210 * @param p The primitive to be tested 211 * @param values List of possible values 212 * @return {@code true} if power key is set and equal to possible values 213 */ 214 private static boolean isBuildingIn(OsmPrimitive p, Collection<String> values) { 215 return p.hasTag("building", values); 216 } 217 218 private void clearCollections() { 219 powerStations.clear(); 220 badConnections.clear(); 221 missingTowerOrPole.clear(); 222 } 223}