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 public UnclosedWaysCheck(int code, String key, String engMessage) { 060 this(code, key, engMessage, Collections.<String>emptyList()); 061 } 062 063 /** 064 * Constructs a new {@code UnclosedWaysCheck}. 065 * @param code The unique numeric code for this check 066 * @param key The OSM key checked 067 * @param engMessage The English message 068 * @param ignoredValues The ignored values. 069 */ 070 public UnclosedWaysCheck(int code, String key, String engMessage, List<String> ignoredValues) { 071 this(code, key, engMessage, ignoredValues, true); 072 } 073 074 /** 075 * Constructs a new {@code UnclosedWaysCheck}. 076 * @param code The unique numeric code for this check 077 * @param key The OSM key checked 078 * @param engMessage The English message 079 * @param specialValues The special values, to be ignored if ignore is set to true; to be considered only if ignore is set to false 080 * @param ignore indicates if special values must be ignored or considered only 081 */ 082 public UnclosedWaysCheck(int code, String key, String engMessage, List<String> specialValues, boolean ignore) { 083 this.code = code; 084 this.key = key; 085 this.engMessage = engMessage; 086 this.specialValues = specialValues; 087 this.ignore = ignore; 088 } 089 090 /** 091 * Returns the test error of the given way, if any. 092 * @param w The way to check 093 * @return The test error if the way is erroneous, {@code null} otherwise 094 */ 095 public final TestError getTestError(Way w) { 096 String value = w.get(key); 097 if (isValueErroneous(value)) { 098 String type = engMessage.contains("{0}") ? tr(engMessage, tr(value)) : tr(engMessage); 099 String etype = engMessage.contains("{0}") ? MessageFormat.format(engMessage, value) : engMessage; 100 return new TestError(UnclosedWays.this, Severity.WARNING, tr("Unclosed way"), 101 type, etype, code, Arrays.asList(w), 102 // The important parts of an unclosed way are the first and 103 // the last node which should be connected, therefore we highlight them 104 Arrays.asList(w.firstNode(), w.lastNode())); 105 } 106 return null; 107 } 108 109 protected boolean isValueErroneous(String value) { 110 return value != null && ignore != specialValues.contains(value); 111 } 112 } 113 114 /** 115 * A check performed by UnclosedWays test where the key is treated as boolean. 116 * @since 6390 117 */ 118 private final class UnclosedWaysBooleanCheck extends UnclosedWaysCheck { 119 120 /** 121 * Constructs a new {@code UnclosedWaysBooleanCheck}. 122 * @param code The unique numeric code for this check 123 * @param key The OSM key checked 124 * @param engMessage The English message 125 */ 126 public UnclosedWaysBooleanCheck(int code, String key, String engMessage) { 127 super(code, key, engMessage); 128 } 129 130 @Override 131 protected boolean isValueErroneous(String value) { 132 Boolean btest = OsmUtils.getOsmBoolean(value); 133 // Not a strict boolean comparison to handle building=house like a building=yes 134 return (btest != null && btest) || (btest == null && value != null); 135 } 136 } 137 138 private final UnclosedWaysCheck[] checks = { 139 new UnclosedWaysCheck(1101, "natural", marktr("natural type {0}"), Arrays.asList("coastline", "cliff", "tree_row", "ridge", "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}"), Arrays.asList("water_slide", "climbing")), 143 new UnclosedWaysCheck(1105, "tourism", marktr("tourism type {0}"), Arrays.asList("attraction")), 144 new UnclosedWaysCheck(1106, "shop", marktr("shop type {0}")), 145 new UnclosedWaysCheck(1107, "leisure", marktr("leisure type {0}"), Arrays.asList("track", "slipway")), 146 new UnclosedWaysCheck(1108, "waterway", marktr("waterway type {0}"), Arrays.asList("riverbank"), false), 147 new UnclosedWaysCheck(1109, "boundary", marktr("boundary type {0}")), 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<>(); 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}