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