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