// License: GPL. For details, see LICENSE file.
package org.openstreetmap.josm.gui.mappaint.styleelement;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import org.openstreetmap.josm.Main;
import org.openstreetmap.josm.data.Preferences.PreferenceChangeEvent;
import org.openstreetmap.josm.data.Preferences.PreferenceChangedListener;
import org.openstreetmap.josm.data.osm.OsmPrimitive;
import org.openstreetmap.josm.tools.LanguageInfo;
/**
*
Provides an abstract parent class and three concrete sub classes for various
* strategies on how to compose the text label which can be rendered close to a node
* or within an area in an OSM map.
*
* The three strategies below support three rules for composing a label:
*
* - {@link StaticLabelCompositionStrategy} - the label is given by a static text
* specified in the MapCSS style file
*
* - {@link TagLookupCompositionStrategy} - the label is given by the content of a
* tag whose name specified in the MapCSS style file
*
* - {@link DeriveLabelFromNameTagsCompositionStrategy} - the label is given by the value
* of one of the configured "name tags". The list of relevant name tags can be configured
* in the JOSM preferences
* see the preference options mappaint.nameOrder and mappaint.nameComplementOrder.
*
* @since 3987 (creation)
* @since 10599 (functional interface)
*/
@FunctionalInterface
public interface LabelCompositionStrategy {
/**
* Replies the text value to be rendered as label for the primitive {@code primitive}.
*
* @param primitive the primitive
*
* @return the text value to be rendered or null, if primitive is null or
* if no suitable value could be composed
*/
String compose(OsmPrimitive primitive);
/**
* Strategy where the label is given by a static text specified in the MapCSS style file.
*/
class StaticLabelCompositionStrategy implements LabelCompositionStrategy {
private final String defaultLabel;
public StaticLabelCompositionStrategy(String defaultLabel) {
this.defaultLabel = defaultLabel;
}
@Override
public String compose(OsmPrimitive primitive) {
return defaultLabel;
}
public String getDefaultLabel() {
return defaultLabel;
}
@Override
public String toString() {
return '{' + getClass().getSimpleName() + " defaultLabel=" + defaultLabel + '}';
}
@Override
public int hashCode() {
return Objects.hash(defaultLabel);
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
StaticLabelCompositionStrategy that = (StaticLabelCompositionStrategy) obj;
return Objects.equals(defaultLabel, that.defaultLabel);
}
}
/**
* Strategy where the label is given by the content of a tag whose name specified in the MapCSS style file.
*/
class TagLookupCompositionStrategy implements LabelCompositionStrategy {
private final String defaultLabelTag;
public TagLookupCompositionStrategy(String defaultLabelTag) {
if (defaultLabelTag != null) {
defaultLabelTag = defaultLabelTag.trim();
if (defaultLabelTag.isEmpty()) {
defaultLabelTag = null;
}
}
this.defaultLabelTag = defaultLabelTag;
}
@Override
public String compose(OsmPrimitive primitive) {
if (defaultLabelTag == null) return null;
if (primitive == null) return null;
return primitive.get(defaultLabelTag);
}
public String getDefaultLabelTag() {
return defaultLabelTag;
}
@Override
public String toString() {
return '{' + getClass().getSimpleName() + " defaultLabelTag=" + defaultLabelTag + '}';
}
@Override
public int hashCode() {
return Objects.hash(defaultLabelTag);
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
TagLookupCompositionStrategy that = (TagLookupCompositionStrategy) obj;
return Objects.equals(defaultLabelTag, that.defaultLabelTag);
}
}
/**
* Strategy where the label is given by the value of one of the configured "name tags".
* The list of relevant name tags can be configured in the JOSM preferences
* see the preference options mappaint.nameOrder and mappaint.nameComplementOrder
*/
class DeriveLabelFromNameTagsCompositionStrategy implements LabelCompositionStrategy, PreferenceChangedListener {
/**
* The list of default name tags from which a label candidate is derived.
*/
private static final String[] DEFAULT_NAME_TAGS = {
"name:" + LanguageInfo.getJOSMLocaleCode(),
"name",
"int_name",
"distance",
"ref",
"operator",
"brand",
"addr:housenumber"
};
/**
* The list of default name complement tags from which a label candidate is derived.
*/
private static final String[] DEFAULT_NAME_COMPLEMENT_TAGS = {
"capacity"
};
private List nameTags = new ArrayList<>();
private List nameComplementTags = new ArrayList<>();
/**
* Creates the strategy and initializes its name tags from the preferences.
*/
public DeriveLabelFromNameTagsCompositionStrategy() {
initNameTagsFromPreferences();
}
private static List buildNameTags(List nameTags) {
List result = new ArrayList<>();
if (nameTags != null) {
for (String tag: nameTags) {
if (tag == null) {
continue;
}
tag = tag.trim();
if (tag.isEmpty()) {
continue;
}
result.add(tag);
}
}
return result;
}
/**
* Sets the name tags to be looked up in order to build up the label.
*
* @param nameTags the name tags. null values are ignored.
*/
public void setNameTags(List nameTags) {
this.nameTags = buildNameTags(nameTags);
}
/**
* Sets the name complement tags to be looked up in order to build up the label.
*
* @param nameComplementTags the name complement tags. null values are ignored.
* @since 6541
*/
public void setNameComplementTags(List nameComplementTags) {
this.nameComplementTags = buildNameTags(nameComplementTags);
}
/**
* Replies an unmodifiable list of the name tags used to compose the label.
*
* @return the list of name tags
*/
public List getNameTags() {
return Collections.unmodifiableList(nameTags);
}
/**
* Replies an unmodifiable list of the name complement tags used to compose the label.
*
* @return the list of name complement tags
* @since 6541
*/
public List getNameComplementTags() {
return Collections.unmodifiableList(nameComplementTags);
}
/**
* Initializes the name tags to use from a list of default name tags (see
* {@link #DEFAULT_NAME_TAGS} and {@link #DEFAULT_NAME_COMPLEMENT_TAGS})
* and from name tags configured in the preferences using the keys
* mappaint.nameOrder and mappaint.nameComplementOrder.
*/
public final void initNameTagsFromPreferences() {
if (Main.pref == null) {
this.nameTags = new ArrayList<>(Arrays.asList(DEFAULT_NAME_TAGS));
this.nameComplementTags = new ArrayList<>(Arrays.asList(DEFAULT_NAME_COMPLEMENT_TAGS));
} else {
this.nameTags = new ArrayList<>(
Main.pref.getCollection("mappaint.nameOrder", Arrays.asList(DEFAULT_NAME_TAGS))
);
this.nameComplementTags = new ArrayList<>(
Main.pref.getCollection("mappaint.nameComplementOrder", Arrays.asList(DEFAULT_NAME_COMPLEMENT_TAGS))
);
}
}
private String getPrimitiveName(OsmPrimitive n) {
StringBuilder name = new StringBuilder();
if (!n.hasKeys()) return null;
for (String rn : nameTags) {
String val = n.get(rn);
if (val != null) {
name.append(val);
break;
}
}
for (String rn : nameComplementTags) {
String comp = n.get(rn);
if (comp != null) {
if (name.length() == 0) {
name.append(comp);
} else {
name.append(" (").append(comp).append(')');
}
break;
}
}
return name.toString();
}
@Override
public String compose(OsmPrimitive primitive) {
if (primitive == null) return null;
return getPrimitiveName(primitive);
}
@Override
public String toString() {
return '{' + getClass().getSimpleName() + '}';
}
@Override
public void preferenceChanged(PreferenceChangeEvent e) {
if (e.getKey() != null && e.getKey().startsWith("mappaint.name")) {
initNameTagsFromPreferences();
}
}
}
}