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