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 }