Ticket #6107: mapcss-patch-3.txt

File mapcss-patch-3.txt, 47.4 KB (added by anonymous, 15 years ago)

Replaces former mapcss-patch-2.txt

Line 
1Index: src/org/openstreetmap/josm/data/osm/visitor/paint/MapPainter.java
2===================================================================
3--- src/org/openstreetmap/josm/data/osm/visitor/paint/MapPainter.java (revision 3986)
4+++ src/org/openstreetmap/josm/data/osm/visitor/paint/MapPainter.java (working copy)
5@@ -38,12 +38,11 @@
6 import org.openstreetmap.josm.gui.NavigatableComponent;
7 import org.openstreetmap.josm.gui.mappaint.NodeElemStyle;
8 import org.openstreetmap.josm.gui.mappaint.NodeElemStyle.HorizontalTextAlignment;
9-import org.openstreetmap.josm.gui.mappaint.NodeElemStyle.Symbol;
10 import org.openstreetmap.josm.gui.mappaint.NodeElemStyle.NodeTextElement;
11+import org.openstreetmap.josm.gui.mappaint.NodeElemStyle.Symbol;
12 import org.openstreetmap.josm.gui.mappaint.NodeElemStyle.VerticalTextAlignment;
13 import org.openstreetmap.josm.gui.mappaint.TextElement;
14 import org.openstreetmap.josm.tools.ImageProvider;
15-import org.openstreetmap.josm.tools.LanguageInfo;
16 import org.openstreetmap.josm.tools.Pair;
17
18 public class MapPainter {
19@@ -73,8 +72,6 @@
20
21 private final boolean leftHandTraffic;
22
23- private final Collection<String> regionalNameOrder;
24-
25 private static final double PHI = Math.toRadians(20);
26 private static final double cosPHI = Math.cos(PHI);
27 private static final double sinPHI = Math.sin(PHI);
28@@ -103,8 +100,6 @@
29 this.virtualNodeSpace = Main.pref.getInteger("mappaint.node.virtual-space", 70);
30 this.segmentNumberSpace = Main.pref.getInteger("mappaint.segmentnumber.space", 40);
31
32- String[] names = {"name:" + LanguageInfo.getJOSMLocaleCode(), "name", "int_name", "ref", "operator", "brand", "addr:housenumber"};
33- this.regionalNameOrder = Main.pref.getCollection("mappaint.nameOrder", Arrays.asList(names));
34 this.circum = circum;
35 this.leftHandTraffic = leftHandTraffic;
36 }
37@@ -117,7 +112,7 @@
38 * e.g. oneway street or waterway
39 * @param onewayReversed for oneway=-1 and similar
40 */
41- public void drawWay(Way way, Color color, BasicStroke line, BasicStroke dashes, Color dashedColor,
42+ public void drawWay(Way way, Color color, BasicStroke line, BasicStroke dashes, Color dashedColor,
43 TextElement text, boolean showOrientation, boolean showHeadArrowOnly,
44 boolean showOneway, boolean onewayReversed) {
45
46@@ -252,7 +247,7 @@
47 private void drawTextOnPath(Way way, TextElement text) {
48 if (text == null)
49 return;
50- String name = text.getString(way, this);
51+ String name = text.getString(way);
52 if (name == null || name.equals(""))
53 return;
54
55@@ -291,8 +286,8 @@
56 double tStart;
57
58 if (p1[0] < p2[0] &&
59- p1[2] < Math.PI/2 &&
60- p1[2] > -Math.PI/2) {
61+ p1[2] < Math.PI/2 &&
62+ p1[2] > -Math.PI/2) {
63 angleOffset = 0;
64 offsetSign = 1;
65 tStart = t1;
66@@ -346,8 +341,8 @@
67 continue;
68 }
69 return new double[] {poly.xpoints[i-1]+(totalLen - curLen)/segLen*dx,
70- poly.ypoints[i-1]+(totalLen - curLen)/segLen*dy,
71- Math.atan2(dy, dx)};
72+ poly.ypoints[i-1]+(totalLen - curLen)/segLen*dy,
73+ Math.atan2(dy, dx)};
74 }
75 return null;
76 }
77@@ -494,9 +489,12 @@
78 if (!isShowNames() || text == null)
79 return;
80
81- String s = text.textKey == null ? getNodeName(n) : n.get(text.textKey);
82- if (s == null)
83- return;
84+ /*
85+ * abort if we can't compose the label to be rendered
86+ */
87+ if (text.labelCompositionStrategy == null) return;
88+ String s = text.labelCompositionStrategy.compose(n);
89+ if (s == null) return;
90
91 Font defaultFont = g.getFont();
92 g.setFont(text.font);
93@@ -597,9 +595,12 @@
94 }
95
96 if (text != null && isShowNames()) {
97- String name = text.textKey == null ? getAreaName(osm) : osm.get(text.textKey);
98- if (name == null)
99- return;
100+ /*
101+ * abort if we can't compose the label to be rendered
102+ */
103+ if (text.labelCompositionStrategy == null) return;
104+ String name = text.labelCompositionStrategy.compose(osm);
105+ if (name == null) return;
106
107 Rectangle pb = polygon.getBounds();
108 FontMetrics fontMetrics = g.getFontMetrics(orderFont); // if slow, use cache
109@@ -930,34 +931,6 @@
110 }
111 }
112
113- //TODO Not a good place for this method
114- public String getNodeName(Node n) {
115- String name = null;
116- if (n.hasKeys()) {
117- for (String rn : regionalNameOrder) {
118- name = n.get(rn);
119- if (name != null) {
120- break;
121- }
122- }
123- }
124- return name;
125- }
126-
127- //TODO Not a good place for this method
128- public String getAreaName(OsmPrimitive w) {
129- String name = null;
130- if (w.hasKeys()) {
131- for (String rn : regionalNameOrder) {
132- name = w.get(rn);
133- if (name != null) {
134- break;
135- }
136- }
137- }
138- return name;
139- }
140-
141 public boolean isInactive() {
142 return inactive;
143 }
144Index: src/org/openstreetmap/josm/gui/mappaint/ElemStyle.java
145===================================================================
146--- src/org/openstreetmap/josm/gui/mappaint/ElemStyle.java (revision 3986)
147+++ src/org/openstreetmap/josm/gui/mappaint/ElemStyle.java (working copy)
148@@ -4,6 +4,8 @@
149 import static org.openstreetmap.josm.tools.Utils.equal;
150
151 import java.awt.Font;
152+import java.util.HashMap;
153+import java.util.Map;
154
155 import org.openstreetmap.josm.Main;
156 import org.openstreetmap.josm.data.osm.OsmPrimitive;
157@@ -16,7 +18,7 @@
158 public float z_index;
159 public float object_z_index;
160 public boolean isModifier; // false, if style can serve as main style for the
161- // primitive; true, if it is a highlight or modifier
162+ // primitive; true, if it is a highlight or modifier
163
164 public ElemStyle(float z_index, float object_z_index, boolean isModifier) {
165 this.z_index = z_index;
166@@ -59,11 +61,86 @@
167 return null;
168 }
169
170+ /* ------------------------------------------------------------------------------- */
171+ /* cached values */
172+ /* ------------------------------------------------------------------------------- */
173+ /*
174+ * Two preference values and the set of created fonts are cached in order to avoid
175+ * expensive lookups in the rendering loop and in order avoid too many font objects
176+ * (in analogy to flyweight pattern).
177+ *
178+ * FIXME: cached preference values are not updated if the user changes them during
179+ * a JOSM session. Should have a listener listening to preference changes.
180+ */
181+ static private String DEFAULT_FONT_NAME = null;
182+ static private Float DEFAULT_FONT_SIZE = null;
183+ static private void initDefaultFontParameters() {
184+ if (DEFAULT_FONT_NAME != null) return; // already initialized - skip initialization
185+ DEFAULT_FONT_NAME = Main.pref.get("mappaint.font", "Helvetica");
186+ DEFAULT_FONT_SIZE = (float) Main.pref.getInteger("mappaint.fontsize", 8);
187+ }
188+
189+ static private class FontDescriptor {
190+ public String name;
191+ public int style;
192+ public int size;
193+
194+ public FontDescriptor(String name, int style, int size){
195+ this.name = name;
196+ this.style = style;
197+ this.size = size;
198+ }
199+
200+ @Override
201+ public int hashCode() {
202+ final int prime = 31;
203+ int result = 1;
204+ result = prime * result + ((name == null) ? 0 : name.hashCode());
205+ result = prime * result + size;
206+ result = prime * result + style;
207+ return result;
208+ }
209+ @Override
210+ public boolean equals(Object obj) {
211+ if (this == obj)
212+ return true;
213+ if (obj == null)
214+ return false;
215+ if (getClass() != obj.getClass())
216+ return false;
217+ FontDescriptor other = (FontDescriptor) obj;
218+ if (name == null) {
219+ if (other.name != null)
220+ return false;
221+ } else if (!name.equals(other.name))
222+ return false;
223+ if (size != other.size)
224+ return false;
225+ if (style != other.style)
226+ return false;
227+ return true;
228+ }
229+ }
230+
231+ static private final Map<FontDescriptor, Font> FONT_MAP = new HashMap<FontDescriptor, Font>();
232+ static private Font getCachedFont(FontDescriptor fd) {
233+ Font f = FONT_MAP.get(fd);
234+ if (f != null) return f;
235+ f = new Font(fd.name, fd.style, fd.size);
236+ FONT_MAP.put(fd, f);
237+ return f;
238+ }
239+
240+ static private Font getCachedFont(String name, int style, int size){
241+ return getCachedFont(new FontDescriptor(name, style, size));
242+ }
243+
244 protected static Font getFont(Cascade c) {
245- String name = c.get("font-family", Main.pref.get("mappaint.font", "Helvetica"), String.class);
246- float size = c.get("font-size", (float) Main.pref.getInteger("mappaint.fontsize", 8), Float.class);
247+ initDefaultFontParameters(); // populated cached preferences, if necesary
248+ String name = c.get("font-family", DEFAULT_FONT_NAME, String.class);
249+ float size = c.get("font-size", DEFAULT_FONT_SIZE, Float.class);
250 int weight = Font.PLAIN;
251- Keyword weightKW = c.get("font-wheight", null, Keyword.class);
252+ Keyword weightKW = c.get("font-weight", null, Keyword.class);
253 if (weightKW != null && equal(weightKW, "bold")) {
254 weight = Font.BOLD;
255 }
256@@ -72,7 +149,7 @@
257 if (styleKW != null && equal(styleKW.val, "italic")) {
258 style = Font.ITALIC;
259 }
260- return new Font(name, weight | style, Math.round(size));
261+ return getCachedFont(name, style | weight, Math.round(size));
262 }
263
264 @Override
265Index: src/org/openstreetmap/josm/gui/mappaint/LabelCompositionStrategy.java
266===================================================================
267--- src/org/openstreetmap/josm/gui/mappaint/LabelCompositionStrategy.java (revision 0)
268+++ src/org/openstreetmap/josm/gui/mappaint/LabelCompositionStrategy.java (revision 0)
269@@ -0,0 +1,244 @@
270+// License: GPL. For details, see LICENSE file.
271+package org.openstreetmap.josm.gui.mappaint;
272+import java.util.ArrayList;
273+import java.util.Arrays;
274+import java.util.Collections;
275+import java.util.List;
276+
277+import org.openstreetmap.josm.Main;
278+import org.openstreetmap.josm.data.osm.OsmPrimitive;
279+import org.openstreetmap.josm.tools.LanguageInfo;
280+
281+/**
282+ * <p>Provides an abstract parent class and three concrete sub classes for various
283+ * strategies on how to compose the text label which can be rendered close to a node
284+ * or within an area in an OSM map.</p>
285+ *
286+ * <p>The three strategies below support three rules for composing a label:
287+ * <ul>
288+ * <li>{@link StaticLabelCompositionStrategy} - the label is given by a static text
289+ * specified in the MapCSS style file</li>
290+ *
291+ * <li>{@link TagLookupCompositionStrategy} - the label is given by the content of a
292+ * tag whose name specified in the MapCSS style file</li>
293+ *
294+ * <li>{@link DeriveLabelFromNameTagsCompositionStrategy} - the label is given by the value
295+ * of one
296+ * of the configured "name tags". The list of relevant name tags can be configured
297+ * in the JOSM preferences
298+ * content of a tag whose name specified in the MapCSS style file, see the preference
299+ * option <tt>mappaint.nameOrder</tt>.</li>
300+ * </ul>
301+ * </p>
302+ *
303+ */
304+public abstract class LabelCompositionStrategy {
305+
306+ static public class StaticLabelCompositionStrategy extends LabelCompositionStrategy {
307+ private String defaultLabel;
308+ public StaticLabelCompositionStrategy(String defaultLabel){
309+ this.defaultLabel = defaultLabel;
310+ }
311+
312+ @Override
313+ public String compose(OsmPrimitive primitive) {
314+ return defaultLabel;
315+ }
316+
317+ public String getDefaultLabel() {
318+ return defaultLabel;
319+ }
320+
321+ @Override
322+ public String toString() {
323+ return "{" + getClass().getSimpleName() + " defaultLabel=" + defaultLabel + "}";
324+ }
325+
326+ @Override
327+ public int hashCode() {
328+ final int prime = 31;
329+ int result = 1;
330+ result = prime * result + ((defaultLabel == null) ? 0 : defaultLabel.hashCode());
331+ return result;
332+ }
333+
334+ @Override
335+ public boolean equals(Object obj) {
336+ if (this == obj)
337+ return true;
338+ if (obj == null)
339+ return false;
340+ if (getClass() != obj.getClass())
341+ return false;
342+ StaticLabelCompositionStrategy other = (StaticLabelCompositionStrategy) obj;
343+ if (defaultLabel == null) {
344+ if (other.defaultLabel != null)
345+ return false;
346+ } else if (!defaultLabel.equals(other.defaultLabel))
347+ return false;
348+ return true;
349+ }
350+ }
351+
352+ static public class TagLookupCompositionStrategy extends LabelCompositionStrategy {
353+
354+ private String defaultLabelTag;
355+ public TagLookupCompositionStrategy(String defaultLabelTag){
356+ if (defaultLabelTag != null) {
357+ defaultLabelTag = defaultLabelTag.trim();
358+ if (defaultLabelTag.isEmpty()) {
359+ defaultLabelTag = null;
360+ }
361+ }
362+ this.defaultLabelTag = defaultLabelTag;
363+ }
364+
365+ @Override
366+ public String compose(OsmPrimitive primitive) {
367+ if (defaultLabelTag == null) return null;
368+ if (primitive == null) return null;
369+ return primitive.get(defaultLabelTag);
370+ }
371+
372+ public String getDefaultLabelTag() {
373+ return defaultLabelTag;
374+ }
375+
376+ @Override
377+ public String toString() {
378+ return "{" + getClass().getSimpleName() + " defaultLabelTag=" + defaultLabelTag + "}";
379+ }
380+
381+ @Override
382+ public int hashCode() {
383+ final int prime = 31;
384+ int result = 1;
385+ result = prime * result + ((defaultLabelTag == null) ? 0 : defaultLabelTag.hashCode());
386+ return result;
387+ }
388+
389+ @Override
390+ public boolean equals(Object obj) {
391+ if (this == obj)
392+ return true;
393+ if (obj == null)
394+ return false;
395+ if (getClass() != obj.getClass())
396+ return false;
397+ TagLookupCompositionStrategy other = (TagLookupCompositionStrategy) obj;
398+ if (defaultLabelTag == null) {
399+ if (other.defaultLabelTag != null)
400+ return false;
401+ } else if (!defaultLabelTag.equals(other.defaultLabelTag))
402+ return false;
403+ return true;
404+ }
405+ }
406+
407+ static public class DeriveLabelFromNameTagsCompositionStrategy extends LabelCompositionStrategy {
408+
409+ /**
410+ * The list of default name tags from which a label candidate is derived.
411+ */
412+ static public final String[] DEFAULT_NAME_TAGS = {
413+ "name:" + LanguageInfo.getJOSMLocaleCode(),
414+ "name",
415+ "int_name",
416+ "ref",
417+ "operator",
418+ "brand",
419+ "addr:housenumber"
420+ };
421+
422+ private List<String> nameTags = new ArrayList<String>();
423+
424+ /**
425+ * <p>Creates the strategy and initializes its name tags from the preferences.</p>
426+ *
427+ * <p><strong>Note:</strong> If the list of name tags in the preferences changes, strategy instances
428+ * are not notified. It's up to the client to listen to preference changes and
429+ * invoke {@link #initNameTagsFromPreferences()} accordingly.</p>
430+ *
431+ */
432+ public DeriveLabelFromNameTagsCompositionStrategy() {
433+ initNameTagsFromPreferences();
434+ }
435+
436+ /**
437+ * Sets the name tags to be looked up in order to build up the label
438+ *
439+ * @param nameTags the name tags. null values are ignore.
440+ */
441+ public void setNameTags(List<String> nameTags){
442+ if (nameTags == null) {
443+ nameTags = Collections.emptyList();
444+ }
445+ this.nameTags = new ArrayList<String>();
446+ for(String tag: nameTags) {
447+ if (tag == null) {
448+ continue;
449+ }
450+ tag = tag.trim();
451+ if (tag.isEmpty()) {
452+ continue;
453+ }
454+ this.nameTags.add(tag);
455+ }
456+ }
457+
458+ /**
459+ * Replies an unmodifiable list of the name tags used to compose the label.
460+ *
461+ * @return the list of name tags
462+ */
463+ public List<String> getNameTags() {
464+ return Collections.unmodifiableList(nameTags);
465+ }
466+
467+ /**
468+ * Initializes the name tags to use from a list of default name tags (see
469+ * {@link #DEFAULT_NAME_TAGS}) and from name tags configured in the preferences
470+ * using the preference key <tt>mappaint.nameOrder</tt>.
471+ */
472+ public void initNameTagsFromPreferences() {
473+ if (Main.pref == null){
474+ this.nameTags = new ArrayList<String>(Arrays.asList(DEFAULT_NAME_TAGS));
475+ } else {
476+ this.nameTags = new ArrayList<String>(
477+ Main.pref.getCollection("mappaint.nameOrder", Arrays.asList(DEFAULT_NAME_TAGS))
478+ );
479+ }
480+ }
481+
482+ private String getPrimitiveName(OsmPrimitive n) {
483+ String name = null;
484+ if (!n.hasKeys()) return null;
485+ for (String rn : nameTags) {
486+ name = n.get(rn);
487+ if (name != null) return name;
488+ }
489+ return null;
490+ }
491+
492+ @Override
493+ public String compose(OsmPrimitive primitive) {
494+ if (primitive == null) return null;
495+ return getPrimitiveName(primitive);
496+ }
497+
498+ @Override
499+ public String toString() {
500+ return "{" + getClass().getSimpleName() +"}";
501+ }
502+ }
503+
504+ /**
505+ * Replies the text value to be rendered as label for the primitive {@code primitive}.
506+ *
507+ * @param primitive the primitive
508+ *
509+ * @return the text value to be rendered or null, if primitive is null or
510+ * if no suitable value could be composed
511+ */
512+ abstract public String compose(OsmPrimitive primitive);
513+}
514Index: src/org/openstreetmap/josm/gui/mappaint/MultiCascade.java
515===================================================================
516--- src/org/openstreetmap/josm/gui/mappaint/MultiCascade.java (revision 3986)
517+++ src/org/openstreetmap/josm/gui/mappaint/MultiCascade.java (working copy)
518@@ -6,13 +6,15 @@
519 import java.util.Map;
520 import java.util.Map.Entry;
521
522+import org.openstreetmap.josm.tools.CheckParameterUtil;
523+
524 /**
525 * Several layers / cascades, e.g. one for the main Line and one for each overlay.
526 * The range is (0,Infinity) at first and it shrinks in the process when
527 * StyleSources apply zoom level dependent properties.
528 */
529 public class MultiCascade {
530-
531+
532 private Map<String, Cascade> layers;
533 public Range range;
534
535@@ -27,8 +29,7 @@
536 * a clone of the "*" layer, if it exists.
537 */
538 public Cascade getOrCreateCascade(String layer) {
539- if (layer == null)
540- throw new IllegalArgumentException();
541+ CheckParameterUtil.ensureParameterNotNull(layer);
542 Cascade c = layers.get(layer);
543 if (c == null) {
544 if (layers.containsKey("*")) {
545Index: src/org/openstreetmap/josm/gui/mappaint/NodeElemStyle.java
546===================================================================
547--- src/org/openstreetmap/josm/gui/mappaint/NodeElemStyle.java (revision 3986)
548+++ src/org/openstreetmap/josm/gui/mappaint/NodeElemStyle.java (working copy)
549@@ -25,6 +25,7 @@
550 * applies for Nodes and turn restriction relations
551 */
552 public class NodeElemStyle extends ElemStyle {
553+ //static private final Logger logger = Logger.getLogger(NodeElemStyle.class.getName());
554
555 public ImageIcon icon;
556 public int iconAlpha;
557@@ -62,10 +63,10 @@
558 return false;
559 final Symbol other = (Symbol) obj;
560 return symbol == other.symbol &&
561- size == other.size &&
562- equal(stroke, other.stroke) &&
563- equal(strokeColor, other.strokeColor) &&
564- equal(fillColor, other.fillColor);
565+ size == other.size &&
566+ equal(stroke, other.stroke) &&
567+ equal(strokeColor, other.strokeColor) &&
568+ equal(fillColor, other.fillColor);
569 }
570
571 @Override
572@@ -82,8 +83,8 @@
573 @Override
574 public String toString() {
575 return "symbol=" + symbol + " size=" + size +
576- (stroke != null ? (" stroke=" + stroke + " strokeColor=" + strokeColor) : "") +
577- (fillColor != null ? (" fillColor=" + fillColor) : "");
578+ (stroke != null ? (" stroke=" + stroke + " strokeColor=" + strokeColor) : "") +
579+ (fillColor != null ? (" fillColor=" + fillColor) : "");
580 }
581 }
582
583@@ -107,7 +108,7 @@
584 return false;
585 final NodeTextElement other = (NodeTextElement) obj;
586 return hAlign == other.hAlign &&
587- vAlign == other.vAlign;
588+ vAlign == other.vAlign;
589 }
590
591 @Override
592@@ -140,7 +141,21 @@
593 return create(env, false);
594 }
595
596+ /*
597+ * Caches the default text color from the preferences in order to avoid expensive
598+ * lookup operations in the rendering loop.
599+ *
600+ * FIXME: the cache isn't updated if the user changes the preference during a JOSM
601+ * session. There should be preference listener updating this cache.
602+ */
603+ static private Color DEFAULT_TEXT_COLOR = null;
604+ static private void initDefaultParameters() {
605+ if (DEFAULT_TEXT_COLOR != null) return;
606+ DEFAULT_TEXT_COLOR = PaintColors.TEXT.get();
607+ }
608+
609 private static NodeElemStyle create(Environment env, boolean allowOnlyText) {
610+ initDefaultParameters();
611 Cascade c = env.mc.getCascade(env.layer);
612
613 IconReference iconRef = c.get("icon-image", null, IconReference.class);
614@@ -162,11 +177,14 @@
615 symbol = createSymbol(env);
616 }
617
618- if (icon == null && symbol == null && !allowOnlyText)
619- return null;
620+ NodeTextElement text = null;
621+ TextElement te = TextElement.create(c, DEFAULT_TEXT_COLOR);
622+ // optimization: if we neither have a symbol, nor an icon, nor a text element
623+ // we don't have to check for the remaining style properties and we don't
624+ // have to allocate a node element style. It's important to optimize here
625+ // because this method is invoked in the rendering loop.
626+ if (symbol == null && icon == null && te == null) return null;
627
628- NodeTextElement text = null;
629- TextElement te = TextElement.create(c, PaintColors.TEXT.get());
630 if (te != null) {
631 HorizontalTextAlignment hAlign = HorizontalTextAlignment.RIGHT;
632 Keyword hAlignKW = c.get("text-anchor-horizontal", Keyword.RIGHT, Keyword.class);
633@@ -192,7 +210,7 @@
634 }
635 text = new NodeTextElement(te, hAlign, vAlign);
636 }
637-
638+
639 return new NodeElemStyle(c, icon, iconAlpha, symbol, text);
640 }
641
642@@ -224,7 +242,7 @@
643 shape = SymbolShape.DECAGON;
644 } else
645 return null;
646-
647+
648 Float sizeOnDefault = c_def.get("symbol-size", null, Float.class);
649 if (sizeOnDefault != null && sizeOnDefault <= 0) {
650 sizeOnDefault = null;
651@@ -258,8 +276,9 @@
652 }
653
654 Color fillColor = c.get("symbol-fill-color", null, Color.class);
655- if (stroke == null && fillColor == null)
656+ if (stroke == null && fillColor == null) {
657 fillColor = Color.BLUE;
658+ }
659
660 if (fillColor != null) {
661 float fillAlpha = c.get("symbol-fill-opacity", 1f, Float.class);
662@@ -335,14 +354,14 @@
663 }
664
665 final int size = Utils.max((selected ? settings.getSelectedNodeSize() : 0),
666- (n.isTagged() ? settings.getTaggedNodeSize() : 0),
667- (isConnection ? settings.getConnectionNodeSize() : 0),
668- settings.getUnselectedNodeSize());
669+ (n.isTagged() ? settings.getTaggedNodeSize() : 0),
670+ (isConnection ? settings.getConnectionNodeSize() : 0),
671+ settings.getUnselectedNodeSize());
672
673 final boolean fill = (selected && settings.isFillSelectedNode()) ||
674- (n.isTagged() && settings.isFillTaggedNode()) ||
675- (isConnection && settings.isFillConnectionNode()) ||
676- settings.isFillUnselectedNode();
677+ (n.isTagged() && settings.isFillTaggedNode()) ||
678+ (isConnection && settings.isFillConnectionNode()) ||
679+ settings.isFillUnselectedNode();
680
681 painter.drawNode(n, color, size, fill, text);
682 }
683@@ -394,8 +413,8 @@
684 @Override
685 public String toString() {
686 return "NodeElemStyle{" + super.toString() +
687- (icon != null ? ("icon=" + icon + " iconAlpha=" + iconAlpha) : "") +
688- (symbol != null ? (" symbol=[" + symbol + "]") : "") + '}';
689+ (icon != null ? ("icon=" + icon + " iconAlpha=" + iconAlpha) : "") +
690+ (symbol != null ? (" symbol=[" + symbol + "]") : "") + '}';
691 }
692
693 }
694Index: src/org/openstreetmap/josm/gui/mappaint/TextElement.java
695===================================================================
696--- src/org/openstreetmap/josm/gui/mappaint/TextElement.java (revision 3986)
697+++ src/org/openstreetmap/josm/gui/mappaint/TextElement.java (working copy)
698@@ -1,31 +1,52 @@
699 // License: GPL. For details, see LICENSE file.
700 package org.openstreetmap.josm.gui.mappaint;
701
702-import static org.openstreetmap.josm.tools.Utils.equal;
703-
704 import java.awt.Color;
705 import java.awt.Font;
706+
707 import org.openstreetmap.josm.data.osm.OsmPrimitive;
708-import org.openstreetmap.josm.data.osm.visitor.paint.MapPainter;
709-
710+import org.openstreetmap.josm.gui.mappaint.LabelCompositionStrategy.DeriveLabelFromNameTagsCompositionStrategy;
711+import org.openstreetmap.josm.gui.mappaint.LabelCompositionStrategy.TagLookupCompositionStrategy;
712 import org.openstreetmap.josm.tools.CheckParameterUtil;
713 import org.openstreetmap.josm.tools.Utils;
714
715+/**
716+ * Represents the rendering style for a textual label placed somewhere on the map.
717+ *
718+ */
719 public class TextElement {
720- // textKey == null means automatic generation of text string, otherwise
721- // the corresponding tag value is used
722- public String textKey;
723+ //static private final Logger logger = Logger.getLogger(TextElement.class.getName());
724+
725+ static private final LabelCompositionStrategy AUTO_LABEL_COMPOSITION_STRATEGY = new DeriveLabelFromNameTagsCompositionStrategy();
726+
727+ /** the font to be used when rendering*/
728 public Font font;
729 public int xOffset;
730 public int yOffset;
731 public Color color;
732 public Float haloRadius;
733 public Color haloColor;
734+ /** the strategy for building the actual label value for a given a {@link OsmPrimitive}.
735+ * Check for null before accessing.
736+ */
737+ public LabelCompositionStrategy labelCompositionStrategy;
738
739- public TextElement(String textKey, Font font, int xOffset, int yOffset, Color color, Float haloRadius, Color haloColor) {
740+ /**
741+ * Creates a new text element
742+ *
743+ * @param strategy the strategy indicating how the text is composed for a specific {@link OsmPrimitive} to be rendered.
744+ * If null, no label is rendered.
745+ * @param font the font to be used. Must not be null.
746+ * @param xOffset
747+ * @param yOffset
748+ * @param color the color to be used. Must not be null
749+ * @param haloRadius
750+ * @param haloColor
751+ */
752+ public TextElement(LabelCompositionStrategy strategy, Font font, int xOffset, int yOffset, Color color, Float haloRadius, Color haloColor) {
753 CheckParameterUtil.ensureParameterNotNull(font);
754 CheckParameterUtil.ensureParameterNotNull(color);
755- this.textKey = textKey;
756+ labelCompositionStrategy = strategy;
757 this.font = font;
758 this.xOffset = xOffset;
759 this.yOffset = yOffset;
760@@ -34,8 +55,13 @@
761 this.haloColor = haloColor;
762 }
763
764+ /**
765+ * Copy constructor
766+ *
767+ * @param other the other element.
768+ */
769 public TextElement(TextElement other) {
770- this.textKey = other.textKey;
771+ this.labelCompositionStrategy = other.labelCompositionStrategy;
772 this.font = other.font;
773 this.xOffset = other.xOffset;
774 this.yOffset = other.yOffset;
775@@ -44,17 +70,39 @@
776 this.haloRadius = other.haloRadius;
777 }
778
779- public static TextElement create(Cascade c, Color defTextColor) {
780-
781- String textKey = null;
782+ /**
783+ * Derives a suitable label composition strategy from the style properties in
784+ * {@code c}.
785+ *
786+ * @param c the style properties
787+ * @return the label composition strategy
788+ */
789+ protected static LabelCompositionStrategy buildLabelCompositionStrategy(Cascade c){
790 Keyword textKW = c.get("text", null, Keyword.class, true);
791 if (textKW == null) {
792- textKey = c.get("text", null, String.class);
793- if (textKey == null)
794- return null;
795- } else if (!textKW.val.equals("auto"))
796- return null;
797+ String textKey = c.get("text", null, String.class);
798+ if (textKey == null) return null;
799+ return new TagLookupCompositionStrategy(textKey);
800+ } else if (textKW.val.equals("auto"))
801+ return AUTO_LABEL_COMPOSITION_STRATEGY;
802+ else
803+ return new TagLookupCompositionStrategy(textKW.val);
804+ }
805
806+ /**
807+ * Builds a text element from style properties in {@code c} and the
808+ * default text color {@code defaultTextColor}
809+ *
810+ * @param c the style properties
811+ * @param defaultTextColor the default text color. Must not be null.
812+ * @return the text element or null, if the style properties don't include
813+ * properties for text rendering
814+ * @throws IllegalArgumentException thrown if {@code defaultTextColor} is null
815+ */
816+ public static TextElement create(Cascade c, Color defaultTextColor) throws IllegalArgumentException{
817+ CheckParameterUtil.ensureParameterNotNull(defaultTextColor);
818+
819+ LabelCompositionStrategy strategy = buildLabelCompositionStrategy(c);
820 Font font = ElemStyle.getFont(c);
821
822 float xOffset = 0;
823@@ -70,8 +118,8 @@
824 }
825 xOffset = c.get("text-offset-x", xOffset, Float.class);
826 yOffset = c.get("text-offset-y", yOffset, Float.class);
827-
828- Color color = c.get("text-color", defTextColor, Color.class);
829+
830+ Color color = c.get("text-color", defaultTextColor, Color.class);
831 float alpha = c.get("text-opacity", 1f, Float.class);
832 color = new Color(color.getRed(), color.getGreen(),
833 color.getBlue(), Utils.color_float2int(alpha));
834@@ -88,42 +136,86 @@
835 haloColor.getBlue(), Utils.color_float2int(haloAlpha));
836 }
837
838- return new TextElement(textKey, font, (int) xOffset, - (int) yOffset, color, haloRadius, haloColor);
839+ return new TextElement(strategy, font, (int) xOffset, - (int) yOffset, color, haloRadius, haloColor);
840 }
841
842+ /**
843+ * Replies the label to be rendered for the primitive {@code osm}.
844+ *
845+ * @param osm the the OSM object
846+ * @return the label, or null, if {@code osm} is null or if no label can be
847+ * derived for {@code osm}
848+ */
849+ public String getString(OsmPrimitive osm) {
850+ if (labelCompositionStrategy == null) return null;
851+ return labelCompositionStrategy.compose(osm);
852+ }
853+
854 @Override
855- public boolean equals(Object obj) {
856- if (obj == null || getClass() != obj.getClass())
857- return false;
858- final TextElement other = (TextElement) obj;
859- return equal(textKey, other.textKey) &&
860- equal(font, other.font) &&
861- xOffset == other.xOffset &&
862- yOffset == other.yOffset &&
863- equal(color, other.color) &&
864- equal(haloRadius, other.haloRadius) &&
865- equal(haloColor, other.haloColor);
866+ public String toString() {
867+ StringBuilder sb = new StringBuilder();
868+ sb.append("{TextElement ");
869+ sb.append("strategy=");
870+ sb.append(labelCompositionStrategy == null ? "null" : labelCompositionStrategy.toString());
871+ sb.append("}");
872+ return sb.toString();
873 }
874
875+ /* -------------------------------------------------------------------------------- */
876+ /* equals and hashCode (generated by Eclipse, regenerate if necessary) */
877+ /* -------------------------------------------------------------------------------- */
878 @Override
879 public int hashCode() {
880- int hash = 3;
881- hash = 79 * hash + (textKey != null ? textKey.hashCode() : 0);
882- hash = 79 * hash + font.hashCode();
883- hash = 79 * hash + xOffset;
884- hash = 79 * hash + yOffset;
885- hash = 79 * hash + color.hashCode();
886- hash = 79 * hash + (haloRadius != null ? Float.floatToIntBits(haloRadius) : 0);
887- hash = 79 * hash + (haloColor != null ? haloColor.hashCode() : 0);
888- return hash;
889+ final int prime = 31;
890+ int result = 1;
891+ result = prime * result + ((color == null) ? 0 : color.hashCode());
892+ result = prime * result + ((font == null) ? 0 : font.hashCode());
893+ result = prime * result + ((haloColor == null) ? 0 : haloColor.hashCode());
894+ result = prime * result + ((haloRadius == null) ? 0 : haloRadius.hashCode());
895+ result = prime * result + ((labelCompositionStrategy == null) ? 0 : labelCompositionStrategy.hashCode());
896+ result = prime * result + xOffset;
897+ result = prime * result + yOffset;
898+ return result;
899 }
900
901- public String getString(OsmPrimitive osm, MapPainter painter) {
902- if (textKey == null)
903- return painter.getAreaName(osm);
904- else
905- return osm.get(textKey);
906+ @Override
907+ public boolean equals(Object obj) {
908+ if (this == obj)
909+ return true;
910+ if (obj == null)
911+ return false;
912+ if (getClass() != obj.getClass())
913+ return false;
914+ TextElement other = (TextElement) obj;
915+ if (color == null) {
916+ if (other.color != null)
917+ return false;
918+ } else if (!color.equals(other.color))
919+ return false;
920+ if (font == null) {
921+ if (other.font != null)
922+ return false;
923+ } else if (!font.equals(other.font))
924+ return false;
925+ if (haloColor == null) {
926+ if (other.haloColor != null)
927+ return false;
928+ } else if (!haloColor.equals(other.haloColor))
929+ return false;
930+ if (haloRadius == null) {
931+ if (other.haloRadius != null)
932+ return false;
933+ } else if (!haloRadius.equals(other.haloRadius))
934+ return false;
935+ if (labelCompositionStrategy == null) {
936+ if (other.labelCompositionStrategy != null)
937+ return false;
938+ } else if (!labelCompositionStrategy.equals(other.labelCompositionStrategy))
939+ return false;
940+ if (xOffset != other.xOffset)
941+ return false;
942+ if (yOffset != other.yOffset)
943+ return false;
944+ return true;
945 }
946-
947-
948 }
949Index: src/org/openstreetmap/josm/gui/mappaint/mapcss/MapCSSStyleSource.java
950===================================================================
951--- src/org/openstreetmap/josm/gui/mappaint/mapcss/MapCSSStyleSource.java (revision 3986)
952+++ src/org/openstreetmap/josm/gui/mappaint/mapcss/MapCSSStyleSource.java (working copy)
953@@ -27,7 +27,8 @@
954 import org.openstreetmap.josm.tools.Utils;
955
956 public class MapCSSStyleSource extends StyleSource {
957-
958+ //static private final Logger logger = Logger.getLogger(MapCSSStyleSource.class.getName());
959+
960 final public List<MapCSSRule> rules;
961 private Color backgroundColorOverride;
962
963@@ -65,6 +66,7 @@
964 }
965 }
966
967+ @Override
968 public InputStream getSourceInputStream() throws IOException {
969 MirroredInputStream in = new MirroredInputStream(url);
970 InputStream zip = in.getZipEntry("mapcss", "style");
971@@ -107,26 +109,28 @@
972 Environment env = new Environment(n, mc, "default", this);
973
974 NEXT_RULE:
975- for (MapCSSRule r : rules) {
976- for (Selector s : r.selectors) {
977- if ((s instanceof GeneralSelector)) {
978- GeneralSelector gs = (GeneralSelector) s;
979- if (gs.base.equals(type))
980- {
981- for (Condition cnd : gs.conds) {
982- if (!cnd.applies(env))
983- continue NEXT_RULE;
984+ for (MapCSSRule r : rules) {
985+ for (Selector s : r.selectors) {
986+ if ((s instanceof GeneralSelector)) {
987+ GeneralSelector gs = (GeneralSelector) s;
988+ if (gs.base.equals(type))
989+ {
990+ for (Condition cnd : gs.conds) {
991+ if (!cnd.applies(env)) {
992+ continue NEXT_RULE;
993+ }
994+ }
995+ for (Instruction i : r.declaration) {
996+ i.execute(env);
997+ }
998 }
999- for (Instruction i : r.declaration) {
1000- i.execute(env);
1001- }
1002 }
1003 }
1004 }
1005- }
1006 return mc.getCascade("default");
1007 }
1008
1009+ @Override
1010 public Color getBackgroundColorOverride() {
1011 return backgroundColorOverride;
1012 }
1013@@ -159,7 +163,7 @@
1014 i.execute(env);
1015 }
1016 }
1017- }
1018+ }
1019 env.layer = sub;
1020 for (Instruction i : r.declaration) {
1021 i.execute(env);
1022Index: test/data/styles/label-from-tag.mapcss
1023===================================================================
1024--- test/data/styles/label-from-tag.mapcss (revision 0)
1025+++ test/data/styles/label-from-tag.mapcss (revision 0)
1026@@ -0,0 +1,20 @@
1027+/*
1028+ * Simple test style sheet. Includes a style for nodes where the label is derived
1029+ * from the value of a specific tag.
1030+ *
1031+ */
1032+
1033+meta {
1034+ title: "Test style - Deriving labels from tags";
1035+}
1036+
1037+canvas {
1038+ background-color: #000000;
1039+}
1040+
1041+node {
1042+ text: my_label_tag; /* take the value of the tag 'my_label_tag' as text */
1043+ text-color: white;
1044+ font-size: 12;
1045+}
1046+
1047Index: test/functional/org/openstreetmap/josm/fixtures/JOSMFixture.java
1048===================================================================
1049--- test/functional/org/openstreetmap/josm/fixtures/JOSMFixture.java (revision 3986)
1050+++ test/functional/org/openstreetmap/josm/fixtures/JOSMFixture.java (working copy)
1051@@ -10,7 +10,7 @@
1052 import java.util.logging.Logger;
1053
1054 import org.openstreetmap.josm.Main;
1055-import org.openstreetmap.josm.data.osm.DataSetMergerTest;
1056+import org.openstreetmap.josm.data.Preferences;
1057 import org.openstreetmap.josm.data.projection.Mercator;
1058 import org.openstreetmap.josm.io.OsmApi;
1059 import org.openstreetmap.josm.tools.I18n;
1060@@ -39,7 +39,7 @@
1061 // load properties
1062 //
1063 try {
1064- testProperties.load(DataSetMergerTest.class.getResourceAsStream(testPropertiesResourceName));
1065+ testProperties.load(JOSMFixture.class.getResourceAsStream(testPropertiesResourceName));
1066 } catch(Exception e){
1067 logger.log(Level.SEVERE, MessageFormat.format("failed to load property file ''{0}''", testPropertiesResourceName));
1068 fail(MessageFormat.format("failed to load property file ''{0}''. \nMake sure the path ''$project_root/test/config'' is on the classpath.", testPropertiesResourceName));
1069@@ -57,6 +57,7 @@
1070 }
1071 }
1072 System.setProperty("josm.home", josmHome);
1073+ Main.pref = new Preferences();
1074 I18n.init();
1075 // initialize the plaform hook, and
1076 Main.determinePlatformHook();
1077Index: test/unit/org/openstreetmap/josm/gui/mappaint/AllMappaintTests.groovy
1078===================================================================
1079--- test/unit/org/openstreetmap/josm/gui/mappaint/AllMappaintTests.groovy (revision 0)
1080+++ test/unit/org/openstreetmap/josm/gui/mappaint/AllMappaintTests.groovy (revision 0)
1081@@ -0,0 +1,15 @@
1082+// License: GPL. For details, see LICENSE file.
1083+package org.openstreetmap.josm.gui.mappaint
1084+
1085+import junit.framework.TestCase;
1086+
1087+import org.junit.runner.RunWith;
1088+import org.junit.runners.Suite;
1089+
1090+@RunWith(Suite.class)
1091+@Suite.SuiteClasses([
1092+ LabelCompositionStrategyTest.class,
1093+ MapCSSWithExtendedTextDirectivesTest.class
1094+])
1095+public class AllMappaintTests extends TestCase{}
1096+
1097Index: test/unit/org/openstreetmap/josm/gui/mappaint/LabelCompositionStrategyTest.groovy
1098===================================================================
1099--- test/unit/org/openstreetmap/josm/gui/mappaint/LabelCompositionStrategyTest.groovy (revision 0)
1100+++ test/unit/org/openstreetmap/josm/gui/mappaint/LabelCompositionStrategyTest.groovy (revision 0)
1101@@ -0,0 +1,66 @@
1102+// License: GPL. For details, see LICENSE file.
1103+package org.openstreetmap.josm.gui.mappaint
1104+
1105+import org.junit.*
1106+import org.openstreetmap.josm.fixtures.JOSMFixture;
1107+import org.openstreetmap.josm.gui.mappaint.LabelCompositionStrategy.DeriveLabelFromNameTagsCompositionStrategy
1108+import org.openstreetmap.josm.gui.mappaint.LabelCompositionStrategy.StaticLabelCompositionStrategy;
1109+import org.openstreetmap.josm.gui.mappaint.LabelCompositionStrategy.TagLookupCompositionStrategy
1110+import org.openstreetmap.josm.data.osm.Node;
1111+
1112+class LabelCompositionStrategyTest {
1113+
1114+ @BeforeClass
1115+ public static void createJOSMFixture(){
1116+ JOSMFixture.createUnitTestFixture().init()
1117+ }
1118+
1119+ @Test
1120+ public void createStaticLabelCompositionStrategy() {
1121+ def n = new Node()
1122+
1123+ def strat = new StaticLabelCompositionStrategy(null)
1124+ assert strat.compose(n) == null
1125+
1126+ strat = new StaticLabelCompositionStrategy("a label")
1127+ assert strat.compose(n) == "a label"
1128+ }
1129+
1130+ @Test
1131+ public void createTagLookupCompositionStrategy() {
1132+ def n = new Node()
1133+ n.put("my-tag", "my-value")
1134+
1135+ def strat = new TagLookupCompositionStrategy(null)
1136+ assert strat.compose(n) == null
1137+
1138+ strat = new TagLookupCompositionStrategy("name")
1139+ assert strat.compose(n) == null
1140+
1141+ strat = new TagLookupCompositionStrategy("my-tag")
1142+ assert strat.compose(n) == "my-value"
1143+ }
1144+
1145+ @Test
1146+ public void createDeriveLabelFromNameTagsCompositionStrategy() {
1147+ def n
1148+ def strat
1149+
1150+ strat = new DeriveLabelFromNameTagsCompositionStrategy()
1151+ strat.setNameTags(null)
1152+ assert strat.getNameTags() == []
1153+
1154+ strat = new DeriveLabelFromNameTagsCompositionStrategy()
1155+ strat.setNameTags(["name", "brand"])
1156+ assert strat.getNameTags() == ["name", "brand"]
1157+
1158+ n = new Node()
1159+ n.put("brand", "my brand")
1160+ assert strat.compose(n) == "my brand"
1161+
1162+ n = new Node()
1163+ n.put("name", "my name")
1164+ n.put("brand", "my brand")
1165+ assert strat.compose(n) == "my name"
1166+ }
1167+}
1168Index: test/unit/org/openstreetmap/josm/gui/mappaint/MapCSSWithExtendedTextDirectivesTest.groovy
1169===================================================================
1170--- test/unit/org/openstreetmap/josm/gui/mappaint/MapCSSWithExtendedTextDirectivesTest.groovy (revision 0)
1171+++ test/unit/org/openstreetmap/josm/gui/mappaint/MapCSSWithExtendedTextDirectivesTest.groovy (revision 0)
1172@@ -0,0 +1,58 @@
1173+// License: GPL. For details, see LICENSE file.
1174+package org.openstreetmap.josm.gui.mappaint
1175+
1176+import java.awt.Color;
1177+
1178+import org.junit.*;
1179+import org.openstreetmap.josm.fixtures.JOSMFixture
1180+import org.openstreetmap.josm.gui.mappaint.LabelCompositionStrategy.DeriveLabelFromNameTagsCompositionStrategy
1181+import org.openstreetmap.josm.gui.mappaint.LabelCompositionStrategy.StaticLabelCompositionStrategy
1182+import org.openstreetmap.josm.gui.mappaint.LabelCompositionStrategy.TagLookupCompositionStrategy
1183+class MapCSSWithExtendedTextDirectivesTest {
1184+
1185+
1186+ @BeforeClass
1187+ public static void createJOSMFixture(){
1188+ JOSMFixture.createUnitTestFixture().init()
1189+ }
1190+
1191+ @Test
1192+ public void createAutoTextElement() {
1193+ Cascade c = new Cascade()
1194+ c.put("text", new Keyword("auto"))
1195+
1196+ TextElement te = TextElement.create(c, Color.WHITE)
1197+ assert te.labelCompositionStrategy != null
1198+ assert te.labelCompositionStrategy instanceof DeriveLabelFromNameTagsCompositionStrategy
1199+ }
1200+
1201+ @Test
1202+ public void createTextElementComposingTextFromTag() {
1203+ Cascade c = new Cascade()
1204+ c.put("text", "my_name")
1205+
1206+ TextElement te = TextElement.create(c, Color.WHITE)
1207+ assert te.labelCompositionStrategy != null
1208+ assert te.labelCompositionStrategy instanceof TagLookupCompositionStrategy
1209+ assert te.labelCompositionStrategy.getDefaultLabelTag() == "my_name"
1210+ }
1211+
1212+ @Test
1213+ public void createTextElementComposingTextFromTag_2() {
1214+ Cascade c = new Cascade()
1215+ c.put("text", new Keyword("my_name"))
1216+
1217+ TextElement te = TextElement.create(c, Color.WHITE)
1218+ assert te.labelCompositionStrategy != null
1219+ assert te.labelCompositionStrategy instanceof TagLookupCompositionStrategy
1220+ assert te.labelCompositionStrategy.getDefaultLabelTag() == "my_name"
1221+ }
1222+
1223+ @Test
1224+ public void createNullStrategy() {
1225+ Cascade c = new Cascade()
1226+
1227+ TextElement te = TextElement.create(c, Color.WHITE)
1228+ assert te.labelCompositionStrategy == null
1229+ }
1230+}