001 // License: GPL. See LICENSE file for details. 002 package org.openstreetmap.josm.data.validation.tests; 003 004 import static org.openstreetmap.josm.tools.I18n.tr; 005 006 import java.util.ArrayList; 007 import java.util.Arrays; 008 import java.util.Collection; 009 import java.util.HashMap; 010 import java.util.List; 011 import java.util.Map; 012 013 import org.openstreetmap.josm.Main; 014 import org.openstreetmap.josm.command.ChangePropertyCommand; 015 import org.openstreetmap.josm.command.Command; 016 import org.openstreetmap.josm.data.osm.Node; 017 import org.openstreetmap.josm.data.osm.OsmPrimitive; 018 import org.openstreetmap.josm.data.osm.Relation; 019 import org.openstreetmap.josm.data.osm.Way; 020 import org.openstreetmap.josm.data.osm.visitor.paint.relations.Multipolygon; 021 import org.openstreetmap.josm.data.osm.visitor.paint.relations.Multipolygon.JoinedWay; 022 import org.openstreetmap.josm.data.osm.visitor.paint.relations.MultipolygonCache; 023 import org.openstreetmap.josm.data.validation.Severity; 024 import org.openstreetmap.josm.data.validation.Test; 025 import org.openstreetmap.josm.data.validation.TestError; 026 import org.openstreetmap.josm.tools.Geometry; 027 028 /** 029 * Checks for nodes in power lines/minor_lines that do not have a power=tower/pole tag.<br/> 030 * See #7812 for discussions about this test. 031 */ 032 public class PowerLines extends Test { 033 034 protected static final int POWER_LINES = 2501; 035 036 public static final Collection<String> POWER_LINE_TAGS = Arrays.asList("line", "minor_line"); 037 public static final Collection<String> POWER_TOWER_TAGS = Arrays.asList("tower", "pole"); 038 public static final Collection<String> POWER_STATION_TAGS = Arrays.asList("station", "sub_station", "plant", "generator"); 039 public static final Collection<String> POWER_ALLOWED_TAGS = Arrays.asList("switch", "transformer", "busbar", "generator"); 040 041 protected final Map<Way, String> towerPoleTagMap = new HashMap<Way, String>(); 042 043 protected final List<PowerLineError> potentialErrors = new ArrayList<PowerLineError>(); 044 045 protected final List<OsmPrimitive> powerStations = new ArrayList<OsmPrimitive>(); 046 047 public PowerLines() { 048 super(tr("Power lines"), tr("Checks for nodes in power lines that do not have a power=tower/pole tag.")); 049 } 050 051 @Override 052 public void visit(Way w) { 053 if (w.isUsable()) { 054 if (isPowerLine(w)) { 055 String fixValue = null; 056 boolean erroneous = false; 057 boolean canFix = false; 058 for (Node n : w.getNodes()) { 059 if (!isPowerTower(n)) { 060 if (!isPowerAllowed(n)) { 061 potentialErrors.add(new PowerLineError(n, w)); 062 erroneous = true; 063 } 064 } else if (fixValue == null) { 065 // First tower/pole tag found, remember it 066 fixValue = n.get("power"); 067 canFix = true; 068 } else if (!fixValue.equals(n.get("power"))) { 069 // The power line contains both "tower" and "pole" -> cannot fix this error 070 canFix = false; 071 } 072 } 073 if (erroneous && canFix) { 074 towerPoleTagMap.put(w, fixValue); 075 } 076 } else if (w.isClosed() && isPowerStation(w)) { 077 powerStations.add(w); 078 } 079 } 080 } 081 082 @Override 083 public void visit(Relation r) { 084 if (r.isMultipolygon() && isPowerStation(r)) { 085 powerStations.add(r); 086 } 087 } 088 089 @Override 090 public void endTest() { 091 for (PowerLineError e : potentialErrors) { 092 if (!isInPowerStation(e.getNode())) { 093 errors.add(e); 094 } 095 } 096 super.endTest(); 097 } 098 099 protected final boolean isInPowerStation(Node n) { 100 for (OsmPrimitive station : powerStations) { 101 List<List<Node>> nodesLists = new ArrayList<List<Node>>(); 102 if (station instanceof Way) { 103 nodesLists.add(((Way)station).getNodes()); 104 } else if (station instanceof Relation) { 105 Multipolygon polygon = MultipolygonCache.getInstance().get(Main.map.mapView, (Relation) station); 106 if (polygon != null) { 107 for (JoinedWay outer : Multipolygon.joinWays(polygon.getOuterWays())) { 108 nodesLists.add(outer.getNodes()); 109 } 110 } 111 } 112 for (List<Node> nodes : nodesLists) { 113 if (Geometry.nodeInsidePolygon(n, nodes)) { 114 return true; 115 } 116 } 117 } 118 return false; 119 } 120 121 @Override 122 public Command fixError(TestError testError) { 123 if (isFixable(testError)) { 124 return new ChangePropertyCommand( 125 testError.getPrimitives().iterator().next(), 126 "power", towerPoleTagMap.get(((PowerLineError)testError).line)); 127 } 128 return null; 129 } 130 131 @Override 132 public boolean isFixable(TestError testError) { 133 return testError instanceof PowerLineError && towerPoleTagMap.containsKey(((PowerLineError)testError).line); 134 } 135 136 /** 137 * Determines if the specified way denotes a power line. 138 * @param w The way to be tested 139 * @return True if power key is set and equal to line/minor_line 140 */ 141 protected static final boolean isPowerLine(Way w) { 142 return isPowerIn(w, POWER_LINE_TAGS); 143 } 144 145 /** 146 * Determines if the specified primitive denotes a power station. 147 * @param w The way to be tested 148 * @return True if power key is set and equal to station/sub_station/plant 149 */ 150 protected static final boolean isPowerStation(OsmPrimitive p) { 151 return isPowerIn(p, POWER_STATION_TAGS); 152 } 153 154 /** 155 * Determines if the specified node denotes a power tower/pole. 156 * @param w The node to be tested 157 * @return True if power key is set and equal to tower/pole 158 */ 159 protected static final boolean isPowerTower(Node n) { 160 return isPowerIn(n, POWER_TOWER_TAGS); 161 } 162 163 /** 164 * Determines if the specified node denotes a power infrastructure allowed on a power line. 165 * @param w The node to be tested 166 * @return True if power key is set and equal to switch/tranformer/busbar/generator 167 */ 168 protected static final boolean isPowerAllowed(Node n) { 169 return isPowerIn(n, POWER_ALLOWED_TAGS); 170 } 171 172 private static final boolean isPowerIn(OsmPrimitive p, Collection<String> values) { 173 String v = p.get("power"); 174 return v != null && values != null && values.contains(v); 175 } 176 177 protected class PowerLineError extends TestError { 178 public final Way line; 179 public PowerLineError(Node n, Way line) { 180 super(PowerLines.this, Severity.WARNING, 181 tr("Missing power tower/pole within power line"), POWER_LINES, n); 182 this.line = line; 183 } 184 public final Node getNode() { 185 return (Node) getPrimitives().iterator().next(); 186 } 187 } 188 }