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}