// License: GPL. For details, see LICENSE file.
package org.openstreetmap.josm.data.validation.tests;
import static org.openstreetmap.josm.tools.I18n.tr;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.openstreetmap.josm.Main;
import org.openstreetmap.josm.command.Command;
import org.openstreetmap.josm.data.osm.DataSet;
import org.openstreetmap.josm.data.osm.OsmPrimitive;
import org.openstreetmap.josm.data.osm.Relation;
import org.openstreetmap.josm.data.osm.RelationMember;
import org.openstreetmap.josm.data.osm.Way;
import org.openstreetmap.josm.data.validation.Severity;
import org.openstreetmap.josm.data.validation.Test;
import org.openstreetmap.josm.data.validation.TestError;
import org.openstreetmap.josm.gui.progress.ProgressMonitor;
/**
* Checks for untagged ways
*
* @author frsantos
*/
public class UntaggedWay extends Test {
// CHECKSTYLE.OFF: SingleSpaceSeparator
/** Empty way error */
protected static final int EMPTY_WAY = 301;
/** Untagged way error */
protected static final int UNTAGGED_WAY = 302;
/** Unnamed way error */
protected static final int UNNAMED_WAY = 303;
/** One node way error */
protected static final int ONE_NODE_WAY = 304;
/** Unnamed junction error */
protected static final int UNNAMED_JUNCTION = 305;
/** Untagged, but commented way error */
protected static final int COMMENTED_WAY = 306;
// CHECKSTYLE.ON: SingleSpaceSeparator
private Set<Way> waysUsedInRelations;
/** Ways that must have a name */
static final Set<String> NAMED_WAYS = new HashSet<>();
static {
NAMED_WAYS.add("motorway");
NAMED_WAYS.add("trunk");
NAMED_WAYS.add("primary");
NAMED_WAYS.add("secondary");
NAMED_WAYS.add("tertiary");
NAMED_WAYS.add("residential");
NAMED_WAYS.add("pedestrian");
}
/** Whitelist of roles allowed to reference an untagged way */
static final Set<String> WHITELIST = new HashSet<>();
static {
WHITELIST.add("outer");
WHITELIST.add("inner");
WHITELIST.add("perimeter");
WHITELIST.add("edge");
WHITELIST.add("outline");
}
/**
* Constructor
*/
public UntaggedWay() {
super(tr("Untagged, empty and one node ways"),
tr("This test checks for untagged, empty and one node ways."));
}
@Override
public void visit(Way w) {
if (!w.isUsable())
return;
Map<String, String> tags = w.getKeys();
if (!tags.isEmpty()) {
String highway = tags.get("highway");
if (highway != null && NAMED_WAYS.contains(highway) && !tags.containsKey("name") && !tags.containsKey("ref")
&& !"yes".equals(tags.get("noname"))) {
boolean isJunction = false;
boolean hasName = false;
for (String key : tags.keySet()) {
hasName = key.startsWith("name:") || key.endsWith("_name") || key.endsWith("_ref");
if (hasName) {
break;
}
if ("junction".equals(key)) {
isJunction = true;
break;
}
}
if (!hasName && !isJunction) {
errors.add(TestError.builder(this, Severity.WARNING, UNNAMED_WAY)
.message(tr("Unnamed ways"))
.primitives(w)
.build());
} else if (isJunction) {
errors.add(TestError.builder(this, Severity.OTHER, UNNAMED_JUNCTION)
.message(tr("Unnamed junction"))
.primitives(w)
.build());
}
}
}
if (!w.isTagged() && !waysUsedInRelations.contains(w)) {
if (w.hasKeys()) {
errors.add(TestError.builder(this, Severity.WARNING, COMMENTED_WAY)
.message(tr("Untagged ways (commented)"))
.primitives(w)
.build());
} else {
errors.add(TestError.builder(this, Severity.WARNING, UNTAGGED_WAY)
.message(tr("Untagged ways"))
.primitives(w)
.build());
}
}
if (w.getNodesCount() == 0) {
errors.add(TestError.builder(this, Severity.ERROR, EMPTY_WAY)
.message(tr("Empty ways"))
.primitives(w)
.build());
} else if (w.getNodesCount() == 1) {
errors.add(TestError.builder(this, Severity.ERROR, ONE_NODE_WAY)
.message(tr("One node ways"))
.primitives(w)
.build());
}
}
@Override
public void startTest(ProgressMonitor monitor) {
super.startTest(monitor);
DataSet ds = Main.getLayerManager().getEditDataSet();
if (ds == null)
return;
waysUsedInRelations = new HashSet<>();
for (Relation r : ds.getRelations()) {
if (r.isUsable()) {
for (RelationMember m : r.getMembers()) {
if (r.isMultipolygon() || WHITELIST.contains(m.getRole())) {
OsmPrimitive member = m.getMember();
if (member instanceof Way && member.isUsable() && !member.isTagged()) {
waysUsedInRelations.add((Way) member);
}
}
}
}
}
}
@Override
public void endTest() {
waysUsedInRelations = null;
super.endTest();
}
@Override
public boolean isFixable(TestError testError) {
if (testError.getTester() instanceof UntaggedWay)
return testError.getCode() == EMPTY_WAY
|| testError.getCode() == ONE_NODE_WAY;
return false;
}
@Override
public Command fixError(TestError testError) {
return deletePrimitivesIfNeeded(testError.getPrimitives());
}
@Override
public boolean isPrimitiveUsable(OsmPrimitive p) {
return p.isUsable();
}
}