// License: GPL. See LICENSE file for details.
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.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 {
/** 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;
private Set<Way> waysUsedInRelations;
/** Ways that must have a name */
public 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 */
public 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")) {
boolean isRoundabout = false;
boolean hasName = false;
for (String key : w.keySet()) {
hasName = key.startsWith("name:") || key.endsWith("_name") || key.endsWith("_ref");
if (hasName) {
break;
}
if ("junction".equals(key)) {
isRoundabout = "roundabout".equals(w.get("junction"));
break;
}
}
if (!hasName && !isRoundabout) {
errors.add(new TestError(this, Severity.WARNING, tr("Unnamed ways"), UNNAMED_WAY, w));
} else if (isRoundabout) {
errors.add(new TestError(this, Severity.WARNING, tr("Unnamed junction"), UNNAMED_JUNCTION, w));
}
}
}
if (!w.isTagged() && !waysUsedInRelations.contains(w)) {
if (w.hasKeys()) {
errors.add(new TestError(this, Severity.WARNING, tr("Untagged ways (commented)"), COMMENTED_WAY, w));
} else {
errors.add(new TestError(this, Severity.WARNING, tr("Untagged ways"), UNTAGGED_WAY, w));
}
}
if (w.getNodesCount() == 0) {
errors.add(new TestError(this, Severity.ERROR, tr("Empty ways"), EMPTY_WAY, w));
} else if (w.getNodesCount() == 1) {
errors.add(new TestError(this, Severity.ERROR, tr("One node ways"), ONE_NODE_WAY, w));
}
}
@Override
public void startTest(ProgressMonitor monitor) {
super.startTest(monitor);
waysUsedInRelations = new HashSet<>();
for (Relation r : Main.main.getCurrentDataSet().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();
}
}