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