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.actions.JosmAction; 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 /** Empty way error */ 030 protected static final int EMPTY_WAY = 301; 031 /** Untagged way error */ 032 protected static final int UNTAGGED_WAY = 302; 033 /** Unnamed way error */ 034 protected static final int UNNAMED_WAY = 303; 035 /** One node way error */ 036 protected static final int ONE_NODE_WAY = 304; 037 /** Unnamed junction error */ 038 protected static final int UNNAMED_JUNCTION = 305; 039 /** Untagged, but commented way error */ 040 protected static final int COMMENTED_WAY = 306; 041 042 private Set<Way> waysUsedInRelations; 043 044 /** Ways that must have a name */ 045 protected static final Set<String> NAMED_WAYS = new HashSet<>(); 046 static { 047 NAMED_WAYS.add("motorway"); 048 NAMED_WAYS.add("trunk"); 049 NAMED_WAYS.add("primary"); 050 NAMED_WAYS.add("secondary"); 051 NAMED_WAYS.add("tertiary"); 052 NAMED_WAYS.add("residential"); 053 NAMED_WAYS.add("pedestrian"); 054 } 055 056 /** Whitelist of roles allowed to reference an untagged way */ 057 protected static final Set<String> WHITELIST = new HashSet<>(); 058 static { 059 WHITELIST.add("outer"); 060 WHITELIST.add("inner"); 061 WHITELIST.add("perimeter"); 062 WHITELIST.add("edge"); 063 WHITELIST.add("outline"); 064 } 065 066 /** 067 * Constructor 068 */ 069 public UntaggedWay() { 070 super(tr("Untagged, empty and one node ways"), 071 tr("This test checks for untagged, empty and one node ways.")); 072 } 073 074 @Override 075 public void visit(Way w) { 076 if (!w.isUsable()) 077 return; 078 079 Map<String, String> tags = w.getKeys(); 080 if (!tags.isEmpty()) { 081 String highway = tags.get("highway"); 082 if (highway != null && NAMED_WAYS.contains(highway) && !tags.containsKey("name") && !tags.containsKey("ref") 083 && !"yes".equals(tags.get("noname"))) { 084 boolean isJunction = false; 085 boolean hasName = false; 086 for (String key : tags.keySet()) { 087 hasName = key.startsWith("name:") || key.endsWith("_name") || key.endsWith("_ref"); 088 if (hasName) { 089 break; 090 } 091 if ("junction".equals(key)) { 092 isJunction = true; 093 break; 094 } 095 } 096 097 if (!hasName && !isJunction) { 098 errors.add(new TestError(this, Severity.WARNING, tr("Unnamed ways"), UNNAMED_WAY, w)); 099 } else if (isJunction) { 100 errors.add(new TestError(this, Severity.OTHER, tr("Unnamed junction"), UNNAMED_JUNCTION, w)); 101 } 102 } 103 } 104 105 if (!w.isTagged() && !waysUsedInRelations.contains(w)) { 106 if (w.hasKeys()) { 107 errors.add(new TestError(this, Severity.WARNING, tr("Untagged ways (commented)"), COMMENTED_WAY, w)); 108 } else { 109 errors.add(new TestError(this, Severity.WARNING, tr("Untagged ways"), UNTAGGED_WAY, w)); 110 } 111 } 112 113 if (w.getNodesCount() == 0) { 114 errors.add(new TestError(this, Severity.ERROR, tr("Empty ways"), EMPTY_WAY, w)); 115 } else if (w.getNodesCount() == 1) { 116 errors.add(new TestError(this, Severity.ERROR, tr("One node ways"), ONE_NODE_WAY, w)); 117 } 118 } 119 120 @Override 121 public void startTest(ProgressMonitor monitor) { 122 super.startTest(monitor); 123 DataSet ds = JosmAction.getCurrentDataSet(); 124 if (ds == null) 125 return; 126 waysUsedInRelations = new HashSet<>(); 127 for (Relation r : ds.getRelations()) { 128 if (r.isUsable()) { 129 for (RelationMember m : r.getMembers()) { 130 if (r.isMultipolygon() || WHITELIST.contains(m.getRole())) { 131 OsmPrimitive member = m.getMember(); 132 if (member instanceof Way && member.isUsable() && !member.isTagged()) { 133 waysUsedInRelations.add((Way) member); 134 } 135 } 136 } 137 } 138 } 139 } 140 141 @Override 142 public void endTest() { 143 waysUsedInRelations = null; 144 super.endTest(); 145 } 146 147 @Override 148 public boolean isFixable(TestError testError) { 149 if (testError.getTester() instanceof UntaggedWay) 150 return testError.getCode() == EMPTY_WAY 151 || testError.getCode() == ONE_NODE_WAY; 152 153 return false; 154 } 155 156 @Override 157 public Command fixError(TestError testError) { 158 return deletePrimitivesIfNeeded(testError.getPrimitives()); 159 } 160 161 @Override 162 public boolean isPrimitiveUsable(OsmPrimitive p) { 163 return p.isUsable(); 164 } 165}