001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.data.validation.tests; 003 004import static org.openstreetmap.josm.data.validation.tests.CrossingWays.HIGHWAY; 005import static org.openstreetmap.josm.tools.I18n.tr; 006 007import java.util.HashSet; 008import java.util.Map; 009import java.util.Set; 010 011import org.openstreetmap.josm.Main; 012import org.openstreetmap.josm.command.Command; 013import org.openstreetmap.josm.data.osm.DataSet; 014import org.openstreetmap.josm.data.osm.OsmPrimitive; 015import org.openstreetmap.josm.data.osm.Relation; 016import org.openstreetmap.josm.data.osm.RelationMember; 017import org.openstreetmap.josm.data.osm.Way; 018import org.openstreetmap.josm.data.validation.Severity; 019import org.openstreetmap.josm.data.validation.Test; 020import org.openstreetmap.josm.data.validation.TestError; 021import org.openstreetmap.josm.gui.progress.ProgressMonitor; 022 023/** 024 * Checks for untagged ways 025 * 026 * @author frsantos 027 */ 028public class UntaggedWay extends Test { 029 030 // CHECKSTYLE.OFF: SingleSpaceSeparator 031 /** Empty way error */ 032 protected static final int EMPTY_WAY = 301; 033 /** Untagged way error */ 034 protected static final int UNTAGGED_WAY = 302; 035 /** Unnamed way error */ 036 protected static final int UNNAMED_WAY = 303; 037 /** One node way error */ 038 protected static final int ONE_NODE_WAY = 304; 039 /** Unnamed junction error */ 040 protected static final int UNNAMED_JUNCTION = 305; 041 /** Untagged, but commented way error */ 042 protected static final int COMMENTED_WAY = 306; 043 // CHECKSTYLE.ON: SingleSpaceSeparator 044 045 private Set<Way> waysUsedInRelations; 046 047 /** Ways that must have a name */ 048 static final Set<String> NAMED_WAYS = new HashSet<>(); 049 static { 050 NAMED_WAYS.add("motorway"); 051 NAMED_WAYS.add("trunk"); 052 NAMED_WAYS.add("primary"); 053 NAMED_WAYS.add("secondary"); 054 NAMED_WAYS.add("tertiary"); 055 NAMED_WAYS.add("residential"); 056 NAMED_WAYS.add("pedestrian"); 057 } 058 059 /** Whitelist of roles allowed to reference an untagged way */ 060 static final Set<String> WHITELIST = new HashSet<>(); 061 static { 062 WHITELIST.add("outer"); 063 WHITELIST.add("inner"); 064 WHITELIST.add("perimeter"); 065 WHITELIST.add("edge"); 066 WHITELIST.add("outline"); 067 } 068 069 /** 070 * Constructor 071 */ 072 public UntaggedWay() { 073 super(tr("Untagged, empty and one node ways"), 074 tr("This test checks for untagged, empty and one node ways.")); 075 } 076 077 @Override 078 public void visit(Way w) { 079 if (!w.isUsable()) 080 return; 081 082 Map<String, String> tags = w.getKeys(); 083 if (!tags.isEmpty()) { 084 String highway = tags.get(HIGHWAY); 085 if (highway != null && NAMED_WAYS.contains(highway) && !tags.containsKey("name") && !tags.containsKey("ref") 086 && !"yes".equals(tags.get("noname"))) { 087 boolean isJunction = false; 088 boolean hasName = false; 089 for (String key : tags.keySet()) { 090 hasName = key.startsWith("name:") || key.endsWith("_name") || key.endsWith("_ref"); 091 if (hasName) { 092 break; 093 } 094 if ("junction".equals(key)) { 095 isJunction = true; 096 break; 097 } 098 } 099 100 if (!hasName && !isJunction) { 101 errors.add(TestError.builder(this, Severity.WARNING, UNNAMED_WAY) 102 .message(tr("Unnamed ways")) 103 .primitives(w) 104 .build()); 105 } else if (isJunction) { 106 errors.add(TestError.builder(this, Severity.OTHER, UNNAMED_JUNCTION) 107 .message(tr("Unnamed junction")) 108 .primitives(w) 109 .build()); 110 } 111 } 112 } 113 114 if (!w.isTagged() && !waysUsedInRelations.contains(w)) { 115 if (w.hasKeys()) { 116 errors.add(TestError.builder(this, Severity.WARNING, COMMENTED_WAY) 117 .message(tr("Untagged ways (commented)")) 118 .primitives(w) 119 .build()); 120 } else { 121 errors.add(TestError.builder(this, Severity.WARNING, UNTAGGED_WAY) 122 .message(tr("Untagged ways")) 123 .primitives(w) 124 .build()); 125 } 126 } 127 128 if (w.getNodesCount() == 0) { 129 errors.add(TestError.builder(this, Severity.ERROR, EMPTY_WAY) 130 .message(tr("Empty ways")) 131 .primitives(w) 132 .build()); 133 } else if (w.getNodesCount() == 1) { 134 errors.add(TestError.builder(this, Severity.ERROR, ONE_NODE_WAY) 135 .message(tr("One node ways")) 136 .primitives(w) 137 .build()); 138 } 139 } 140 141 @Override 142 public void startTest(ProgressMonitor monitor) { 143 super.startTest(monitor); 144 DataSet ds = Main.main.getEditDataSet(); 145 if (ds == null) 146 return; 147 waysUsedInRelations = new HashSet<>(); 148 for (Relation r : ds.getRelations()) { 149 if (r.isUsable()) { 150 for (RelationMember m : r.getMembers()) { 151 if (r.isMultipolygon() || WHITELIST.contains(m.getRole())) { 152 OsmPrimitive member = m.getMember(); 153 if (member instanceof Way && member.isUsable() && !member.isTagged()) { 154 waysUsedInRelations.add((Way) member); 155 } 156 } 157 } 158 } 159 } 160 } 161 162 @Override 163 public void endTest() { 164 waysUsedInRelations = null; 165 super.endTest(); 166 } 167 168 @Override 169 public boolean isFixable(TestError testError) { 170 if (testError.getTester() instanceof UntaggedWay) 171 return testError.getCode() == EMPTY_WAY 172 || testError.getCode() == ONE_NODE_WAY; 173 174 return false; 175 } 176 177 @Override 178 public Command fixError(TestError testError) { 179 return deletePrimitivesIfNeeded(testError.getPrimitives()); 180 } 181 182 @Override 183 public boolean isPrimitiveUsable(OsmPrimitive p) { 184 return p.isUsable(); 185 } 186}