Changeset 18829 in josm


Ignore:
Timestamp:
2023-09-18T17:54:36+02:00 (8 months ago)
Author:
taylor.smock
Message:

Fix #16998: Add parent_osm_primitives and convert_primitives_to_string

Location:
trunk
Files:
4 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/org/openstreetmap/josm/data/osm/SimplePrimitiveId.java

    r17823 r18829  
    9999        final Matcher m = MULTIPLE_IDS_PATTERN.matcher(s);
    100100        if (m.matches()) {
    101             return extractIdsInto(m, new ArrayList<SimplePrimitiveId>());
     101            return extractIdsInto(m, new ArrayList<>());
    102102        } else {
    103103            throw new IllegalArgumentException("The string " + s + " does not match the pattern " + MULTIPLE_IDS_PATTERN);
     
    149149        return firstChar == 'n' ? OsmPrimitiveType.NODE : firstChar == 'w' ? OsmPrimitiveType.WAY : OsmPrimitiveType.RELATION;
    150150    }
     151
     152    /**
     153     * Convert a primitive to a simple id
     154     *
     155     * @param primitive The primitive to convert
     156     * @return The type (may be n, w, or r, or something else) + the id (e.g., w42)
     157     * @since 18829
     158     */
     159    public static String toSimpleId(PrimitiveId primitive) {
     160        switch (primitive.getType()) {
     161            case NODE:
     162                return "n" + primitive.getUniqueId();
     163            case CLOSEDWAY:
     164            case WAY:
     165                return "w" + primitive.getUniqueId();
     166            case MULTIPOLYGON:
     167            case RELATION:
     168                return "r" + primitive.getUniqueId();
     169        }
     170        throw new IllegalArgumentException("Unknown primitive type: " + primitive.getType());
     171    }
    151172}
  • trunk/src/org/openstreetmap/josm/gui/mappaint/mapcss/ExpressionFactory.java

    r18664 r18829  
    77import java.lang.annotation.RetentionPolicy;
    88import java.lang.annotation.Target;
     9import java.lang.reflect.Array;
     10import java.util.Arrays;
    911import java.util.Collection;
    1012import java.util.Collections;
     
    1820import java.util.function.Function;
    1921
     22import org.openstreetmap.josm.data.osm.PrimitiveId;
    2023import org.openstreetmap.josm.gui.mappaint.Cascade;
    2124import org.openstreetmap.josm.gui.mappaint.Environment;
     
    101104                                    BiFunction<T, U, ?> biFunction, TriFunction<T, U, V, ?> triFunction) {
    102105            return args -> env -> {
    103                 T v1 = args.size() >= 1 ? Cascade.convertTo(args.get(0).evaluate(env), type1) : null;
     106                T v1 = !args.isEmpty() ? Cascade.convertTo(args.get(0).evaluate(env), type1) : null;
    104107                U v2 = args.size() >= 2 ? Cascade.convertTo(args.get(1).evaluate(env), type2) : null;
    105108                V v3 = args.size() >= 3 ? Cascade.convertTo(args.get(2).evaluate(env), type3) : null;
     
    111114                                       QuadFunction<T, U, V, W, ?> function) {
    112115            return args -> env -> {
    113                 T v1 = args.size() >= 1 ? Cascade.convertTo(args.get(0).evaluate(env), type1) : null;
     116                T v1 = !args.isEmpty() ? Cascade.convertTo(args.get(0).evaluate(env), type1) : null;
    114117                U v2 = args.size() >= 2 ? Cascade.convertTo(args.get(1).evaluate(env), type2) : null;
    115118                V v3 = args.size() >= 3 ? Cascade.convertTo(args.get(2).evaluate(env), type3) : null;
     
    119122        }
    120123
    121         static <T> Factory ofEnv(Function<Environment, ?> function) {
     124        /**
     125         * Create a factory that accepts an iterable (array <i>or</i> generic iterable)
     126         * @param type The expected type class
     127         * @param function The function to apply the arguments to
     128         * @param <T> The iterable type
     129         * @return The result of the function call
     130         */
     131        @SuppressWarnings("unchecked")
     132        static <T> Factory ofIterable(Class<T> type, Function<Iterable<T>, ?> function) {
     133            return args -> env -> {
     134                Object arg0 = args.get(0).evaluate(env);
     135                if (args.size() == 1 && arg0 instanceof Iterable) {
     136                    return function.apply((Iterable<T>) arg0);
     137                } else {
     138                    return function.apply(Arrays.asList(args.stream().map(arg -> Cascade.convertTo(arg, type))
     139                            .toArray(length -> (T[]) Array.newInstance(type, length))));
     140                }
     141            };
     142        }
     143
     144        /**
     145         * Create a {@link Factory} for a function
     146         * @param function The function to use
     147         * @return The result of the function
     148         */
     149        static Factory ofEnv(Function<Environment, ?> function) {
    122150            return args -> function::apply;
    123151        }
    124152
     153        /**
     154         * Create a {@link Factory} for a function that takes a parameter
     155         * @param type The parameter type class
     156         * @param function The function to use when one argument is available
     157         * @param <T> the type of the input to the function
     158         * @return The result of the function
     159         */
    125160        static <T> Factory ofEnv(Class<T> type, BiFunction<Environment, T, ?> function) {
    126161            return args -> env -> {
     
    130165        }
    131166
     167        /**
     168         * Create a {@link Factory} for an overloaded function
     169         * @param type1 The first parameter type class
     170         * @param type2 The second parameter type class
     171         * @param biFunction The function to use when one argument is available
     172         * @param triFunction The function to use when two arguments are available
     173         * @param <T> the type of the input to the function
     174         * @param <U> the type of the input to the function
     175         * @return The result of one of the functions
     176         */
    132177        static <T, U> Factory ofEnv(Class<T> type1, Class<U> type2,
    133178                                    BiFunction<Environment, T, ?> biFunction, TriFunction<Environment, T, U, ?> triFunction) {
    134179            return args -> env -> {
    135                 T v1 = args.size() >= 1 ? Cascade.convertTo(args.get(0).evaluate(env), type1) : null;
     180                T v1 = !args.isEmpty() ? Cascade.convertTo(args.get(0).evaluate(env), type1) : null;
    136181                U v2 = args.size() >= 2 ? Cascade.convertTo(args.get(1).evaluate(env), type2) : null;
    137182                return v1 == null ? null : v2 == null ? biFunction.apply(env, v1) : triFunction.apply(env, v1, v2);
     183            };
     184        }
     185
     186        /**
     187         * Create a {@link Factory} for an overloaded function
     188         * @param type1 The first parameter type class
     189         * @param type2 The second parameter type class
     190         * @param function The function to use when no args are available
     191         * @param biFunction The function to use when one argument is available
     192         * @param triFunction The function to use when two arguments are available
     193         * @param <T> the type of the input to the function
     194         * @param <U> the type of the input to the function
     195         * @return The result of one of the functions
     196         */
     197        static <T, U> Factory ofEnv(Class<T> type1, Class<U> type2, Function<Environment, ?> function,
     198                                    BiFunction<Environment, T, ?> biFunction, TriFunction<Environment, T, U, ?> triFunction) {
     199            return args -> env -> {
     200                T v1 = !args.isEmpty() ? Cascade.convertTo(args.get(0).evaluate(env), type1) : null;
     201                U v2 = args.size() >= 2 ? Cascade.convertTo(args.get(1).evaluate(env), type2) : null;
     202                return v1 == null ? function.apply(env) : v2 == null ? biFunction.apply(env, v1) : triFunction.apply(env, v1, v2);
    138203            };
    139204        }
     
    170235        FACTORY_MAP.put("color2html", Factory.of(Color.class, Functions::color2html));
    171236        FACTORY_MAP.put("concat", Factory.ofObjectVarargs(Functions::concat));
     237        FACTORY_MAP.put("convert_primitive_to_string", Factory.of(PrimitiveId.class, Functions::convert_primitive_to_string));
     238        FACTORY_MAP.put("convert_primitives_to_string", Factory.ofIterable(PrimitiveId.class, Functions::convert_primitives_to_string));
    172239        FACTORY_MAP.put("cos", Factory.of(Math::cos));
    173240        FACTORY_MAP.put("cosh", Factory.of(Math::cosh));
     
    215282        FACTORY_MAP.put("outside", Factory.ofEnv(String.class, Functions::outside));
    216283        FACTORY_MAP.put("parent_osm_id", Factory.ofEnv(Functions::parent_osm_id));
     284        FACTORY_MAP.put("parent_osm_primitives", Factory.ofEnv(String.class, String.class,
     285                Functions::parent_osm_primitives, Functions::parent_osm_primitives, Functions::parent_osm_primitives));
    217286        FACTORY_MAP.put("parent_tag", Factory.ofEnv(String.class, Functions::parent_tag));
    218287        FACTORY_MAP.put("parent_tags", Factory.ofEnv(String.class, Functions::parent_tags));
     
    391460    /**
    392461     * Function to calculate the length of a string or list in a MapCSS eval expression.
    393      *
     462     * <p>
    394463     * Separate implementation to support overloading for different argument types.
    395      *
     464     * <p>
    396465     * The use for calculating the length of a list is deprecated, use
    397466     * {@link Functions#count(java.util.List)} instead (see #10061).
  • trunk/src/org/openstreetmap/josm/gui/mappaint/mapcss/Functions.java

    r18665 r18829  
    44import java.awt.Color;
    55import java.nio.charset.StandardCharsets;
     6import java.util.ArrayList;
    67import java.util.Arrays;
     8import java.util.Collection;
    79import java.util.Collections;
    810import java.util.List;
     
    2123import org.openstreetmap.josm.data.osm.Node;
    2224import org.openstreetmap.josm.data.osm.OsmPrimitive;
     25import org.openstreetmap.josm.data.osm.PrimitiveId;
    2326import org.openstreetmap.josm.data.osm.Relation;
     27import org.openstreetmap.josm.data.osm.SimplePrimitiveId;
    2428import org.openstreetmap.josm.data.osm.Way;
    2529import org.openstreetmap.josm.data.osm.search.SearchCompiler;
     
    4650/**
    4751 * List of functions that can be used in MapCSS expressions.
    48  *
     52 * <p>
    4953 * First parameter can be of type {@link Environment} (if needed). This is
    5054 * automatically filled in by JOSM and the user only sees the remaining arguments.
     
    458462    /**
    459463     * Gets a list of all non-null values of the key {@code key} from the object's parent(s).
    460      *
     464     * <p>
    461465     * The values are sorted according to {@link AlphanumComparator}.
    462466     * @param env the environment
     
    520524
    521525    /**
     526     * Gets a list of all OSM id's of the object's parent(s) with a specified key.
     527     *
     528     * @param env      the environment
     529     * @param key      the OSM key
     530     * @param keyValue the regex value of the OSM key
     531     * @return a list of non-null values of the OSM id's from the object's parent(s)
     532     * @since 18829
     533     */
     534    @NullableArguments
     535    public static List<IPrimitive> parent_osm_primitives(final Environment env, String key, String keyValue) {
     536         if (env.parent == null) {
     537             if (env.osm != null) {
     538                final ArrayList<IPrimitive> parents = new ArrayList<>();
     539                for (IPrimitive parent : env.osm.getReferrers()) {
     540                    if ((key == null || parent.get(key) != null)
     541                            && (keyValue == null || regexp_test(keyValue, parent.get(key)))) {
     542                        parents.add(parent);
     543                    }
     544                }
     545                return Collections.unmodifiableList(parents);
     546            }
     547            return Collections.emptyList();
     548         }
     549         return Collections.singletonList(env.parent);
     550     }
     551
     552     /**
     553      * Gets a list of all OSM id's of the object's parent(s) with a specified key.
     554      *
     555      * @param env the environment
     556      * @param key the OSM key
     557      * @return a list of non-null values of the OSM id's from the object's parent(s)
     558      * @since 18829
     559      */
     560     @NullableArguments
     561     public static List<IPrimitive> parent_osm_primitives(final Environment env, String key) {
     562         return parent_osm_primitives(env, key, null);
     563     }
     564
     565    /**
     566     * Gets a list of all OSM id's of the object's parent(s).
     567     *
     568     * @param env the environment
     569     * @return a list of non-null values of the OSM id's from the object's parent(s)
     570     * @since 18829
     571     */
     572    public static List<IPrimitive> parent_osm_primitives(final Environment env) {
     573        return parent_osm_primitives(env, null, null);
     574    }
     575
     576    /**
     577     * Convert Primitives to a string
     578     *
     579     * @param primitives The primitives to convert
     580     * @return A list of strings in the format type + id (in the list order)
     581     * @see SimplePrimitiveId#toSimpleId
     582     * @since 18829
     583     */
     584    public static List<String> convert_primitives_to_string(Iterable<PrimitiveId> primitives) {
     585        final List<String> primitiveStrings = new ArrayList<>(primitives instanceof Collection ?
     586                ((Collection<?>) primitives).size() : 0);
     587        for (PrimitiveId primitive : primitives) {
     588            primitiveStrings.add(convert_primitive_to_string(primitive));
     589        }
     590        return primitiveStrings;
     591    }
     592
     593    /**
     594     * Convert a primitive to a string
     595     *
     596     * @param primitive The primitive to convert
     597     * @return A string in the format type + id
     598     * @see SimplePrimitiveId#toSimpleId
     599     * @since 18829
     600     */
     601    public static String convert_primitive_to_string(PrimitiveId primitive) {
     602        return SimplePrimitiveId.toSimpleId(primitive);
     603    }
     604
     605    /**
    522606     * Returns the lowest distance between the OSM object and a GPX point
    523607     * <p>
     
    554638            return null;
    555639        }
    556         return Float.valueOf(env.index + 1f);
     640        return env.index + 1f;
    557641    }
    558642
     
    726810        try {
    727811            return RotationAngle.parseCardinalRotation(cardinal);
    728         } catch (IllegalArgumentException ignore) {
    729             Logging.trace(ignore);
     812        } catch (IllegalArgumentException illegalArgumentException) {
     813            Logging.trace(illegalArgumentException);
    730814            return null;
    731815        }
     
    778862     * Obtains the JOSM key {@link org.openstreetmap.josm.data.Preferences} string for key {@code key},
    779863     * and defaults to {@code def} if that is null.
    780      *
     864     * <p>
    781865     * If the default value can be {@linkplain Cascade#convertTo converted} to a {@link Color},
    782866     * the {@link NamedColorProperty} is retrieved as string.
     
    9891073    /**
    9901074     * Returns a title-cased version of the string where words start with an uppercase character and the remaining characters are lowercase
    991      *
     1075     * <p>
    9921076     * Also known as "capitalize".
    9931077     * @param str The source string
     
    10531137
    10541138    /**
    1055      * Percent-decode a string. (See https://en.wikipedia.org/wiki/Percent-encoding)
     1139     * Percent-decode a string. (See
     1140     * <a href="https://en.wikipedia.org/wiki/Percent-encoding">https://en.wikipedia.org/wiki/Percent-encoding</a>)
     1141     * <p>
    10561142     * This is especially useful for wikipedia titles
    10571143     * @param s url-encoded string
     
    10701156
    10711157    /**
    1072      * Percent-encode a string. (See https://en.wikipedia.org/wiki/Percent-encoding)
     1158     * Percent-encode a string.
     1159     * (See <a href="https://en.wikipedia.org/wiki/Percent-encoding">https://en.wikipedia.org/wiki/Percent-encoding</a>)
     1160     * <p>
    10731161     * This is especially useful for data urls, e.g.
    10741162     * <code>concat("data:image/svg+xml,", URL_encode("&lt;svg&gt;...&lt;/svg&gt;"));</code>
     
    10821170    /**
    10831171     * XML-encode a string.
    1084      *
     1172     * <p>
    10851173     * Escapes special characters in xml. Alternative to using &lt;![CDATA[ ... ]]&gt; blocks.
    10861174     * @param s arbitrary string
  • trunk/test/unit/org/openstreetmap/josm/gui/mappaint/mapcss/FunctionsTest.java

    r18664 r18829  
    22package org.openstreetmap.josm.gui.mappaint.mapcss;
    33
     4import static org.junit.jupiter.api.Assertions.assertAll;
    45import static org.junit.jupiter.api.Assertions.assertEquals;
     6import static org.junit.jupiter.api.Assertions.assertFalse;
    57import static org.junit.jupiter.api.Assertions.assertNotNull;
    68import static org.junit.jupiter.api.Assertions.assertNull;
     9import static org.junit.jupiter.api.Assertions.assertSame;
    710import static org.junit.jupiter.api.Assertions.assertTrue;
    811import static org.openstreetmap.josm.data.osm.OsmPrimitiveType.NODE;
    912
     13import java.util.Arrays;
    1014import java.util.Collections;
     15import java.util.List;
    1116import java.util.Objects;
    1217
     
    1419import org.openstreetmap.josm.TestUtils;
    1520import org.openstreetmap.josm.data.coor.LatLon;
     21import org.openstreetmap.josm.data.osm.IPrimitive;
    1622import org.openstreetmap.josm.data.osm.Node;
    1723import org.openstreetmap.josm.data.osm.OsmPrimitive;
    1824import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
     25import org.openstreetmap.josm.data.osm.Relation;
     26import org.openstreetmap.josm.data.osm.RelationMember;
     27import org.openstreetmap.josm.data.osm.SimplePrimitiveId;
    1928import org.openstreetmap.josm.data.osm.User;
    2029import org.openstreetmap.josm.data.osm.Way;
     
    181190        Config.getPref().put(colorKey, null);
    182191    }
     192
     193    @Test
     194    void testConvertPrimitivesToString() {
     195        assertEquals(Collections.singletonList("n1"), Functions.convert_primitives_to_string(
     196                Collections.singleton(new SimplePrimitiveId(1, NODE))));
     197        assertEquals(Arrays.asList("n1", "n9223372036854775807"), Functions.convert_primitives_to_string(
     198                Arrays.asList(new SimplePrimitiveId(1, NODE), new SimplePrimitiveId(Long.MAX_VALUE, NODE))));
     199    }
     200
     201    @Test
     202    void testParentOsmPrimitives() {
     203        final Environment env = new EnvBuilder(NODE).build();
     204        final Relation relation1 = TestUtils.newRelation("", new RelationMember("", (Node) env.osm));
     205        final Relation relation2 = TestUtils.newRelation("type=something", new RelationMember("", (Node) env.osm));
     206        final Relation relation3 = TestUtils.newRelation("type=somethingelse", new RelationMember("", (Node) env.osm));
     207
     208        TestUtils.addFakeDataSet((Node) env.osm);
     209        for (Relation relation : Arrays.asList(relation1, relation2, relation3)) {
     210            ((Node) env.osm).getDataSet().addPrimitive(relation);
     211        }
     212
     213        final List<IPrimitive> allReferrers = Functions.parent_osm_primitives(env);
     214        assertAll(() -> assertEquals(3, allReferrers.size()),
     215                () -> assertTrue(allReferrers.contains(relation1)),
     216                () -> assertTrue(allReferrers.contains(relation2)),
     217                () -> assertTrue(allReferrers.contains(relation3)));
     218
     219        final List<IPrimitive> typeReferrers = Functions.parent_osm_primitives(env, "type");
     220        assertAll(() -> assertEquals(2, typeReferrers.size()),
     221                () -> assertFalse(typeReferrers.contains(relation1)),
     222                () -> assertTrue(typeReferrers.contains(relation2)),
     223                () -> assertTrue(typeReferrers.contains(relation3)));
     224
     225        final List<IPrimitive> typeSomethingReferrers = Functions.parent_osm_primitives(env, "type", "something");
     226        assertAll(() -> assertEquals(1, typeSomethingReferrers.size()),
     227                () -> assertSame(relation2, typeSomethingReferrers.get(0)));
     228
     229        assertTrue(Functions.parent_osm_primitives(env, "type2").isEmpty());
     230    }
    183231}
Note: See TracChangeset for help on using the changeset viewer.