001// License: GPL. See LICENSE file for details. 002package org.openstreetmap.josm.data.validation.tests; 003 004import static org.openstreetmap.josm.tools.I18n.marktr; 005import static org.openstreetmap.josm.tools.I18n.tr; 006 007import java.text.MessageFormat; 008import java.util.Arrays; 009import java.util.Collections; 010import java.util.HashSet; 011import java.util.List; 012import java.util.Set; 013 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.Way; 018import org.openstreetmap.josm.data.validation.Severity; 019import org.openstreetmap.josm.data.validation.Test; 020import org.openstreetmap.josm.data.validation.TestError; 021 022/** 023 * Check area type ways for errors 024 * 025 * @author stoecker 026 * @since 3669 027 */ 028public class UnclosedWays extends Test { 029 030 /** 031 * Constructs a new {@code UnclosedWays} test. 032 */ 033 public UnclosedWays() { 034 super(tr("Unclosed Ways"), tr("This tests if ways which should be circular are closed.")); 035 } 036 037 /** 038 * A check performed by UnclosedWays test. 039 * @since 6390 040 */ 041 private class UnclosedWaysCheck { 042 /** The unique numeric code for this check */ 043 public final int code; 044 /** The OSM key checked */ 045 public final String key; 046 /** The English message */ 047 private final String engMessage; 048 /** The special values, to be ignored if ignore is set to true; to be considered only if ignore is set to false */ 049 private final List<String> specialValues; 050 /** The boolean indicating if special values must be ignored or considered only */ 051 private final boolean ignore; 052 053 /** 054 * Constructs a new {@code UnclosedWaysCheck}. 055 * @param code The unique numeric code for this check 056 * @param key The OSM key checked 057 * @param engMessage The English message 058 */ 059 @SuppressWarnings("unchecked") 060 public UnclosedWaysCheck(int code, String key, String engMessage) { 061 this(code, key, engMessage, (List<String>) Collections.EMPTY_LIST); 062 } 063 064 /** 065 * Constructs a new {@code UnclosedWaysCheck}. 066 * @param code The unique numeric code for this check 067 * @param key The OSM key checked 068 * @param engMessage The English message 069 * @param ignoredValues The ignored values. 070 */ 071 public UnclosedWaysCheck(int code, String key, String engMessage, List<String> ignoredValues) { 072 this(code, key, engMessage, ignoredValues, true); 073 } 074 075 /** 076 * Constructs a new {@code UnclosedWaysCheck}. 077 * @param code The unique numeric code for this check 078 * @param key The OSM key checked 079 * @param engMessage The English message 080 * @param specialValues The special values, to be ignored if ignore is set to true; to be considered only if ignore is set to false 081 * @param ignore indicates if special values must be ignored or considered only 082 */ 083 public UnclosedWaysCheck(int code, String key, String engMessage, List<String> specialValues, boolean ignore) { 084 this.code = code; 085 this.key = key; 086 this.engMessage = engMessage; 087 this.specialValues = specialValues; 088 this.ignore = ignore; 089 } 090 091 /** 092 * Returns the test error of the given way, if any. 093 * @param w The way to check 094 * @return The test error if the way is erroneous, {@code null} otherwise 095 */ 096 public final TestError getTestError(Way w) { 097 String value = w.get(key); 098 if (isValueErroneous(value)) { 099 String type = engMessage.contains("{0}") ? tr(engMessage, tr(value)) : tr(engMessage); 100 String etype = engMessage.contains("{0}") ? MessageFormat.format(engMessage, value) : engMessage; 101 return new TestError(UnclosedWays.this, Severity.WARNING, tr("Unclosed way"), 102 type, etype, code, Arrays.asList(w), 103 // The important parts of an unclosed way are the first and 104 // the last node which should be connected, therefore we highlight them 105 Arrays.asList(w.firstNode(), w.lastNode())); 106 } 107 return null; 108 } 109 110 protected boolean isValueErroneous(String value) { 111 return value != null && ignore != specialValues.contains(value); 112 } 113 } 114 115 /** 116 * A check performed by UnclosedWays test where the key is treated as boolean. 117 * @since 6390 118 */ 119 private final class UnclosedWaysBooleanCheck extends UnclosedWaysCheck { 120 121 /** 122 * Constructs a new {@code UnclosedWaysBooleanCheck}. 123 * @param code The unique numeric code for this check 124 * @param key The OSM key checked 125 * @param engMessage The English message 126 */ 127 public UnclosedWaysBooleanCheck(int code, String key, String engMessage) { 128 super(code, key, engMessage); 129 } 130 131 @Override 132 protected boolean isValueErroneous(String value) { 133 Boolean btest = OsmUtils.getOsmBoolean(value); 134 // Not a strict boolean comparison to handle building=house like a building=yes 135 return (btest != null && btest) || (btest == null && value != null); 136 } 137 } 138 139 private final UnclosedWaysCheck[] checks = { 140 new UnclosedWaysCheck(1101, "natural", marktr("natural type {0}"), Arrays.asList("coastline", "cliff", "tree_row")), 141 new UnclosedWaysCheck(1102, "landuse", marktr("landuse type {0}")), 142 new UnclosedWaysCheck(1103, "amenities", marktr("amenities type {0}")), 143 new UnclosedWaysCheck(1104, "sport", marktr("sport type {0}"), Arrays.asList("water_slide", "climbing")), 144 new UnclosedWaysCheck(1105, "tourism", marktr("tourism type {0}")), 145 new UnclosedWaysCheck(1106, "shop", marktr("shop type {0}")), 146 new UnclosedWaysCheck(1107, "leisure", marktr("leisure type {0}"), Arrays.asList("track")), 147 new UnclosedWaysCheck(1108, "waterway", marktr("waterway type {0}"), Arrays.asList("riverbank"), false), 148 new UnclosedWaysBooleanCheck(1120, "building", marktr("building")), 149 new UnclosedWaysBooleanCheck(1130, "area", marktr("area")), 150 }; 151 152 /** 153 * Returns the set of checked OSM keys. 154 * @return The set of checked OSM keys. 155 * @since 6390 156 */ 157 public Set<String> getCheckedKeys() { 158 Set<String> keys = new HashSet<String>(); 159 for (UnclosedWaysCheck c : checks) { 160 keys.add(c.key); 161 } 162 return keys; 163 } 164 165 @Override 166 public void visit(Way w) { 167 168 if (!w.isUsable() || w.isArea()) 169 return; 170 171 for (OsmPrimitive parent: w.getReferrers()) { 172 if (parent instanceof Relation && ((Relation)parent).isMultipolygon()) 173 return; 174 } 175 176 for (UnclosedWaysCheck c : checks) { 177 TestError error = c.getTestError(w); 178 if (error != null) { 179 errors.add(error); 180 return; 181 } 182 } 183 } 184}