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.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    protected static final Set<String> NAMED_WAYS = new HashSet<>();
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    protected static final Set<String> WHITELIST = new HashSet<>();
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                    && !"yes".equals(tags.get("noname"))) {
083                boolean isJunction = false;
084                boolean hasName = false;
085                for (String key : tags.keySet()) {
086                    hasName = key.startsWith("name:") || key.endsWith("_name") || key.endsWith("_ref");
087                    if (hasName) {
088                        break;
089                    }
090                    if ("junction".equals(key)) {
091                        isJunction = true;
092                        break;
093                    }
094                }
095
096                if (!hasName && !isJunction) {
097                    errors.add(new TestError(this, Severity.WARNING, tr("Unnamed ways"), UNNAMED_WAY, w));
098                } else if (isJunction) {
099                    errors.add(new TestError(this, Severity.OTHER, tr("Unnamed junction"), UNNAMED_JUNCTION, w));
100                }
101            }
102        }
103
104        if (!w.isTagged() && !waysUsedInRelations.contains(w)) {
105            if (w.hasKeys()) {
106                errors.add(new TestError(this, Severity.WARNING, tr("Untagged ways (commented)"), COMMENTED_WAY, w));
107            } else {
108                errors.add(new TestError(this, Severity.WARNING, tr("Untagged ways"), UNTAGGED_WAY, w));
109            }
110        }
111
112        if (w.getNodesCount() == 0) {
113            errors.add(new TestError(this, Severity.ERROR, tr("Empty ways"), EMPTY_WAY, w));
114        } else if (w.getNodesCount() == 1) {
115            errors.add(new TestError(this, Severity.ERROR, tr("One node ways"), ONE_NODE_WAY, w));
116        }
117    }
118
119    @Override
120    public void startTest(ProgressMonitor monitor) {
121        super.startTest(monitor);
122        waysUsedInRelations = new HashSet<>();
123        for (Relation r : Main.main.getCurrentDataSet().getRelations()) {
124            if (r.isUsable()) {
125                for (RelationMember m : r.getMembers()) {
126                    if (r.isMultipolygon() || WHITELIST.contains(m.getRole())) {
127                        OsmPrimitive member = m.getMember();
128                        if (member instanceof Way && member.isUsable() && !member.isTagged()) {
129                            waysUsedInRelations.add((Way) member);
130                        }
131                    }
132                }
133            }
134        }
135    }
136
137    @Override
138    public void endTest() {
139        waysUsedInRelations = null;
140        super.endTest();
141    }
142
143    @Override
144    public boolean isFixable(TestError testError) {
145        if (testError.getTester() instanceof UntaggedWay)
146            return testError.getCode() == EMPTY_WAY
147                || testError.getCode() == ONE_NODE_WAY;
148
149        return false;
150    }
151
152    @Override
153    public Command fixError(TestError testError) {
154        return deletePrimitivesIfNeeded(testError.getPrimitives());
155    }
156
157    @Override
158    public boolean isPrimitiveUsable(OsmPrimitive p) {
159        return p.isUsable();
160    }
161}