001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.gui.mappaint.styleelement; 003 004import java.util.ArrayList; 005import java.util.Arrays; 006import java.util.Collections; 007import java.util.List; 008import java.util.Objects; 009 010import org.openstreetmap.josm.Main; 011import org.openstreetmap.josm.data.Preferences.PreferenceChangeEvent; 012import org.openstreetmap.josm.data.Preferences.PreferenceChangedListener; 013import org.openstreetmap.josm.data.osm.OsmPrimitive; 014import org.openstreetmap.josm.tools.LanguageInfo; 015 016/** 017 * <p>Provides an abstract parent class and three concrete sub classes for various 018 * strategies on how to compose the text label which can be rendered close to a node 019 * or within an area in an OSM map.</p> 020 * 021 * <p>The three strategies below support three rules for composing a label: 022 * <ul> 023 * <li>{@link StaticLabelCompositionStrategy} - the label is given by a static text 024 * specified in the MapCSS style file</li> 025 * 026 * <li>{@link TagLookupCompositionStrategy} - the label is given by the content of a 027 * tag whose name specified in the MapCSS style file</li> 028 * 029 * <li>{@link DeriveLabelFromNameTagsCompositionStrategy} - the label is given by the value 030 * of one 031 * of the configured "name tags". The list of relevant name tags can be configured 032 * in the JOSM preferences 033 * content of a tag whose name specified in the MapCSS style file, see the preference 034 * options <tt>mappaint.nameOrder</tt> and <tt>mappaint.nameComplementOrder</tt>.</li> 035 * </ul> 036 * @since 3987 (creation) 037 * @since 10599 (functional interface) 038 */ 039@FunctionalInterface 040public interface LabelCompositionStrategy { 041 042 /** 043 * Replies the text value to be rendered as label for the primitive {@code primitive}. 044 * 045 * @param primitive the primitive 046 * 047 * @return the text value to be rendered or null, if primitive is null or 048 * if no suitable value could be composed 049 */ 050 String compose(OsmPrimitive primitive); 051 052 class StaticLabelCompositionStrategy implements LabelCompositionStrategy { 053 private final String defaultLabel; 054 055 public StaticLabelCompositionStrategy(String defaultLabel) { 056 this.defaultLabel = defaultLabel; 057 } 058 059 @Override 060 public String compose(OsmPrimitive primitive) { 061 return defaultLabel; 062 } 063 064 public String getDefaultLabel() { 065 return defaultLabel; 066 } 067 068 @Override 069 public String toString() { 070 return '{' + getClass().getSimpleName() + " defaultLabel=" + defaultLabel + '}'; 071 } 072 073 @Override 074 public int hashCode() { 075 return Objects.hash(defaultLabel); 076 } 077 078 @Override 079 public boolean equals(Object obj) { 080 if (this == obj) return true; 081 if (obj == null || getClass() != obj.getClass()) return false; 082 StaticLabelCompositionStrategy that = (StaticLabelCompositionStrategy) obj; 083 return Objects.equals(defaultLabel, that.defaultLabel); 084 } 085 } 086 087 class TagLookupCompositionStrategy implements LabelCompositionStrategy { 088 089 private final String defaultLabelTag; 090 091 public TagLookupCompositionStrategy(String defaultLabelTag) { 092 if (defaultLabelTag != null) { 093 defaultLabelTag = defaultLabelTag.trim(); 094 if (defaultLabelTag.isEmpty()) { 095 defaultLabelTag = null; 096 } 097 } 098 this.defaultLabelTag = defaultLabelTag; 099 } 100 101 @Override 102 public String compose(OsmPrimitive primitive) { 103 if (defaultLabelTag == null) return null; 104 if (primitive == null) return null; 105 return primitive.get(defaultLabelTag); 106 } 107 108 public String getDefaultLabelTag() { 109 return defaultLabelTag; 110 } 111 112 @Override 113 public String toString() { 114 return '{' + getClass().getSimpleName() + " defaultLabelTag=" + defaultLabelTag + '}'; 115 } 116 117 @Override 118 public int hashCode() { 119 return Objects.hash(defaultLabelTag); 120 } 121 122 @Override 123 public boolean equals(Object obj) { 124 if (this == obj) return true; 125 if (obj == null || getClass() != obj.getClass()) return false; 126 TagLookupCompositionStrategy that = (TagLookupCompositionStrategy) obj; 127 return Objects.equals(defaultLabelTag, that.defaultLabelTag); 128 } 129 } 130 131 class DeriveLabelFromNameTagsCompositionStrategy implements LabelCompositionStrategy, PreferenceChangedListener { 132 133 /** 134 * The list of default name tags from which a label candidate is derived. 135 */ 136 private static final String[] DEFAULT_NAME_TAGS = { 137 "name:" + LanguageInfo.getJOSMLocaleCode(), 138 "name", 139 "int_name", 140 "distance", 141 "ref", 142 "operator", 143 "brand", 144 "addr:housenumber" 145 }; 146 147 /** 148 * The list of default name complement tags from which a label candidate is derived. 149 */ 150 private static final String[] DEFAULT_NAME_COMPLEMENT_TAGS = { 151 "capacity" 152 }; 153 154 private List<String> nameTags = new ArrayList<>(); 155 private List<String> nameComplementTags = new ArrayList<>(); 156 157 /** 158 * <p>Creates the strategy and initializes its name tags from the preferences.</p> 159 */ 160 public DeriveLabelFromNameTagsCompositionStrategy() { 161 initNameTagsFromPreferences(); 162 } 163 164 private static List<String> buildNameTags(List<String> nameTags) { 165 if (nameTags == null) { 166 nameTags = Collections.emptyList(); 167 } 168 List<String> result = new ArrayList<>(); 169 for (String tag: nameTags) { 170 if (tag == null) { 171 continue; 172 } 173 tag = tag.trim(); 174 if (tag.isEmpty()) { 175 continue; 176 } 177 result.add(tag); 178 } 179 return result; 180 } 181 182 /** 183 * Sets the name tags to be looked up in order to build up the label. 184 * 185 * @param nameTags the name tags. null values are ignored. 186 */ 187 public void setNameTags(List<String> nameTags) { 188 this.nameTags = buildNameTags(nameTags); 189 } 190 191 /** 192 * Sets the name complement tags to be looked up in order to build up the label. 193 * 194 * @param nameComplementTags the name complement tags. null values are ignored. 195 * @since 6541 196 */ 197 public void setNameComplementTags(List<String> nameComplementTags) { 198 this.nameComplementTags = buildNameTags(nameComplementTags); 199 } 200 201 /** 202 * Replies an unmodifiable list of the name tags used to compose the label. 203 * 204 * @return the list of name tags 205 */ 206 public List<String> getNameTags() { 207 return Collections.unmodifiableList(nameTags); 208 } 209 210 /** 211 * Replies an unmodifiable list of the name complement tags used to compose the label. 212 * 213 * @return the list of name complement tags 214 * @since 6541 215 */ 216 public List<String> getNameComplementTags() { 217 return Collections.unmodifiableList(nameComplementTags); 218 } 219 220 /** 221 * Initializes the name tags to use from a list of default name tags (see 222 * {@link #DEFAULT_NAME_TAGS} and {@link #DEFAULT_NAME_COMPLEMENT_TAGS}) 223 * and from name tags configured in the preferences using the keys 224 * <tt>mappaint.nameOrder</tt> and <tt>mappaint.nameComplementOrder</tt>. 225 */ 226 public final void initNameTagsFromPreferences() { 227 if (Main.pref == null) { 228 this.nameTags = new ArrayList<>(Arrays.asList(DEFAULT_NAME_TAGS)); 229 this.nameComplementTags = new ArrayList<>(Arrays.asList(DEFAULT_NAME_COMPLEMENT_TAGS)); 230 } else { 231 this.nameTags = new ArrayList<>( 232 Main.pref.getCollection("mappaint.nameOrder", Arrays.asList(DEFAULT_NAME_TAGS)) 233 ); 234 this.nameComplementTags = new ArrayList<>( 235 Main.pref.getCollection("mappaint.nameComplementOrder", Arrays.asList(DEFAULT_NAME_COMPLEMENT_TAGS)) 236 ); 237 } 238 } 239 240 private String getPrimitiveName(OsmPrimitive n) { 241 StringBuilder name = new StringBuilder(); 242 if (!n.hasKeys()) return null; 243 for (String rn : nameTags) { 244 String val = n.get(rn); 245 if (val != null) { 246 name.append(val); 247 break; 248 } 249 } 250 for (String rn : nameComplementTags) { 251 String comp = n.get(rn); 252 if (comp != null) { 253 if (name.length() == 0) { 254 name.append(comp); 255 } else { 256 name.append(" (").append(comp).append(')'); 257 } 258 break; 259 } 260 } 261 return name.toString(); 262 } 263 264 @Override 265 public String compose(OsmPrimitive primitive) { 266 if (primitive == null) return null; 267 return getPrimitiveName(primitive); 268 } 269 270 @Override 271 public String toString() { 272 return '{' + getClass().getSimpleName() + '}'; 273 } 274 275 @Override 276 public void preferenceChanged(PreferenceChangeEvent e) { 277 if (e.getKey() != null && e.getKey().startsWith("mappaint.name")) { 278 initNameTagsFromPreferences(); 279 } 280 } 281 } 282}