source: josm/trunk/src/org/openstreetmap/josm/gui/mappaint/mapcss/Functions.java

Last change on this file was 18876, checked in by taylor.smock, 6 months ago

See #23238: Some MapCSS functions cause NPEs when part of fixAdd

JOSM_Search doesn't trigger the issue unless the search string is non-empty.

  • Property svn:eol-style set to native
File size: 48.9 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.mappaint.mapcss;
3
4import java.awt.Color;
5import java.nio.charset.StandardCharsets;
6import java.util.ArrayList;
7import java.util.Arrays;
8import java.util.Collection;
9import java.util.Collections;
10import java.util.List;
11import java.util.Locale;
12import java.util.Map.Entry;
13import java.util.Objects;
14import java.util.regex.Matcher;
15import java.util.regex.Pattern;
16import java.util.stream.Collectors;
17import java.util.zip.CRC32;
18
19import org.openstreetmap.josm.data.coor.ILatLon;
20import org.openstreetmap.josm.data.coor.LatLon;
21import org.openstreetmap.josm.data.gpx.GpxDistance;
22import org.openstreetmap.josm.data.osm.IPrimitive;
23import org.openstreetmap.josm.data.osm.Node;
24import org.openstreetmap.josm.data.osm.OsmPrimitive;
25import org.openstreetmap.josm.data.osm.PrimitiveId;
26import org.openstreetmap.josm.data.osm.Relation;
27import org.openstreetmap.josm.data.osm.SimplePrimitiveId;
28import org.openstreetmap.josm.data.osm.Way;
29import org.openstreetmap.josm.data.osm.search.SearchCompiler;
30import org.openstreetmap.josm.data.osm.search.SearchCompiler.Match;
31import org.openstreetmap.josm.data.osm.search.SearchParseError;
32import org.openstreetmap.josm.data.preferences.NamedColorProperty;
33import org.openstreetmap.josm.gui.MainApplication;
34import org.openstreetmap.josm.gui.mappaint.Cascade;
35import org.openstreetmap.josm.gui.mappaint.Environment;
36import org.openstreetmap.josm.gui.mappaint.MapPaintStyles;
37import org.openstreetmap.josm.gui.mappaint.mapcss.ExpressionFactory.NullableArguments;
38import org.openstreetmap.josm.io.XmlWriter;
39import org.openstreetmap.josm.tools.AlphanumComparator;
40import org.openstreetmap.josm.tools.ColorHelper;
41import org.openstreetmap.josm.tools.Geometry;
42import org.openstreetmap.josm.tools.Logging;
43import org.openstreetmap.josm.tools.RightAndLefthandTraffic;
44import org.openstreetmap.josm.tools.RotationAngle;
45import org.openstreetmap.josm.tools.RotationAngle.WayDirectionRotationAngle;
46import org.openstreetmap.josm.tools.StreamUtils;
47import org.openstreetmap.josm.tools.Territories;
48import org.openstreetmap.josm.tools.Utils;
49
50/**
51 * List of functions that can be used in MapCSS expressions.
52 * <p>
53 * First parameter can be of type {@link Environment} (if needed). This is
54 * automatically filled in by JOSM and the user only sees the remaining arguments.
55 * When one of the user supplied arguments cannot be converted the
56 * expected type or is null, the function is not called and it returns null
57 * immediately. Add the annotation {@link NullableArguments} to allow null arguments.
58 * Every method must be static.
59 *
60 * @since 15245 (extracted from {@link ExpressionFactory})
61 */
62@SuppressWarnings({"UnusedDeclaration", "squid:S100"})
63public final class Functions {
64
65 private Functions() {
66 // Hide implicit public constructor for utility classes
67 }
68
69 /**
70 * Identity function for compatibility with MapCSS specification.
71 * @param o any object
72 * @return {@code o} unchanged
73 */
74 public static Object eval(Object o) {
75 return o;
76 }
77
78 /**
79 * Function associated to the numeric "+" operator.
80 * @param a the first operand
81 * @param b the second operand
82 * @return Sum of arguments
83 * @see Float#sum
84 */
85 public static double plus(double a, double b) {
86 return a + b;
87 }
88
89 /**
90 * Function associated to the numeric "-" operator.
91 * @param a the first operand
92 * @param b the second operand
93 * @return Subtraction of arguments
94 */
95 public static double minus(double a, double b) {
96 return a - b;
97 }
98
99 /**
100 * Function associated to the numeric "*" operator.
101 * @param a the first operand
102 * @param b the second operand
103 * @return Multiplication of arguments
104 */
105 public static double times(double a, double b) {
106 return a * b;
107 }
108
109 /**
110 * Function associated to the numeric "/" operator.
111 * @param a the first operand
112 * @param b the second operand
113 * @return Division of arguments
114 */
115 public static double divided_by(double a, double b) {
116 return a / b;
117 }
118
119 /**
120 * Function associated to the math modulo "%" operator.
121 * @param a first value
122 * @param b second value
123 * @return {@code a mod b}, e.g., {@code mod(7, 5) = 2}
124 */
125 public static float mod(float a, float b) {
126 return a % b;
127 }
128
129 /**
130 * Creates a list of values, e.g., for the {@code dashes} property.
131 * @param ignored The environment (ignored)
132 * @param args The values to put in a list
133 * @return list of values
134 * @see Arrays#asList(Object[])
135 */
136 public static List<Object> list(Environment ignored, Object... args) {
137 return Arrays.asList(args);
138 }
139
140 /**
141 * Returns the number of elements in a list.
142 * @param lst the list
143 * @return length of the list
144 */
145 public static Integer count(List<?> lst) {
146 return lst.size();
147 }
148
149 /**
150 * Returns the first non-null object.
151 * The name originates from <a href="http://wiki.openstreetmap.org/wiki/MapCSS/0.2/eval">MapCSS standard</a>.
152 * @param ignored The environment (ignored)
153 * @param args arguments
154 * @return the first non-null object
155 * @see Utils#firstNonNull(Object[])
156 */
157 @NullableArguments
158 public static Object any(Environment ignored, Object... args) {
159 return Utils.firstNonNull(args);
160 }
161
162 /**
163 * Get the {@code n}th element of the list {@code lst} (counting starts at 0).
164 * @param lst list
165 * @param n index
166 * @return {@code n}th element of the list, or {@code null} if index out of range
167 * @since 5699
168 */
169 public static Object get(List<?> lst, float n) {
170 int idx = Math.round(n);
171 if (idx >= 0 && idx < lst.size()) {
172 return lst.get(idx);
173 }
174 return null;
175 }
176
177 /**
178 * Splits string {@code toSplit} at occurrences of the separator string {@code sep} and returns a list of matches.
179 * @param sep separator string
180 * @param toSplit string to split
181 * @return list of matches
182 * @see String#split(String)
183 * @since 5699
184 */
185 public static List<String> split(String sep, String toSplit) {
186 return Arrays.asList(toSplit.split(Pattern.quote(sep), -1));
187 }
188
189 /**
190 * Creates a color value with the specified amounts of {@code r}ed, {@code g}reen, {@code b}lue (arguments from 0.0 to 1.0)
191 * @param r the red component
192 * @param g the green component
193 * @param b the blue component
194 * @return color matching the given components
195 * @see Color#Color(float, float, float)
196 */
197 public static Color rgb(float r, float g, float b) {
198 try {
199 return new Color(r, g, b);
200 } catch (IllegalArgumentException e) {
201 Logging.trace(e);
202 return null;
203 }
204 }
205
206 /**
207 * Creates a color value with the specified amounts of {@code r}ed, {@code g}reen, {@code b}lue, {@code alpha}
208 * (arguments from 0.0 to 1.0)
209 * @param r the red component
210 * @param g the green component
211 * @param b the blue component
212 * @param alpha the alpha component
213 * @return color matching the given components
214 * @see Color#Color(float, float, float, float)
215 */
216 public static Color rgba(float r, float g, float b, float alpha) {
217 try {
218 return new Color(r, g, b, alpha);
219 } catch (IllegalArgumentException e) {
220 Logging.trace(e);
221 return null;
222 }
223 }
224
225 /**
226 * Create color from hsb color model. (arguments form 0.0 to 1.0)
227 * @param h hue
228 * @param s saturation
229 * @param b brightness
230 * @return the corresponding color
231 */
232 public static Color hsb_color(float h, float s, float b) {
233 try {
234 return Color.getHSBColor(h, s, b);
235 } catch (IllegalArgumentException e) {
236 Logging.trace(e);
237 return null;
238 }
239 }
240
241 /**
242 * Creates a color value from an HTML notation, i.e., {@code #rrggbb}.
243 * @param html HTML notation
244 * @return color matching the given notation
245 */
246 public static Color html2color(String html) {
247 return ColorHelper.html2color(html);
248 }
249
250 /**
251 * Computes the HTML notation ({@code #rrggbb}) for a color value).
252 * @param c color
253 * @return HTML notation matching the given color
254 */
255 public static String color2html(Color c) {
256 return ColorHelper.color2html(c);
257 }
258
259 /**
260 * Get the value of the red color channel in the rgb color model
261 * @param c color
262 * @return the red color channel in the range [0;1]
263 * @see java.awt.Color#getRed()
264 */
265 public static float red(Color c) {
266 return ColorHelper.int2float(c.getRed());
267 }
268
269 /**
270 * Get the value of the green color channel in the rgb color model
271 * @param c color
272 * @return the green color channel in the range [0;1]
273 * @see java.awt.Color#getGreen()
274 */
275 public static float green(Color c) {
276 return ColorHelper.int2float(c.getGreen());
277 }
278
279 /**
280 * Get the value of the blue color channel in the rgb color model
281 * @param c color
282 * @return the blue color channel in the range [0;1]
283 * @see java.awt.Color#getBlue()
284 */
285 public static float blue(Color c) {
286 return ColorHelper.int2float(c.getBlue());
287 }
288
289 /**
290 * Get the value of the alpha channel in the rgba color model
291 * @param c color
292 * @return the alpha channel in the range [0;1]
293 * @see java.awt.Color#getAlpha()
294 */
295 public static float alpha(Color c) {
296 return ColorHelper.int2float(c.getAlpha());
297 }
298
299 /**
300 * Assembles the strings to one.
301 * @param ignored The environment (ignored)
302 * @param args arguments
303 * @return assembled string
304 * @see Collectors#joining
305 */
306 @NullableArguments
307 public static String concat(Environment ignored, Object... args) {
308 return Arrays.stream(args)
309 .filter(Objects::nonNull)
310 .map(String::valueOf)
311 .collect(Collectors.joining());
312 }
313
314 /**
315 * Assembles the strings to one, where the first entry is used as separator.
316 * @param ignored The environment (ignored)
317 * @param args arguments. First one is used as separator
318 * @return assembled string
319 * @see String#join(CharSequence, CharSequence...)
320 */
321 @NullableArguments
322 public static String join(Environment ignored, String... args) {
323 return String.join(args[0], Arrays.asList(args).subList(1, args.length));
324 }
325
326 /**
327 * Joins a list of {@code values} into a single string with fields separated by {@code separator}.
328 * @param separator the separator
329 * @param values collection of objects
330 * @return assembled string
331 * @see String#join(CharSequence, Iterable)
332 */
333 public static String join_list(final String separator, final List<String> values) {
334 return String.join(separator, values);
335 }
336
337 /**
338 * Returns the value of the property {@code key}, e.g., {@code prop("width")}.
339 * @param env the environment
340 * @param key the property key
341 * @return the property value
342 */
343 public static Object prop(final Environment env, String key) {
344 return prop(env, key, null);
345 }
346
347 /**
348 * Returns the value of the property {@code key} from layer {@code layer}.
349 * @param env the environment
350 * @param key the property key
351 * @param layer layer
352 * @return the property value
353 */
354 public static Object prop(final Environment env, String key, String layer) {
355 return env.getCascade(layer).get(key);
356 }
357
358 /**
359 * Determines whether property {@code key} is set.
360 * @param env the environment
361 * @param key the property key
362 * @return {@code true} if the property is set, {@code false} otherwise
363 */
364 public static Boolean is_prop_set(final Environment env, String key) {
365 return is_prop_set(env, key, null);
366 }
367
368 /**
369 * Determines whether property {@code key} is set on layer {@code layer}.
370 * @param env the environment
371 * @param key the property key
372 * @param layer layer
373 * @return {@code true} if the property is set, {@code false} otherwise
374 */
375 public static Boolean is_prop_set(final Environment env, String key, String layer) {
376 return env.getCascade(layer).containsKey(key);
377 }
378
379 /**
380 * Gets the value of the key {@code key} from the object in question.
381 * @param env the environment
382 * @param key the OSM key
383 * @return the value for given key
384 */
385 public static String tag(final Environment env, String key) {
386 return env.osm == null ? null : env.osm.get(key);
387 }
388
389 /**
390 * Get keys that follow a regex
391 * @param env the environment
392 * @param keyRegex the pattern that the key must match
393 * @return the values for the keys that match the pattern
394 * @see Functions#tag_regex(Environment, String, String)
395 * @since 15315
396 */
397 public static List<String> tag_regex(final Environment env, String keyRegex) {
398 return tag_regex(env, keyRegex, "");
399 }
400
401 /**
402 * Get keys that follow a regex
403 * @param env the environment
404 * @param keyRegex the pattern that the key must match
405 * @param flags a string that may contain "i" (case insensitive), "m" (multiline) and "s" ("dot all")
406 * @return the values for the keys that match the pattern
407 * @see Pattern#CASE_INSENSITIVE
408 * @see Pattern#DOTALL
409 * @see Pattern#MULTILINE
410 * @since 15315
411 */
412 public static List<String> tag_regex(final Environment env, String keyRegex, String flags) {
413 if (env.osm == null) {
414 return Collections.emptyList();
415 }
416 int f = parse_regex_flags(flags);
417 Pattern compiled = Pattern.compile(keyRegex, f);
418 return env.osm.getKeys().entrySet().stream()
419 .filter(object -> compiled.matcher(object.getKey()).find())
420 .map(Entry::getValue).collect(Collectors.toList());
421 }
422
423 /**
424 * Parse flags for regex usage. Shouldn't be used in mapcss
425 * @param flags a string that may contain "i" (case insensitive), "m" (multiline) and "s" ("dot all")
426 * @return An int that can be used by a {@link Pattern} object
427 * @see Pattern#CASE_INSENSITIVE
428 * @see Pattern#DOTALL
429 * @see Pattern#MULTILINE
430 */
431 private static int parse_regex_flags(String flags) {
432 int f = 0;
433 if (flags.contains("i")) {
434 f |= Pattern.CASE_INSENSITIVE;
435 }
436 if (flags.contains("s")) {
437 f |= Pattern.DOTALL;
438 }
439 if (flags.contains("m")) {
440 f |= Pattern.MULTILINE;
441 }
442 return f;
443 }
444
445 /**
446 * Gets the first non-null value of the key {@code key} from the object's parent(s).
447 * @param env the environment
448 * @param key the OSM key
449 * @return first non-null value of the key {@code key} from the object's parent(s)
450 */
451 public static String parent_tag(final Environment env, String key) {
452 if (env.parent == null) {
453 if (env.osm != null) {
454 // we don't have a matched parent, so just search all referrers
455 return env.osm.getReferrers().stream()
456 .map(parent -> parent.get(key))
457 .filter(Objects::nonNull)
458 .findFirst().orElse(null);
459 }
460 return null;
461 }
462 return env.parent.get(key);
463 }
464
465 /**
466 * Gets a list of all non-null values of the key {@code key} from the object's parent(s).
467 * <p>
468 * The values are sorted according to {@link AlphanumComparator}.
469 * @param env the environment
470 * @param key the OSM key
471 * @return a list of non-null values of the key {@code key} from the object's parent(s)
472 */
473 public static List<String> parent_tags(final Environment env, String key) {
474 if (env.parent == null) {
475 if (env.osm != null) {
476 // we don't have a matched parent, so just search all referrers
477 return env.osm.getReferrers().stream().map(parent -> parent.get(key))
478 .filter(Objects::nonNull)
479 .distinct()
480 .sorted(AlphanumComparator.getInstance())
481 .collect(StreamUtils.toUnmodifiableList());
482 }
483 return Collections.emptyList();
484 }
485 return Collections.singletonList(env.parent.get(key));
486 }
487
488 /**
489 * Get the rotation angle of the preceding parent way segment at the node location.
490 * If there is no preceding parent way segment, the following way segment is used instead.
491 * Requires a parent way object matched via
492 * <a href="https://josm.openstreetmap.de/wiki/Help/Styles/MapCSSImplementation#LinkSelector">child selector</a>.
493 *
494 * @param env the environment
495 * @return the rotation angle of the parent way segment at the node in radians,
496 * otherwise null if there is no matching parent way or the object is not a node
497 * @since 18664
498 */
499 public static Double parent_way_angle(final Environment env) {
500 if (env.osm instanceof Node && env.parent instanceof Way) {
501 return WayDirectionRotationAngle.getRotationAngleForNodeOnWay((Node) env.osm, (Way) env.parent);
502 }
503 return null;
504 }
505
506 /**
507 * Gets the value of the key {@code key} from the object's child.
508 * @param env the environment
509 * @param key the OSM key
510 * @return the value of the key {@code key} from the object's child, or {@code null} if there is no child
511 */
512 public static String child_tag(final Environment env, String key) {
513 return env.child == null ? null : env.child.get(key);
514 }
515
516 /**
517 * Returns the OSM id of the object's parent.
518 * <p>
519 * Parent must be matched by child selector.
520 * @param env the environment
521 * @return the OSM id of the object's parent, if available, or {@code null}
522 * @see IPrimitive#getUniqueId()
523 */
524 public static Long parent_osm_id(final Environment env) {
525 return env.parent == null ? null : env.parent.getUniqueId();
526 }
527
528 /**
529 * Gets a list of all OSM id's of the object's parent(s) with a specified key.
530 *
531 * @param env the environment
532 * @param key the OSM key
533 * @param keyValue the regex value of the OSM key
534 * @return a list of non-null values of the OSM id's from the object's parent(s)
535 * @since 18829
536 */
537 @NullableArguments
538 public static List<IPrimitive> parent_osm_primitives(final Environment env, String key, String keyValue) {
539 if (env.parent == null) {
540 if (env.osm != null) {
541 final ArrayList<IPrimitive> parents = new ArrayList<>();
542 for (IPrimitive parent : env.osm.getReferrers()) {
543 if ((key == null || parent.get(key) != null)
544 && (keyValue == null || regexp_test(keyValue, parent.get(key)))) {
545 parents.add(parent);
546 }
547 }
548 return Collections.unmodifiableList(parents);
549 }
550 return Collections.emptyList();
551 }
552 return Collections.singletonList(env.parent);
553 }
554
555 /**
556 * Gets a list of all OSM id's of the object's parent(s) with a specified key.
557 *
558 * @param env the environment
559 * @param key the OSM key
560 * @return a list of non-null values of the OSM id's from the object's parent(s)
561 * @since 18829
562 */
563 @NullableArguments
564 public static List<IPrimitive> parent_osm_primitives(final Environment env, String key) {
565 return parent_osm_primitives(env, key, null);
566 }
567
568 /**
569 * Gets a list of all OSM id's of the object's parent(s).
570 *
571 * @param env the environment
572 * @return a list of non-null values of the OSM id's from the object's parent(s)
573 * @since 18829
574 */
575 public static List<IPrimitive> parent_osm_primitives(final Environment env) {
576 return parent_osm_primitives(env, null, null);
577 }
578
579 /**
580 * Convert Primitives to a string
581 *
582 * @param primitives The primitives to convert
583 * @return A list of strings in the format type + id (in the list order)
584 * @see SimplePrimitiveId#toSimpleId
585 * @since 18829
586 */
587 public static List<String> convert_primitives_to_string(Iterable<PrimitiveId> primitives) {
588 final List<String> primitiveStrings = new ArrayList<>(primitives instanceof Collection ?
589 ((Collection<?>) primitives).size() : 0);
590 for (PrimitiveId primitive : primitives) {
591 primitiveStrings.add(convert_primitive_to_string(primitive));
592 }
593 return primitiveStrings;
594 }
595
596 /**
597 * Convert a primitive to a string
598 *
599 * @param primitive The primitive to convert
600 * @return A string in the format type + id
601 * @see SimplePrimitiveId#toSimpleId
602 * @since 18829
603 */
604 public static String convert_primitive_to_string(PrimitiveId primitive) {
605 return SimplePrimitiveId.toSimpleId(primitive);
606 }
607
608 /**
609 * Returns the lowest distance between the OSM object and a GPX point
610 * <p>
611 * @param env the environment
612 * @return the distance between the object and the closest gpx point or {@code Double.MAX_VALUE}
613 * @since 14802
614 */
615 public static double gpx_distance(final Environment env) {
616 if (env.osm instanceof OsmPrimitive) {
617 return MainApplication.getLayerManager().getAllGpxData().stream()
618 .mapToDouble(gpx -> GpxDistance.getLowestDistance((OsmPrimitive) env.osm, gpx))
619 .min().orElse(Double.MAX_VALUE);
620 }
621 return Double.MAX_VALUE;
622 }
623
624 /**
625 * Determines whether the object has a tag with the given key.
626 * @param env the environment
627 * @param key the OSM key
628 * @return {@code true} if the object has a tag with the given key, {@code false} otherwise
629 */
630 public static boolean has_tag_key(final Environment env, String key) {
631 return env.osm != null && env.osm.hasKey(key);
632 }
633
634 /**
635 * Returns the index of node in parent way or member in parent relation.
636 * @param env the environment
637 * @return the index as float. Starts at 1
638 */
639 public static Float index(final Environment env) {
640 if (env.index == null) {
641 return null;
642 }
643 return env.index + 1f;
644 }
645
646 /**
647 * Sort an array of strings
648 * @param ignored The environment (ignored)
649 * @param sortables The array to sort
650 * @return The sorted list
651 * @since 15279
652 */
653 public static List<String> sort(Environment ignored, String... sortables) {
654 Arrays.parallelSort(sortables);
655 return Arrays.asList(sortables);
656 }
657
658 /**
659 * Sort a list of strings
660 * @param sortables The list to sort
661 * @return The sorted list
662 * @since 15279
663 */
664 public static List<String> sort_list(List<String> sortables) {
665 Collections.sort(sortables);
666 return sortables;
667 }
668
669 /**
670 * Get unique values
671 * @param ignored The environment (ignored)
672 * @param values A list of values that may have duplicates
673 * @return A list with no duplicates
674 * @since 15323
675 */
676 public static List<String> uniq(Environment ignored, String... values) {
677 return uniq_list(Arrays.asList(values));
678 }
679
680 /**
681 * Get unique values
682 * @param values A list of values that may have duplicates
683 * @return A list with no duplicates
684 * @since 15323
685 */
686 public static List<String> uniq_list(List<String> values) {
687 return values.stream().distinct().collect(Collectors.toList());
688 }
689
690 /**
691 * Returns the role of current object in parent relation, or role of child if current object is a relation.
692 * @param env the environment
693 * @return role of current object in parent relation, or role of child if current object is a relation
694 * @see Environment#getRole()
695 */
696 public static String role(final Environment env) {
697 return env.getRole();
698 }
699
700 /**
701 * Returns the number of primitives in a relation with the specified roles.
702 * @param env the environment
703 * @param roles The roles to count in the relation
704 * @return The number of relation members with the specified role
705 * @since 15196
706 */
707 public static int count_roles(final Environment env, String... roles) {
708 int rValue = 0;
709 if (env.osm instanceof Relation) {
710 List<String> roleList = Arrays.asList(roles);
711 Relation rel = (Relation) env.osm;
712 rValue = (int) rel.getMembers().stream()
713 .filter(member -> roleList.contains(member.getRole()))
714 .count();
715 }
716 return rValue;
717 }
718
719 /**
720 * Returns the area of a closed way or multipolygon in square meters or {@code null}.
721 * @param env the environment
722 * @return the area of a closed way or multipolygon in square meters or {@code null}
723 * @see Geometry#computeArea(IPrimitive)
724 */
725 public static Float areasize(final Environment env) {
726 final Double area = Geometry.computeArea(env.osm);
727 return area == null ? null : area.floatValue();
728 }
729
730 /**
731 * Returns the length of the way in metres or {@code null}.
732 * @param env the environment
733 * @return the length of the way in metres or {@code null}.
734 * @see Way#getLength()
735 */
736 public static Float waylength(final Environment env) {
737 if (env.osm instanceof Way) {
738 return (float) ((Way) env.osm).getLength();
739 } else {
740 return null;
741 }
742 }
743
744 /**
745 * Function associated to the logical "!" operator.
746 * @param b boolean value
747 * @return {@code true} if {@code !b}
748 */
749 public static boolean not(boolean b) {
750 return !b;
751 }
752
753 /**
754 * Function associated to the logical "&gt;=" operator.
755 * @param a first value
756 * @param b second value
757 * @return {@code true} if {@code a >= b}
758 */
759 public static boolean greater_equal(float a, float b) {
760 return a >= b;
761 }
762
763 /**
764 * Function associated to the logical "&lt;=" operator.
765 * @param a first value
766 * @param b second value
767 * @return {@code true} if {@code a <= b}
768 */
769 public static boolean less_equal(float a, float b) {
770 return a <= b;
771 }
772
773 /**
774 * Function associated to the logical "&gt;" operator.
775 * @param a first value
776 * @param b second value
777 * @return {@code true} if {@code a > b}
778 */
779 public static boolean greater(float a, float b) {
780 return a > b;
781 }
782
783 /**
784 * Function associated to the logical "&lt;" operator.
785 * @param a first value
786 * @param b second value
787 * @return {@code true} if {@code a < b}
788 */
789 public static boolean less(float a, float b) {
790 return a < b;
791 }
792
793 /**
794 * Converts an angle in degrees to radians.
795 * @param degree the angle in degrees
796 * @return the angle in radians
797 * @see Math#toRadians(double)
798 */
799 public static double degree_to_radians(double degree) {
800 return Utils.toRadians(degree);
801 }
802
803 /**
804 * Converts an angle diven in cardinal directions to radians.
805 * The following values are supported: {@code n}, {@code north}, {@code ne}, {@code northeast},
806 * {@code e}, {@code east}, {@code se}, {@code southeast}, {@code s}, {@code south},
807 * {@code sw}, {@code southwest}, {@code w}, {@code west}, {@code nw}, {@code northwest}.
808 * @param cardinal the angle in cardinal directions.
809 * @return the angle in radians
810 * @see RotationAngle#parseCardinalRotation(String)
811 */
812 public static Double cardinal_to_radians(String cardinal) {
813 try {
814 return RotationAngle.parseCardinalRotation(cardinal);
815 } catch (IllegalArgumentException illegalArgumentException) {
816 Logging.trace(illegalArgumentException);
817 return null;
818 }
819 }
820
821 /**
822 * Determines if the objects {@code a} and {@code b} are equal.
823 * @param a First object
824 * @param b Second object
825 * @return {@code true} if objects are equal, {@code false} otherwise
826 * @see Object#equals(Object)
827 */
828 @SuppressWarnings("squid:S1221")
829 public static boolean equal(Object a, Object b) {
830 if (a.getClass() == b.getClass()) return a.equals(b);
831 if (a.equals(Cascade.convertTo(b, a.getClass()))) return true;
832 return b.equals(Cascade.convertTo(a, b.getClass()));
833 }
834
835 /**
836 * Determines if the objects {@code a} and {@code b} are not equal.
837 * @param a First object
838 * @param b Second object
839 * @return {@code false} if objects are equal, {@code true} otherwise
840 * @see Object#equals(Object)
841 */
842 public static boolean not_equal(Object a, Object b) {
843 return !equal(a, b);
844 }
845
846 /**
847 * Determines whether the JOSM search with {@code searchStr} applies to the object.
848 * @param env the environment
849 * @param searchStr the search string
850 * @return {@code true} if the JOSM search with {@code searchStr} applies to the object
851 * @see SearchCompiler
852 */
853 public static Boolean JOSM_search(final Environment env, String searchStr) {
854 if (env.osm == null) {
855 return null;
856 }
857 Match m;
858 try {
859 m = SearchCompiler.compile(searchStr);
860 } catch (SearchParseError ex) {
861 Logging.trace(ex);
862 return null;
863 }
864 return m.match(env.osm);
865 }
866
867 /**
868 * Obtains the JOSM key {@link org.openstreetmap.josm.data.Preferences} string for key {@code key},
869 * and defaults to {@code def} if that is null.
870 * <p>
871 * If the default value can be {@linkplain Cascade#convertTo converted} to a {@link Color},
872 * the {@link NamedColorProperty} is retrieved as string.
873 *
874 * @param env the environment
875 * @param key Key in JOSM preference
876 * @param def Default value
877 * @return value for key, or default value if not found
878 */
879 public static String JOSM_pref(Environment env, String key, String def) {
880 return MapPaintStyles.getStyles().getPreferenceCached(env != null ? env.source : null, key, def);
881 }
882
883 /**
884 * Tests if string {@code target} matches pattern {@code pattern}
885 * @param pattern The regex expression
886 * @param target The character sequence to be matched
887 * @return {@code true} if, and only if, the entire region sequence matches the pattern
888 * @see Pattern#matches(String, CharSequence)
889 * @since 5699
890 */
891 public static boolean regexp_test(String pattern, String target) {
892 return Pattern.matches(pattern, target);
893 }
894
895 /**
896 * Tests if string {@code target} matches pattern {@code pattern}
897 * @param pattern The regex expression
898 * @param target The character sequence to be matched
899 * @param flags a string that may contain "i" (case insensitive), "m" (multiline) and "s" ("dot all")
900 * @return {@code true} if, and only if, the entire region sequence matches the pattern
901 * @see Pattern#CASE_INSENSITIVE
902 * @see Pattern#DOTALL
903 * @see Pattern#MULTILINE
904 * @since 5699
905 */
906 public static boolean regexp_test(String pattern, String target, String flags) {
907 int f = parse_regex_flags(flags);
908 return Pattern.compile(pattern, f).matcher(target).matches();
909 }
910
911 /**
912 * Tries to match string against pattern regexp and returns a list of capture groups in case of success.
913 * The first element (index 0) is the complete match (i.e. string).
914 * Further elements correspond to the bracketed parts of the regular expression.
915 * @param pattern The regex expression
916 * @param target The character sequence to be matched
917 * @param flags a string that may contain "i" (case insensitive), "m" (multiline) and "s" ("dot all")
918 * @return a list of capture groups if {@link Matcher#matches()}, or {@code null}.
919 * @see Pattern#CASE_INSENSITIVE
920 * @see Pattern#DOTALL
921 * @see Pattern#MULTILINE
922 * @since 5701
923 */
924 public static List<String> regexp_match(String pattern, String target, String flags) {
925 int f = parse_regex_flags(flags);
926 return Utils.getMatches(Pattern.compile(pattern, f).matcher(target));
927 }
928
929 /**
930 * Tries to match string against pattern regexp and returns a list of capture groups in case of success.
931 * The first element (index 0) is the complete match (i.e. string).
932 * Further elements correspond to the bracketed parts of the regular expression.
933 * @param pattern The regex expression
934 * @param target The character sequence to be matched
935 * @return a list of capture groups if {@link Matcher#matches()}, or {@code null}.
936 * @since 5701
937 */
938 public static List<String> regexp_match(String pattern, String target) {
939 return Utils.getMatches(Pattern.compile(pattern).matcher(target));
940 }
941
942 /**
943 * Returns the OSM id of the current object.
944 * @param env the environment
945 * @return the OSM id of the current object
946 * @see IPrimitive#getUniqueId()
947 */
948 public static long osm_id(final Environment env) {
949 return env.osm != null ? env.osm.getUniqueId() : 0;
950 }
951
952 /**
953 * Returns the OSM user name who last touched the current object.
954 * @param env the environment
955 * @return the OSM user name who last touched the current object
956 * @see IPrimitive#getUser
957 * @since 15246
958 */
959 public static String osm_user_name(final Environment env) {
960 return env.osm != null ? env.osm.getUser().getName() : null;
961 }
962
963 /**
964 * Returns the OSM user id who last touched the current object.
965 * @param env the environment
966 * @return the OSM user id who last touched the current object
967 * @see IPrimitive#getUser
968 * @since 15246
969 */
970 public static long osm_user_id(final Environment env) {
971 return env.osm != null ? env.osm.getUser().getId() : 0;
972 }
973
974 /**
975 * Returns the version number of the current object.
976 * @param env the environment
977 * @return the version number of the current object
978 * @see IPrimitive#getVersion
979 * @since 15246
980 */
981 public static int osm_version(final Environment env) {
982 return env.osm != null ? env.osm.getVersion() : 0;
983 }
984
985 /**
986 * Returns the id of the changeset the current object was last uploaded to.
987 * @param env the environment
988 * @return the id of the changeset the current object was last uploaded to
989 * @see IPrimitive#getChangesetId
990 * @since 15246
991 */
992 public static int osm_changeset_id(final Environment env) {
993 return env.osm != null ? env.osm.getChangesetId() : 0;
994 }
995
996 /**
997 * Returns the time of last modification to the current object, as timestamp.
998 * @param env the environment
999 * @return the time of last modification to the current object, as timestamp
1000 * @see IPrimitive#getRawTimestamp
1001 * @since 15246
1002 */
1003 public static int osm_timestamp(final Environment env) {
1004 return env.osm != null ? env.osm.getRawTimestamp() : 0;
1005 }
1006
1007 /**
1008 * Translates some text for the current locale. The first argument is the text to translate,
1009 * and the subsequent arguments are parameters for the string indicated by <code>{0}</code>, <code>{1}</code>, …
1010 * @param ignored The environment (ignored)
1011 * @param args arguments
1012 * @return the translated string
1013 */
1014 @NullableArguments
1015 public static String tr(Environment ignored, String... args) {
1016 final String text = args[0];
1017 System.arraycopy(args, 1, args, 0, args.length - 1);
1018 return org.openstreetmap.josm.tools.I18n.tr(text, (Object[]) args);
1019 }
1020
1021 /**
1022 * Returns the substring of {@code s} starting at index {@code begin} (inclusive, 0-indexed).
1023 * @param s The base string
1024 * @param begin The start index
1025 * @return the substring
1026 * @see String#substring(int)
1027 */
1028 public static String substring(String s, /* due to missing Cascade.convertTo for int*/ float begin) {
1029 return s == null ? null : s.substring((int) begin);
1030 }
1031
1032 /**
1033 * Returns the substring of {@code s} starting at index {@code begin} (inclusive)
1034 * and ending at index {@code end}, (exclusive, 0-indexed).
1035 * @param s The base string
1036 * @param begin The start index
1037 * @param end The end index
1038 * @return the substring
1039 * @see String#substring(int, int)
1040 */
1041 public static String substring(String s, float begin, float end) {
1042 return s == null ? null : s.substring((int) begin, (int) end);
1043 }
1044
1045 /**
1046 * Replaces in {@code s} every {@code} target} substring by {@code replacement}.
1047 * @param s The source string
1048 * @param target The sequence of char values to be replaced
1049 * @param replacement The replacement sequence of char values
1050 * @return The resulting string
1051 * @see String#replace(CharSequence, CharSequence)
1052 */
1053 public static String replace(String s, String target, String replacement) {
1054 return s == null ? null : s.replace(target, replacement);
1055 }
1056
1057 /**
1058 * Converts string {@code s} to uppercase.
1059 * @param s The source string
1060 * @return The resulting string
1061 * @see String#toUpperCase(Locale)
1062 * @since 11756
1063 */
1064 public static String upper(String s) {
1065 return s == null ? null : s.toUpperCase(Locale.ENGLISH);
1066 }
1067
1068 /**
1069 * Converts string {@code s} to lowercase.
1070 * @param s The source string
1071 * @return The resulting string
1072 * @see String#toLowerCase(Locale)
1073 * @since 11756
1074 */
1075 public static String lower(String s) {
1076 return s == null ? null : s.toLowerCase(Locale.ENGLISH);
1077 }
1078
1079 /**
1080 * Returns a title-cased version of the string where words start with an uppercase character and the remaining characters are lowercase
1081 * <p>
1082 * Also known as "capitalize".
1083 * @param str The source string
1084 * @return The resulting string
1085 * @see Character#toTitleCase(char)
1086 * @since 17613
1087 */
1088 public static String title(String str) {
1089 // adapted from org.apache.commons.lang3.text.WordUtils.capitalize
1090 if (str == null) {
1091 return null;
1092 }
1093 final char[] buffer = str.toCharArray();
1094 boolean capitalizeNext = true;
1095 for (int i = 0; i < buffer.length; i++) {
1096 final char ch = buffer[i];
1097 if (Character.isWhitespace(ch)) {
1098 capitalizeNext = true;
1099 } else if (capitalizeNext) {
1100 buffer[i] = Character.toTitleCase(ch);
1101 capitalizeNext = false;
1102 } else {
1103 buffer[i] = Character.toLowerCase(ch);
1104 }
1105 }
1106 return new String(buffer);
1107 }
1108
1109 /**
1110 * Trim whitespaces from the string {@code s}.
1111 * @param s The source string
1112 * @return The resulting string
1113 * @see Utils#strip
1114 * @since 11756
1115 */
1116 public static String trim(String s) {
1117 return Utils.strip(s);
1118 }
1119
1120 /**
1121 * Trim whitespaces from the strings {@code strings}.
1122 *
1123 * @param strings The list of strings to strip
1124 * @return The resulting string
1125 * @see Utils#strip
1126 * @since 15591
1127 */
1128 public static List<String> trim_list(List<String> strings) {
1129 return strings.stream().map(Utils::strip).filter(str -> !str.isEmpty()).collect(Collectors.toList());
1130 }
1131
1132 /**
1133 * Check if two strings are similar, but not identical, i.e., have a Levenshtein distance of 1 or 2.
1134 * @param string1 first string to compare
1135 * @param string2 second string to compare
1136 * @return true if the normalized strings are different but only a "little bit"
1137 * @see Utils#isSimilar
1138 * @since 14371
1139 */
1140 public static boolean is_similar(String string1, String string2) {
1141 return Utils.isSimilar(string1, string2);
1142 }
1143
1144 /**
1145 * Percent-decode a string. (See
1146 * <a href="https://en.wikipedia.org/wiki/Percent-encoding">https://en.wikipedia.org/wiki/Percent-encoding</a>)
1147 * <p>
1148 * This is especially useful for wikipedia titles
1149 * @param s url-encoded string
1150 * @return the decoded string, or original in case of an error
1151 * @since 11756
1152 */
1153 public static String URL_decode(String s) {
1154 if (s == null) return null;
1155 try {
1156 return Utils.decodeUrl(s);
1157 } catch (IllegalStateException e) {
1158 Logging.debug(e);
1159 return s;
1160 }
1161 }
1162
1163 /**
1164 * Percent-encode a string.
1165 * (See <a href="https://en.wikipedia.org/wiki/Percent-encoding">https://en.wikipedia.org/wiki/Percent-encoding</a>)
1166 * <p>
1167 * This is especially useful for data urls, e.g.
1168 * <code>concat("data:image/svg+xml,", URL_encode("&lt;svg&gt;...&lt;/svg&gt;"));</code>
1169 * @param s arbitrary string
1170 * @return the encoded string
1171 */
1172 public static String URL_encode(String s) {
1173 return s == null ? null : Utils.encodeUrl(s);
1174 }
1175
1176 /**
1177 * XML-encode a string.
1178 * <p>
1179 * Escapes special characters in xml. Alternative to using &lt;![CDATA[ ... ]]&gt; blocks.
1180 * @param s arbitrary string
1181 * @return the encoded string
1182 */
1183 public static String XML_encode(String s) {
1184 return s == null ? null : XmlWriter.encode(s);
1185 }
1186
1187 /**
1188 * Calculates the CRC32 checksum from a string (based on RFC 1952).
1189 * @param s the string
1190 * @return long value from 0 to 2^32-1
1191 */
1192 public static long CRC32_checksum(String s) {
1193 CRC32 cs = new CRC32();
1194 cs.update(s.getBytes(StandardCharsets.UTF_8));
1195 return cs.getValue();
1196 }
1197
1198 /**
1199 * check if there is right-hand traffic at the current location
1200 * @param env the environment
1201 * @return true if there is right-hand traffic
1202 * @since 7193
1203 */
1204 public static boolean is_right_hand_traffic(Environment env) {
1205 final LatLon center = center(env);
1206 if (center != null) {
1207 return RightAndLefthandTraffic.isRightHandTraffic(center);
1208 }
1209 return false;
1210 }
1211
1212 /**
1213 * Determines whether the way is {@link Geometry#isClockwise closed and oriented clockwise},
1214 * or non-closed and the {@link Geometry#angleIsClockwise 1st, 2nd and last node are in clockwise order}.
1215 *
1216 * @param env the environment
1217 * @return true if the way is closed and oriented clockwise
1218 */
1219 public static boolean is_clockwise(Environment env) {
1220 if (!(env.osm instanceof Way)) {
1221 return false;
1222 }
1223 final Way way = (Way) env.osm;
1224 return (way.isClosed() && Geometry.isClockwise(way))
1225 || (!way.isClosed() && way.getNodesCount() > 2 && Geometry.angleIsClockwise(way.getNode(0), way.getNode(1), way.lastNode()));
1226 }
1227
1228 /**
1229 * Determines whether the way is {@link Geometry#isClockwise closed and oriented anticlockwise},
1230 * or non-closed and the {@link Geometry#angleIsClockwise 1st, 2nd and last node are in anticlockwise order}.
1231 *
1232 * @param env the environment
1233 * @return true if the way is closed and oriented clockwise
1234 */
1235 public static boolean is_anticlockwise(Environment env) {
1236 if (!(env.osm instanceof Way)) {
1237 return false;
1238 }
1239 final Way way = (Way) env.osm;
1240 return (way.isClosed() && !Geometry.isClockwise(way))
1241 || (!way.isClosed() && way.getNodesCount() > 2 && !Geometry.angleIsClockwise(way.getNode(0), way.getNode(1), way.lastNode()));
1242 }
1243
1244 /**
1245 * Prints the object to the command line (for debugging purpose).
1246 * @param o the object
1247 * @return the same object, unchanged
1248 */
1249 @NullableArguments
1250 public static Object print(Object o) {
1251 System.out.print(o == null ? "none" : o.toString());
1252 return o;
1253 }
1254
1255 /**
1256 * Prints the object to the command line, with new line at the end
1257 * (for debugging purpose).
1258 * @param o the object
1259 * @return the same object, unchanged
1260 */
1261 @NullableArguments
1262 public static Object println(Object o) {
1263 System.out.println(o == null ? "none" : o.toString());
1264 return o;
1265 }
1266
1267 /**
1268 * Get the number of tags for the current primitive.
1269 * @param env the environment
1270 * @return number of tags
1271 */
1272 public static int number_of_tags(Environment env) {
1273 return env.osm != null ? env.osm.getNumKeys() : 0;
1274 }
1275
1276 /**
1277 * Get value of a setting.
1278 * @param env the environment
1279 * @param key setting key (given as layer identifier, e.g. setting::mykey {...})
1280 * @return the value of the setting (calculated when the style is loaded)
1281 */
1282 public static Object setting(Environment env, String key) {
1283 return env.source != null ? env.source.settingValues.get(key) : null;
1284 }
1285
1286 /**
1287 * Returns the center of the environment OSM primitive.
1288 * @param env the environment
1289 * @return the center of the environment OSM primitive
1290 * @since 11247
1291 */
1292 public static LatLon center(Environment env) {
1293 if (env.osm instanceof ILatLon) {
1294 return new LatLon(((ILatLon) env.osm).lat(), ((ILatLon) env.osm).lon());
1295 } else if (env.osm != null) {
1296 return env.osm.getBBox().getCenter();
1297 }
1298 return null;
1299 }
1300
1301 /**
1302 * Determines if the object is inside territories matching given ISO3166 codes.
1303 * @param env the environment
1304 * @param codes comma-separated list of ISO3166-1-alpha2 or ISO3166-2 country/subdivision codes
1305 * @return {@code true} if the object is inside territory matching given ISO3166 codes
1306 * @since 11247
1307 */
1308 public static boolean inside(Environment env, String codes) {
1309 return Arrays.stream(codes.toUpperCase(Locale.ENGLISH).split(",", -1))
1310 .anyMatch(code -> Territories.isIso3166Code(code.trim(), center(env)));
1311 }
1312
1313 /**
1314 * Determines if the object is outside territories matching given ISO3166 codes.
1315 * @param env the environment
1316 * @param codes comma-separated list of ISO3166-1-alpha2 or ISO3166-2 country/subdivision codes
1317 * @return {@code true} if the object is outside territory matching given ISO3166 codes
1318 * @since 11247
1319 */
1320 public static boolean outside(Environment env, String codes) {
1321 return !inside(env, codes);
1322 }
1323
1324 /**
1325 * Determines if the object centroid lies at given lat/lon coordinates.
1326 * @param env the environment
1327 * @param lat latitude, i.e., the north-south position in degrees
1328 * @param lon longitude, i.e., the east-west position in degrees
1329 * @return {@code true} if the object centroid lies at given lat/lon coordinates
1330 * @since 12514
1331 */
1332 public static boolean at(Environment env, double lat, double lon) {
1333 final ILatLon center = center(env);
1334 if (center != null) {
1335 return new LatLon(lat, lon).equalsEpsilon(center, ILatLon.MAX_SERVER_PRECISION);
1336 }
1337 return false;
1338 }
1339
1340 /**
1341 * Parses the string argument as a boolean.
1342 * @param value the {@code String} containing the boolean representation to be parsed
1343 * @return the boolean represented by the string argument
1344 * @see Boolean#parseBoolean
1345 * @since 16110
1346 */
1347 public static boolean to_boolean(String value) {
1348 return Boolean.parseBoolean(value);
1349 }
1350
1351 /**
1352 * Parses the string argument as a byte.
1353 * @param value the {@code String} containing the byte representation to be parsed
1354 * @return the byte represented by the string argument
1355 * @see Byte#parseByte
1356 * @since 16110
1357 */
1358 public static byte to_byte(String value) {
1359 return Byte.parseByte(value);
1360 }
1361
1362 /**
1363 * Parses the string argument as a short.
1364 * @param value the {@code String} containing the short representation to be parsed
1365 * @return the short represented by the string argument
1366 * @see Short#parseShort
1367 * @since 16110
1368 */
1369 public static short to_short(String value) {
1370 return Short.parseShort(value);
1371 }
1372
1373 /**
1374 * Parses the string argument as an int.
1375 * @param value the {@code String} containing the int representation to be parsed
1376 * @return the int represented by the string argument
1377 * @see Integer#parseInt
1378 * @since 16110
1379 */
1380 public static int to_int(String value) {
1381 return Integer.parseInt(value);
1382 }
1383
1384 /**
1385 * Parses the string argument as a long.
1386 * @param value the {@code String} containing the long representation to be parsed
1387 * @return the long represented by the string argument
1388 * @see Long#parseLong
1389 * @since 16110
1390 */
1391 public static long to_long(String value) {
1392 return Long.parseLong(value);
1393 }
1394
1395 /**
1396 * Parses the string argument as a float.
1397 * @param value the {@code String} containing the float representation to be parsed
1398 * @return the float represented by the string argument
1399 * @see Float#parseFloat
1400 * @since 16110
1401 */
1402 public static float to_float(String value) {
1403 return Float.parseFloat(value);
1404 }
1405
1406 /**
1407 * Parses the string argument as a double.
1408 * @param value the {@code String} containing the double representation to be parsed
1409 * @return the double represented by the string argument
1410 * @see Double#parseDouble
1411 * @since 16110
1412 */
1413 public static double to_double(String value) {
1414 return Double.parseDouble(value);
1415 }
1416}
Note: See TracBrowser for help on using the repository browser.