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