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}