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