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