001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.data.osm; 003 004import java.util.Arrays; 005import java.util.Collection; 006import java.util.HashSet; 007import java.util.Locale; 008import java.util.Map; 009import java.util.Set; 010 011import org.openstreetmap.josm.data.coor.LatLon; 012import org.openstreetmap.josm.tools.CheckParameterUtil; 013import org.openstreetmap.josm.tools.TextTagParser; 014 015/** 016 * Utility methods/constants that are useful for generic OSM tag handling. 017 */ 018public final class OsmUtils { 019 020 private static final Set<String> TRUE_VALUES = new HashSet<>(Arrays 021 .asList("true", "yes", "1", "on")); 022 private static final Set<String> FALSE_VALUES = new HashSet<>(Arrays 023 .asList("false", "no", "0", "off")); 024 private static final Set<String> REVERSE_VALUES = new HashSet<>(Arrays 025 .asList("reverse", "-1")); 026 027 /** 028 * A value that should be used to indicate true 029 * @since 12186 030 */ 031 public static final String TRUE_VALUE = "yes"; 032 /** 033 * A value that should be used to indicate false 034 * @since 12186 035 */ 036 public static final String FALSE_VALUE = "no"; 037 /** 038 * A value that should be used to indicate that a property applies reversed on the way 039 * @since 12186 040 */ 041 public static final String REVERSE_VALUE = "-1"; 042 043 /** 044 * Discouraged synonym for {@link #TRUE_VALUE} 045 */ 046 public static final String trueval = TRUE_VALUE; 047 /** 048 * Discouraged synonym for {@link #FALSE_VALUE} 049 */ 050 public static final String falseval = FALSE_VALUE; 051 /** 052 * Discouraged synonym for {@link #REVERSE_VALUE} 053 */ 054 public static final String reverseval = REVERSE_VALUE; 055 056 private OsmUtils() { 057 // Hide default constructor for utils classes 058 } 059 060 /** 061 * Converts a string to a boolean value 062 * @param value The string to convert 063 * @return {@link Boolean#TRUE} if that string represents a true value, 064 * {@link Boolean#FALSE} if it represents a false value, 065 * <code>null</code> otherwise. 066 */ 067 public static Boolean getOsmBoolean(String value) { 068 if (value == null) return null; 069 String lowerValue = value.toLowerCase(Locale.ENGLISH); 070 if (TRUE_VALUES.contains(lowerValue)) return Boolean.TRUE; 071 if (FALSE_VALUES.contains(lowerValue)) return Boolean.FALSE; 072 return null; 073 } 074 075 /** 076 * Normalizes the OSM boolean value 077 * @param value The tag value 078 * @return The best true/false value or the old value if the input cannot be converted. 079 * @see #TRUE_VALUE 080 * @see #FALSE_VALUE 081 */ 082 public static String getNamedOsmBoolean(String value) { 083 Boolean res = getOsmBoolean(value); 084 return res == null ? value : (res ? trueval : falseval); 085 } 086 087 /** 088 * Check if the value is a value indicating that a property applies reversed. 089 * @param value The value to check 090 * @return true if it is reversed. 091 */ 092 public static boolean isReversed(String value) { 093 return REVERSE_VALUES.contains(value); 094 } 095 096 /** 097 * Check if a tag value represents a boolean true value 098 * @param value The value to check 099 * @return true if it is a true value. 100 */ 101 public static boolean isTrue(String value) { 102 return TRUE_VALUES.contains(value); 103 } 104 105 /** 106 * Check if a tag value represents a boolean false value 107 * @param value The value to check 108 * @return true if it is a true value. 109 */ 110 public static boolean isFalse(String value) { 111 return FALSE_VALUES.contains(value); 112 } 113 114 /** 115 * Creates a new OSM primitive around (0,0) according to the given assertion. Originally written for unit tests, 116 * this can also be used in another places like validation of local MapCSS validator rules. 117 * Ways and relations created using this method are empty. 118 * @param assertion The assertion describing OSM primitive (ex: "way name=Foo railway=rail") 119 * @return a new OSM primitive according to the given assertion 120 * @throws IllegalArgumentException if assertion is null or if the primitive type cannot be deduced from it 121 * @since 7356 122 */ 123 public static OsmPrimitive createPrimitive(String assertion) { 124 return createPrimitive(assertion, LatLon.ZERO, false); 125 } 126 127 /** 128 * Creates a new OSM primitive according to the given assertion. Originally written for unit tests, 129 * this can also be used in another places like validation of local MapCSS validator rules. 130 * @param assertion The assertion describing OSM primitive (ex: "way name=Foo railway=rail") 131 * @param around the coordinate at which the primitive will be located 132 * @param enforceLocation if {@code true}, ways and relations will not be empty to force a physical location 133 * @return a new OSM primitive according to the given assertion 134 * @throws IllegalArgumentException if assertion is null or if the primitive type cannot be deduced from it 135 * @since 14486 136 */ 137 public static OsmPrimitive createPrimitive(String assertion, LatLon around, boolean enforceLocation) { 138 CheckParameterUtil.ensureParameterNotNull(assertion, "assertion"); 139 final String[] x = assertion.split("\\s+", 2); 140 final OsmPrimitive p = "n".equals(x[0]) || "node".equals(x[0]) 141 ? newNode(around) 142 : "w".equals(x[0]) || "way".equals(x[0]) || /*for MapCSS related usage*/ "area".equals(x[0]) 143 ? newWay(around, enforceLocation) 144 : "r".equals(x[0]) || "relation".equals(x[0]) 145 ? newRelation(around, enforceLocation) 146 : null; 147 if (p == null) { 148 throw new IllegalArgumentException("Expecting n/node/w/way/r/relation/area, but got '" + x[0] + '\''); 149 } 150 if (x.length > 1) { 151 for (final Map.Entry<String, String> i : TextTagParser.readTagsFromText(x[1]).entrySet()) { 152 p.put(i.getKey(), i.getValue()); 153 } 154 } 155 return p; 156 } 157 158 private static Node newNode(LatLon around) { 159 return new Node(around); 160 } 161 162 private static Way newWay(LatLon around, boolean enforceLocation) { 163 Way w = new Way(); 164 if (enforceLocation) { 165 w.addNode(newNode(new LatLon(around.lat()+0.1, around.lon()))); 166 w.addNode(newNode(new LatLon(around.lat()-0.1, around.lon()))); 167 } 168 return w; 169 } 170 171 private static Relation newRelation(LatLon around, boolean enforceLocation) { 172 Relation r = new Relation(); 173 if (enforceLocation) { 174 r.addMember(new RelationMember(null, newNode(around))); 175 } 176 return r; 177 } 178 179 /** 180 * Returns the layer value of primitive (null for layer 0). 181 * @param w OSM primitive 182 * @return the value of "layer" key, or null if absent or set to 0 (default value) 183 * @since 12986 184 * @since 13637 (signature) 185 */ 186 public static String getLayer(IPrimitive w) { 187 String layer1 = w.get("layer"); 188 if ("0".equals(layer1)) { 189 layer1 = null; // 0 is default value for layer. 190 } 191 return layer1; 192 } 193 194 /** 195 * Determines if the given collection contains primitives, and that none of them belong to a locked layer. 196 * @param collection collection of OSM primitives 197 * @return {@code true} if the given collection is not empty and does not contain any primitive in a locked layer. 198 * @since 13611 199 * @since 13957 (signature) 200 */ 201 public static boolean isOsmCollectionEditable(Collection<? extends IPrimitive> collection) { 202 if (collection == null || collection.isEmpty()) { 203 return false; 204 } 205 // see #16510: optimization: only consider the first primitive, as collection always refer to the same dataset 206 OsmData<?, ?, ?, ?> ds = collection.iterator().next().getDataSet(); 207 return ds == null || !ds.isLocked(); 208 } 209}