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    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    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(TestError.builder(this, Severity.WARNING, UNNAMED_WAY)
101                            .message(tr("Unnamed ways"))
102                            .primitives(w)
103                            .build());
104                } else if (isJunction) {
105                    errors.add(TestError.builder(this, Severity.OTHER, UNNAMED_JUNCTION)
106                            .message(tr("Unnamed junction"))
107                            .primitives(w)
108                            .build());
109                }
110            }
111        }
112
113        if (!w.isTagged() && !waysUsedInRelations.contains(w)) {
114            if (w.hasKeys()) {
115                errors.add(TestError.builder(this, Severity.WARNING, COMMENTED_WAY)
116                        .message(tr("Untagged ways (commented)"))
117                        .primitives(w)
118                        .build());
119            } else {
120                errors.add(TestError.builder(this, Severity.WARNING, UNTAGGED_WAY)
121                        .message(tr("Untagged ways"))
122                        .primitives(w)
123                        .build());
124            }
125        }
126
127        if (w.getNodesCount() == 0) {
128            errors.add(TestError.builder(this, Severity.ERROR, EMPTY_WAY)
129                    .message(tr("Empty ways"))
130                    .primitives(w)
131                    .build());
132        } else if (w.getNodesCount() == 1) {
133            errors.add(TestError.builder(this, Severity.ERROR, ONE_NODE_WAY)
134                    .message(tr("One node ways"))
135                    .primitives(w)
136                    .build());
137        }
138    }
139
140    @Override
141    public void startTest(ProgressMonitor monitor) {
142        super.startTest(monitor);
143        DataSet ds = Main.getLayerManager().getEditDataSet();
144        if (ds == null)
145            return;
146        waysUsedInRelations = new HashSet<>();
147        for (Relation r : ds.getRelations()) {
148            if (r.isUsable()) {
149                for (RelationMember m : r.getMembers()) {
150                    if (r.isMultipolygon() || WHITELIST.contains(m.getRole())) {
151                        OsmPrimitive member = m.getMember();
152                        if (member instanceof Way && member.isUsable() && !member.isTagged()) {
153                            waysUsedInRelations.add((Way) member);
154                        }
155                    }
156                }
157            }
158        }
159    }
160
161    @Override
162    public void endTest() {
163        waysUsedInRelations = null;
164        super.endTest();
165    }
166
167    @Override
168    public boolean isFixable(TestError testError) {
169        if (testError.getTester() instanceof UntaggedWay)
170            return testError.getCode() == EMPTY_WAY
171                || testError.getCode() == ONE_NODE_WAY;
172
173        return false;
174    }
175
176    @Override
177    public Command fixError(TestError testError) {
178        return deletePrimitivesIfNeeded(testError.getPrimitives());
179    }
180
181    @Override
182    public boolean isPrimitiveUsable(OsmPrimitive p) {
183        return p.isUsable();
184    }
185}