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}