| 1 | // License: GPL. For details, see LICENSE file.
|
|---|
| 2 | package org.openstreetmap.josm.data.osm;
|
|---|
| 3 |
|
|---|
| 4 | import java.util.Collection;
|
|---|
| 5 | import java.util.Locale;
|
|---|
| 6 | import java.util.Map;
|
|---|
| 7 | import java.util.regex.Pattern;
|
|---|
| 8 | import java.util.stream.Stream;
|
|---|
| 9 |
|
|---|
| 10 | import org.openstreetmap.josm.data.coor.LatLon;
|
|---|
| 11 | import org.openstreetmap.josm.tools.CheckParameterUtil;
|
|---|
| 12 | import org.openstreetmap.josm.tools.TextTagParser;
|
|---|
| 13 | import org.openstreetmap.josm.tools.Utils;
|
|---|
| 14 |
|
|---|
| 15 | /**
|
|---|
| 16 | * Utility methods/constants that are useful for generic OSM tag handling.
|
|---|
| 17 | */
|
|---|
| 18 | public final class OsmUtils {
|
|---|
| 19 |
|
|---|
| 20 | /**
|
|---|
| 21 | * A value that should be used to indicate true
|
|---|
| 22 | * @since 12186
|
|---|
| 23 | */
|
|---|
| 24 | public static final String TRUE_VALUE = "yes";
|
|---|
| 25 | /**
|
|---|
| 26 | * A value that should be used to indicate false
|
|---|
| 27 | * @since 12186
|
|---|
| 28 | */
|
|---|
| 29 | public static final String FALSE_VALUE = "no";
|
|---|
| 30 | /**
|
|---|
| 31 | * A value that should be used to indicate that a property applies reversed on the way
|
|---|
| 32 | * @since 12186
|
|---|
| 33 | */
|
|---|
| 34 | public static final String REVERSE_VALUE = "-1";
|
|---|
| 35 |
|
|---|
| 36 | private OsmUtils() {
|
|---|
| 37 | // Hide default constructor for utils classes
|
|---|
| 38 | }
|
|---|
| 39 |
|
|---|
| 40 | /**
|
|---|
| 41 | * Converts a string to a boolean value
|
|---|
| 42 | * @param value The string to convert
|
|---|
| 43 | * @return {@link Boolean#TRUE} if that string represents a true value,<br>
|
|---|
| 44 | * {@link Boolean#FALSE} if it represents a false value,<br>
|
|---|
| 45 | * {@code null} otherwise.
|
|---|
| 46 | */
|
|---|
| 47 | public static Boolean getOsmBoolean(String value) {
|
|---|
| 48 | if (value == null) return null;
|
|---|
| 49 | String lowerValue = value.toLowerCase(Locale.ENGLISH);
|
|---|
| 50 | if (isTrue(lowerValue)) return Boolean.TRUE;
|
|---|
| 51 | if (isFalse(lowerValue)) return Boolean.FALSE;
|
|---|
| 52 | return null;
|
|---|
| 53 | }
|
|---|
| 54 |
|
|---|
| 55 | /**
|
|---|
| 56 | * Normalizes the OSM boolean value
|
|---|
| 57 | * @param value The tag value
|
|---|
| 58 | * @return The best true/false value or the old value if the input cannot be converted.
|
|---|
| 59 | * @see #TRUE_VALUE
|
|---|
| 60 | * @see #FALSE_VALUE
|
|---|
| 61 | */
|
|---|
| 62 | public static String getNamedOsmBoolean(String value) {
|
|---|
| 63 | Boolean res = getOsmBoolean(value);
|
|---|
| 64 | return res == null ? value : (res ? TRUE_VALUE : FALSE_VALUE);
|
|---|
| 65 | }
|
|---|
| 66 |
|
|---|
| 67 | /**
|
|---|
| 68 | * Check if the value is a value indicating that a property applies reversed.
|
|---|
| 69 | * @param value The value to check
|
|---|
| 70 | * @return true if it is reversed.
|
|---|
| 71 | */
|
|---|
| 72 | public static boolean isReversed(String value) {
|
|---|
| 73 | if (value == null) {
|
|---|
| 74 | return false;
|
|---|
| 75 | }
|
|---|
| 76 | switch (value) {
|
|---|
| 77 | case "reverse":
|
|---|
| 78 | case "-1":
|
|---|
| 79 | return true;
|
|---|
| 80 | default:
|
|---|
| 81 | return false;
|
|---|
| 82 | }
|
|---|
| 83 | }
|
|---|
| 84 |
|
|---|
| 85 | /**
|
|---|
| 86 | * Check if a tag value represents a boolean true value
|
|---|
| 87 | * @param value The value to check
|
|---|
| 88 | * @return true if it is a true value.
|
|---|
| 89 | */
|
|---|
| 90 | public static boolean isTrue(String value) {
|
|---|
| 91 | if (value == null) {
|
|---|
| 92 | return false;
|
|---|
| 93 | }
|
|---|
| 94 | switch (value) {
|
|---|
| 95 | case "true":
|
|---|
| 96 | case "yes":
|
|---|
| 97 | case "1":
|
|---|
| 98 | case "on":
|
|---|
| 99 | return true;
|
|---|
| 100 | default:
|
|---|
| 101 | return false;
|
|---|
| 102 | }
|
|---|
| 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 false value.
|
|---|
| 109 | */
|
|---|
| 110 | public static boolean isFalse(String value) {
|
|---|
| 111 | if (value == null) {
|
|---|
| 112 | return false;
|
|---|
| 113 | }
|
|---|
| 114 | switch (value) {
|
|---|
| 115 | case "false":
|
|---|
| 116 | case "no":
|
|---|
| 117 | case "0":
|
|---|
| 118 | case "off":
|
|---|
| 119 | return true;
|
|---|
| 120 | default:
|
|---|
| 121 | return false;
|
|---|
| 122 | }
|
|---|
| 123 | }
|
|---|
| 124 |
|
|---|
| 125 | /**
|
|---|
| 126 | * Creates a new OSM primitive around (0,0) according to the given assertion. Originally written for unit tests,
|
|---|
| 127 | * this can also be used in other places like validation of local MapCSS validator rules.
|
|---|
| 128 | * Ways and relations created using this method are empty.
|
|---|
| 129 | * @param assertion The assertion describing OSM primitive (ex: "way name=Foo railway=rail")
|
|---|
| 130 | * @return a new OSM primitive according to the given assertion
|
|---|
| 131 | * @throws IllegalArgumentException if assertion is null or if the primitive type cannot be deduced from it
|
|---|
| 132 | * @since 7356
|
|---|
| 133 | */
|
|---|
| 134 | public static OsmPrimitive createPrimitive(String assertion) {
|
|---|
| 135 | return createPrimitive(assertion, LatLon.ZERO, false);
|
|---|
| 136 | }
|
|---|
| 137 |
|
|---|
| 138 | /**
|
|---|
| 139 | * Creates a new OSM primitive according to the given assertion. Originally written for unit tests,
|
|---|
| 140 | * this can also be used in other places like validation of local MapCSS validator rules.
|
|---|
| 141 | * @param assertion The assertion describing OSM primitive (ex: "way name=Foo railway=rail")
|
|---|
| 142 | * @param around the coordinate at which the primitive will be located
|
|---|
| 143 | * @param enforceLocation if {@code true}, ways and relations will not be empty to force a physical location
|
|---|
| 144 | * @return a new OSM primitive according to the given assertion
|
|---|
| 145 | * @throws IllegalArgumentException if assertion is null or if the primitive type cannot be deduced from it
|
|---|
| 146 | * @since 14486
|
|---|
| 147 | */
|
|---|
| 148 | public static OsmPrimitive createPrimitive(String assertion, LatLon around, boolean enforceLocation) {
|
|---|
| 149 | CheckParameterUtil.ensureParameterNotNull(assertion, "assertion");
|
|---|
| 150 | final String[] x = assertion.split("\\s+", 2);
|
|---|
| 151 | final OsmPrimitive p = "n".equals(x[0]) || "node".equals(x[0])
|
|---|
| 152 | ? newNode(around)
|
|---|
| 153 | : "w".equals(x[0]) || "way".equals(x[0]) || /*for MapCSS related usage*/ "area".equals(x[0])
|
|---|
| 154 | ? newWay(around, enforceLocation)
|
|---|
| 155 | : "r".equals(x[0]) || "relation".equals(x[0])
|
|---|
| 156 | ? newRelation(around, enforceLocation)
|
|---|
| 157 | : null;
|
|---|
| 158 | if (p == null) {
|
|---|
| 159 | throw new IllegalArgumentException(
|
|---|
| 160 | "Expecting n/node/w/way/r/relation/area, but got '" + x[0] + "' for assertion '" + assertion + '\'');
|
|---|
| 161 | }
|
|---|
| 162 | if (x.length > 1) {
|
|---|
| 163 | for (final Map.Entry<String, String> i : TextTagParser.readTagsFromText(x[1]).entrySet()) {
|
|---|
| 164 | p.put(i.getKey(), i.getValue());
|
|---|
| 165 | }
|
|---|
| 166 | }
|
|---|
| 167 | return p;
|
|---|
| 168 | }
|
|---|
| 169 |
|
|---|
| 170 | private static Node newNode(LatLon around) {
|
|---|
| 171 | return new Node(around);
|
|---|
| 172 | }
|
|---|
| 173 |
|
|---|
| 174 | private static Way newWay(LatLon around, boolean enforceLocation) {
|
|---|
| 175 | Way w = new Way();
|
|---|
| 176 | if (enforceLocation) {
|
|---|
| 177 | w.addNode(newNode(new LatLon(around.lat()+0.1, around.lon())));
|
|---|
| 178 | w.addNode(newNode(new LatLon(around.lat()-0.1, around.lon())));
|
|---|
| 179 | }
|
|---|
| 180 | return w;
|
|---|
| 181 | }
|
|---|
| 182 |
|
|---|
| 183 | private static Relation newRelation(LatLon around, boolean enforceLocation) {
|
|---|
| 184 | Relation r = new Relation();
|
|---|
| 185 | if (enforceLocation) {
|
|---|
| 186 | r.addMember(new RelationMember(null, newNode(around)));
|
|---|
| 187 | }
|
|---|
| 188 | return r;
|
|---|
| 189 | }
|
|---|
| 190 |
|
|---|
| 191 | /**
|
|---|
| 192 | * Returns the layer value of primitive (null for layer 0).
|
|---|
| 193 | * @param w OSM primitive
|
|---|
| 194 | * @return the value of "layer" key, or null if absent or set to 0 (default value)
|
|---|
| 195 | * @since 12986
|
|---|
| 196 | * @since 13637 (signature)
|
|---|
| 197 | */
|
|---|
| 198 | public static String getLayer(IPrimitive w) {
|
|---|
| 199 | String layer1 = w.get("layer");
|
|---|
| 200 | if ("0".equals(layer1)) {
|
|---|
| 201 | layer1 = null; // 0 is default value for layer.
|
|---|
| 202 | }
|
|---|
| 203 | return layer1;
|
|---|
| 204 | }
|
|---|
| 205 |
|
|---|
| 206 | /**
|
|---|
| 207 | * Determines if the given collection contains primitives, and that none of them belong to a locked layer.
|
|---|
| 208 | * @param collection collection of OSM primitives
|
|---|
| 209 | * @return {@code true} if the given collection is not empty and does not contain any primitive in a locked layer.
|
|---|
| 210 | * @since 13611
|
|---|
| 211 | * @since 13957 (signature)
|
|---|
| 212 | */
|
|---|
| 213 | public static boolean isOsmCollectionEditable(Collection<? extends IPrimitive> collection) {
|
|---|
| 214 | if (Utils.isEmpty(collection)) {
|
|---|
| 215 | return false;
|
|---|
| 216 | }
|
|---|
| 217 | // see #16510: optimization: only consider the first primitive, as collection always refer to the same dataset
|
|---|
| 218 | OsmData<?, ?, ?, ?> ds = collection.iterator().next().getDataSet();
|
|---|
| 219 | return ds == null || !ds.isLocked();
|
|---|
| 220 | }
|
|---|
| 221 |
|
|---|
| 222 | /**
|
|---|
| 223 | * Splits a tag value by <a href="https://wiki.openstreetmap.org/wiki/Semi-colon_value_separator">semi-colon value separator</a>.
|
|---|
| 224 | * Spaces around the ; are ignored.
|
|---|
| 225 | *
|
|---|
| 226 | * @param value the value to separate
|
|---|
| 227 | * @return the separated values as Stream
|
|---|
| 228 | * @since 15671
|
|---|
| 229 | */
|
|---|
| 230 | public static Stream<String> splitMultipleValues(String value) {
|
|---|
| 231 | return Pattern.compile("\\s*;\\s*").splitAsStream(value);
|
|---|
| 232 | }
|
|---|
| 233 | }
|
|---|