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