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    }