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