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.HashSet;
007    import java.util.Map;
008    import java.util.Set;
009    
010    import org.openstreetmap.josm.Main;
011    import org.openstreetmap.josm.command.Command;
012    import org.openstreetmap.josm.data.osm.OsmPrimitive;
013    import org.openstreetmap.josm.data.osm.Relation;
014    import org.openstreetmap.josm.data.osm.RelationMember;
015    import org.openstreetmap.josm.data.osm.Way;
016    import org.openstreetmap.josm.data.validation.Severity;
017    import org.openstreetmap.josm.data.validation.Test;
018    import org.openstreetmap.josm.data.validation.TestError;
019    import org.openstreetmap.josm.gui.progress.ProgressMonitor;
020    
021    /**
022     * Checks for untagged ways
023     *
024     * @author frsantos
025     */
026    public class UntaggedWay extends Test
027    {
028        /** Empty way error */
029        protected static final int EMPTY_WAY    = 301;
030        /** Untagged way error */
031        protected static final int UNTAGGED_WAY = 302;
032        /** Unnamed way error */
033        protected static final int UNNAMED_WAY  = 303;
034        /** One node way error */
035        protected static final int ONE_NODE_WAY = 304;
036        /** Unnamed junction error */
037        protected static final int UNNAMED_JUNCTION  = 305;
038        /** Untagged, but commented way error */
039        protected static final int COMMENTED_WAY = 306;
040    
041        private Set<Way> waysUsedInRelations;
042    
043        /** Ways that must have a name */
044        public static final Set<String> NAMED_WAYS = new HashSet<String>();
045        static {
046            NAMED_WAYS.add( "motorway" );
047            NAMED_WAYS.add( "trunk" );
048            NAMED_WAYS.add( "primary" );
049            NAMED_WAYS.add( "secondary" );
050            NAMED_WAYS.add( "tertiary" );
051            NAMED_WAYS.add( "residential" );
052            NAMED_WAYS.add( "pedestrian" ); ;
053        }
054        
055        /** Whitelist of roles allowed to reference an untagged way */
056        public static final Set<String> WHITELIST = new HashSet<String>();
057        static {
058            WHITELIST.add( "outer" );
059            WHITELIST.add( "inner" );
060            WHITELIST.add( "perimeter" );
061            WHITELIST.add( "edge" );
062            WHITELIST.add( "outline" );
063        }
064    
065        /**
066         * Constructor
067         */
068        public UntaggedWay() {
069            super(tr("Untagged, empty and one node ways"),
070                  tr("This test checks for untagged, empty and one node ways."));
071        }
072    
073        @Override
074        public void visit(Way w) {
075            if (!w.isUsable())
076                return;
077    
078            Map<String, String> tags = w.getKeys();
079            if (!tags.isEmpty()) {
080                String highway = tags.get("highway");
081                if (highway != null && NAMED_WAYS.contains(highway)) {
082                    if (!tags.containsKey("name") && !tags.containsKey("ref")) {
083                        boolean isRoundabout = false;
084                        boolean hasName = false;
085                        for (String key : w.keySet()) {
086                            hasName = key.startsWith("name:") || key.endsWith("_name") || key.endsWith("_ref");
087                            if (hasName) {
088                                break;
089                            }
090                            if (key.equals("junction")) {
091                                isRoundabout = w.get("junction").equals("roundabout");
092                                break;
093                            }
094                        }
095    
096                        if (!hasName && !isRoundabout) {
097                            errors.add(new TestError(this, Severity.WARNING, tr("Unnamed ways"), UNNAMED_WAY, w));
098                        } else if (isRoundabout) {
099                            errors.add(new TestError(this, Severity.WARNING, tr("Unnamed junction"), UNNAMED_JUNCTION, w));
100                        }
101                    }
102                }
103            }
104    
105            if (!w.isTagged() && !waysUsedInRelations.contains(w)) {
106                if (w.hasKeys()) {
107                    errors.add(new TestError(this, Severity.WARNING, tr("Untagged ways (commented)"), COMMENTED_WAY, w));
108                } else {
109                    errors.add(new TestError(this, Severity.WARNING, tr("Untagged ways"), UNTAGGED_WAY, w));
110                }
111            }
112    
113            if (w.getNodesCount() == 0) {
114                errors.add(new TestError(this, Severity.ERROR, tr("Empty ways"), EMPTY_WAY, w));
115            } else if (w.getNodesCount() == 1) {
116                errors.add(new TestError(this, Severity.ERROR, tr("One node ways"), ONE_NODE_WAY, w));
117            }
118        }
119    
120        @Override
121        public void startTest(ProgressMonitor monitor) {
122            super.startTest(monitor);
123            waysUsedInRelations = new HashSet<Way>();
124            for (Relation r : Main.main.getCurrentDataSet().getRelations()) {
125                if (r.isUsable()) {
126                    for (RelationMember m : r.getMembers()) {
127                        if (r.isMultipolygon() || WHITELIST.contains(m.getRole())) {
128                            OsmPrimitive member = m.getMember();
129                            if (member != null && member instanceof Way && member.isUsable() && !member.isTagged()) {
130                                waysUsedInRelations.add((Way)member);
131                            }
132                        }
133                    }
134                }
135            }
136        }
137    
138        @Override
139        public void endTest() {
140            waysUsedInRelations = null;
141            super.endTest();
142        }
143    
144        @Override
145        public boolean isFixable(TestError testError) {
146            if (testError.getTester() instanceof UntaggedWay)
147                return testError.getCode() == EMPTY_WAY
148                    || testError.getCode() == ONE_NODE_WAY;
149    
150            return false;
151        }
152    
153        @Override
154        public Command fixError(TestError testError) {
155            return deletePrimitivesIfNeeded(testError.getPrimitives());
156        }
157    }