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