001// License: GPL. For details, see LICENSE file.
002
003package org.openstreetmap.josm.tools;
004
005import java.util.Collection;
006import java.util.Collections;
007import java.util.Comparator;
008import java.util.EnumSet;
009import java.util.Objects;
010import java.util.Optional;
011
012import javax.swing.ImageIcon;
013
014import org.openstreetmap.josm.data.coor.LatLon;
015import org.openstreetmap.josm.data.osm.DataSet;
016import org.openstreetmap.josm.data.osm.INode;
017import org.openstreetmap.josm.data.osm.OsmPrimitive;
018import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
019import org.openstreetmap.josm.gui.mappaint.MapPaintStyles;
020import org.openstreetmap.josm.gui.mappaint.Range;
021import org.openstreetmap.josm.gui.mappaint.StyleElementList;
022import org.openstreetmap.josm.gui.mappaint.styleelement.MapImage;
023import org.openstreetmap.josm.gui.mappaint.styleelement.NodeElement;
024import org.openstreetmap.josm.gui.mappaint.styleelement.StyleElement;
025import org.openstreetmap.josm.gui.tagging.presets.TaggingPreset;
026import org.openstreetmap.josm.gui.tagging.presets.TaggingPresets;
027
028/**
029 * An {@link ImageProvider} for {@link org.openstreetmap.josm.data.osm.OsmPrimitive}
030 *
031 * @since 16838 (extracted from ImageProvider)
032 */
033public final class OsmPrimitiveImageProvider {
034
035    private OsmPrimitiveImageProvider() {
036        // private constructor
037    }
038
039    /**
040     * Returns an {@link ImageIcon} for the given OSM object, at the specified size.
041     * This is a slow operation.
042     * @param primitive Object for which an icon shall be fetched. The icon is chosen based on tags.
043     * @param options zero or more {@linkplain Options options}.
044     * @return Icon for {@code primitive} that fits in cell or {@code null}.
045     * @since 15889
046     */
047    public static ImageResource getResource(OsmPrimitive primitive, Collection<Options> options) {
048        // Check if the current styles have special icon for tagged objects.
049        if (primitive.isTagged()) {
050            ImageResource icon = getResourceFromMapPaintStyles(primitive, options);
051            if (icon != null) {
052                return icon;
053            }
054        }
055
056        // Check if the presets have icons for nodes/relations.
057        if (primitive.isTagged() && (!options.contains(Options.NO_WAY_PRESETS) || OsmPrimitiveType.WAY != primitive.getType())) {
058            final Optional<ImageResource> icon = TaggingPresets.getMatchingPresets(primitive).stream()
059                    .sorted(Comparator.comparing(p -> (p.iconName != null && p.iconName.contains("multipolygon"))
060                            || Utils.isEmpty(p.types) ? Integer.MAX_VALUE : p.types.size()))
061                    .map(TaggingPreset::getImageResource)
062                    .filter(Objects::nonNull)
063                    .findFirst();
064            if (icon.isPresent()) {
065                return icon.get();
066            }
067        }
068
069        // Use generic default icon.
070        return options.contains(Options.NO_DEFAULT)
071                ? null
072                : new ImageProvider("data", primitive.getDisplayType().getAPIName()).getResource();
073    }
074
075    /**
076     * Computes a new padded icon for the given tagged primitive, using map paint styles.
077     * This is a slow operation.
078     * @param primitive tagged OSM primitive
079     * @param options zero or more {@linkplain Options options}.
080     * @return a new padded icon for the given tagged primitive, or null
081     */
082    private static ImageResource getResourceFromMapPaintStyles(OsmPrimitive primitive, Collection<Options> options) {
083        Pair<StyleElementList, Range> nodeStyles;
084        DataSet ds = primitive.getDataSet();
085        if (ds != null) {
086            ds.getReadLock().lock();
087        }
088        try {
089            nodeStyles = MapPaintStyles.getStyles().generateStyles(primitive, 100, false);
090        } finally {
091            if (ds != null) {
092                ds.getReadLock().unlock();
093            }
094        }
095        for (StyleElement style : nodeStyles.a) {
096            if (style instanceof NodeElement) {
097                NodeElement nodeStyle = (NodeElement) style;
098                MapImage icon = nodeStyle.mapImage;
099                if (icon != null && icon.getImageResource() != null &&
100                        (icon.name == null || !options.contains(Options.NO_DEPRECATED) || !icon.name.contains("deprecated"))) {
101                    return icon.getImageResource();
102                }
103            }
104        }
105        return null;
106    }
107
108    /**
109     * Searches for an icon for the given key/value and primitiveType
110     * @param key The tag key
111     * @param value The tag value
112     * @param primitiveType The type of the primitive
113     * @return an icon for the given key/value and primitiveType
114     */
115    public static Optional<ImageResource> getResource(String key, String value, OsmPrimitiveType primitiveType) {
116        final OsmPrimitive virtual = primitiveType
117                .newInstance(0, false);
118        if (virtual instanceof INode) {
119            ((INode) virtual).setCoor(LatLon.ZERO);
120        }
121        virtual.put(key, value);
122        try {
123            final ImageResource padded = getResource(virtual, EnumSet.of(Options.NO_DEFAULT, Options.NO_DEPRECATED));
124            return Optional.ofNullable(padded);
125        } catch (Exception e) {
126            Logging.warn("Failed to find icon for {0} {1}={2}", virtual.getType(), key, value);
127            Logging.warn(e);
128            return Optional.empty();
129        }
130    }
131
132    /**
133     * Options used in {@link #getResource(OsmPrimitive, Collection)}.
134     * @since 15889
135     */
136    public enum Options {
137        /**
138         * Exclude icon indicating deprecated tag usage.
139         */
140        NO_DEPRECATED,
141        /**
142         * Exclude default icon for {@link OsmPrimitiveType} from {@link ImageProvider#get(OsmPrimitiveType)}
143         */
144        NO_DEFAULT,
145        /**
146         * Exclude tagging preset icons.
147         */
148        NO_PRESETS,
149        /**
150         * Exclude tagging preset icons for {@linkplain OsmPrimitiveType#WAY ways}.
151         */
152        NO_WAY_PRESETS;
153
154        static final Collection<Options> DEFAULT = Collections.singleton(Options.NO_WAY_PRESETS);
155    }
156}