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