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