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