001// License: GPL. See LICENSE file for details.
002package org.openstreetmap.josm.data.validation.tests;
003
004import static org.openstreetmap.josm.tools.I18n.tr;
005
006import java.util.HashSet;
007import java.util.Map;
008import java.util.Set;
009
010import org.openstreetmap.josm.Main;
011import org.openstreetmap.josm.command.Command;
012import org.openstreetmap.josm.data.osm.OsmPrimitive;
013import org.openstreetmap.josm.data.osm.Relation;
014import org.openstreetmap.josm.data.osm.RelationMember;
015import org.openstreetmap.josm.data.osm.Way;
016import org.openstreetmap.josm.data.validation.Severity;
017import org.openstreetmap.josm.data.validation.Test;
018import org.openstreetmap.josm.data.validation.TestError;
019import org.openstreetmap.josm.gui.progress.ProgressMonitor;
020
021/**
022 * Checks for untagged ways
023 *
024 * @author frsantos
025 */
026public 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) && !tags.containsKey("name") && !tags.containsKey("ref")) {
082                boolean isRoundabout = false;
083                boolean hasName = false;
084                for (String key : w.keySet()) {
085                    hasName = key.startsWith("name:") || key.endsWith("_name") || key.endsWith("_ref");
086                    if (hasName) {
087                        break;
088                    }
089                    if (key.equals("junction")) {
090                        isRoundabout = w.get("junction").equals("roundabout");
091                        break;
092                    }
093                }
094
095                if (!hasName && !isRoundabout) {
096                    errors.add(new TestError(this, Severity.WARNING, tr("Unnamed ways"), UNNAMED_WAY, w));
097                } else if (isRoundabout) {
098                    errors.add(new TestError(this, Severity.WARNING, tr("Unnamed junction"), UNNAMED_JUNCTION, w));
099                }
100            }
101        }
102
103        if (!w.isTagged() && !waysUsedInRelations.contains(w)) {
104            if (w.hasKeys()) {
105                errors.add(new TestError(this, Severity.WARNING, tr("Untagged ways (commented)"), COMMENTED_WAY, w));
106            } else {
107                errors.add(new TestError(this, Severity.WARNING, tr("Untagged ways"), UNTAGGED_WAY, w));
108            }
109        }
110
111        if (w.getNodesCount() == 0) {
112            errors.add(new TestError(this, Severity.ERROR, tr("Empty ways"), EMPTY_WAY, w));
113        } else if (w.getNodesCount() == 1) {
114            errors.add(new TestError(this, Severity.ERROR, tr("One node ways"), ONE_NODE_WAY, w));
115        }
116    }
117
118    @Override
119    public void startTest(ProgressMonitor monitor) {
120        super.startTest(monitor);
121        waysUsedInRelations = new HashSet<Way>();
122        for (Relation r : Main.main.getCurrentDataSet().getRelations()) {
123            if (r.isUsable()) {
124                for (RelationMember m : r.getMembers()) {
125                    if (r.isMultipolygon() || WHITELIST.contains(m.getRole())) {
126                        OsmPrimitive member = m.getMember();
127                        if (member instanceof Way && member.isUsable() && !member.isTagged()) {
128                            waysUsedInRelations.add((Way)member);
129                        }
130                    }
131                }
132            }
133        }
134    }
135
136    @Override
137    public void endTest() {
138        waysUsedInRelations = null;
139        super.endTest();
140    }
141
142    @Override
143    public boolean isFixable(TestError testError) {
144        if (testError.getTester() instanceof UntaggedWay)
145            return testError.getCode() == EMPTY_WAY
146                || testError.getCode() == ONE_NODE_WAY;
147
148        return false;
149    }
150
151    @Override
152    public Command fixError(TestError testError) {
153        return deletePrimitivesIfNeeded(testError.getPrimitives());
154    }
155
156    @Override
157    public boolean isPrimitiveUsable(OsmPrimitive p) {
158        return p.isUsable();
159    }
160}