source: josm/trunk/src/org/openstreetmap/josm/gui/mappaint/styleelement/LineElement.java @ 11809

Last change on this file since 11809 was 11809, checked in by Don-vip, 9 months ago

findbugs - BC_UNCONFIRMED_CAST

  • Property svn:eol-style set to native
File size: 16.1 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.mappaint.styleelement;
3
4import java.awt.BasicStroke;
5import java.awt.Color;
6import java.util.Arrays;
7import java.util.Objects;
8import java.util.Optional;
9
10import org.openstreetmap.josm.Main;
11import org.openstreetmap.josm.data.osm.Node;
12import org.openstreetmap.josm.data.osm.OsmPrimitive;
13import org.openstreetmap.josm.data.osm.Way;
14import org.openstreetmap.josm.data.osm.visitor.paint.MapPaintSettings;
15import org.openstreetmap.josm.data.osm.visitor.paint.PaintColors;
16import org.openstreetmap.josm.data.osm.visitor.paint.StyledMapRenderer;
17import org.openstreetmap.josm.gui.mappaint.Cascade;
18import org.openstreetmap.josm.gui.mappaint.Environment;
19import org.openstreetmap.josm.gui.mappaint.Keyword;
20import org.openstreetmap.josm.gui.mappaint.MultiCascade;
21import org.openstreetmap.josm.gui.mappaint.mapcss.Instruction.RelativeFloat;
22import org.openstreetmap.josm.tools.Utils;
23
24/**
25 * This is the style definition for a simple line.
26 */
27public class LineElement extends StyleElement {
28    /**
29     * The default style for any untagged way.
30     */
31    public static final LineElement UNTAGGED_WAY = createSimpleLineStyle(null, false);
32
33    private BasicStroke line;
34    public Color color;
35    public Color dashesBackground;
36    public float offset;
37    public float realWidth; // the real width of this line in meter
38    public boolean wayDirectionArrows;
39
40    private BasicStroke dashesLine;
41
42    public enum LineType {
43        NORMAL("", 3f),
44        CASING("casing-", 2f),
45        LEFT_CASING("left-casing-", 2.1f),
46        RIGHT_CASING("right-casing-", 2.1f);
47
48        public final String prefix;
49        public final float defaultMajorZIndex;
50
51        LineType(String prefix, float defaultMajorZindex) {
52            this.prefix = prefix;
53            this.defaultMajorZIndex = defaultMajorZindex;
54        }
55    }
56
57    protected LineElement(Cascade c, float defaultMajorZindex, BasicStroke line, Color color, BasicStroke dashesLine,
58            Color dashesBackground, float offset, float realWidth, boolean wayDirectionArrows) {
59        super(c, defaultMajorZindex);
60        this.line = line;
61        this.color = color;
62        this.dashesLine = dashesLine;
63        this.dashesBackground = dashesBackground;
64        this.offset = offset;
65        this.realWidth = realWidth;
66        this.wayDirectionArrows = wayDirectionArrows;
67    }
68
69    @Override
70    public void paintPrimitive(OsmPrimitive primitive, MapPaintSettings paintSettings, StyledMapRenderer painter,
71            boolean selected, boolean outermember, boolean member) {
72        /* show direction arrows, if draw.segment.relevant_directions_only is not set,
73        the way is tagged with a direction key
74        (even if the tag is negated as in oneway=false) or the way is selected */
75        boolean showOrientation;
76        if (defaultSelectedHandling) {
77            showOrientation = !isModifier && (selected || paintSettings.isShowDirectionArrow()) && !paintSettings.isUseRealWidth();
78        } else {
79            showOrientation = wayDirectionArrows;
80        }
81        boolean showOneway = !isModifier && !selected &&
82                !paintSettings.isUseRealWidth() &&
83                paintSettings.isShowOnewayArrow() && primitive.hasDirectionKeys();
84        boolean onewayReversed = primitive.reversedDirection();
85        /* head only takes over control if the option is true,
86        the direction should be shown at all and not only because it's selected */
87        boolean showOnlyHeadArrowOnly = showOrientation && !selected && paintSettings.isShowHeadArrowOnly();
88        Node lastN;
89
90        Color myDashedColor = dashesBackground;
91        BasicStroke myLine = line, myDashLine = dashesLine;
92        if (realWidth > 0 && paintSettings.isUseRealWidth() && !showOrientation) {
93            float myWidth = (int) (100 / (float) (painter.getCircum() / realWidth));
94            if (myWidth < line.getLineWidth()) {
95                myWidth = line.getLineWidth();
96            }
97            myLine = new BasicStroke(myWidth, line.getEndCap(), line.getLineJoin(),
98                    line.getMiterLimit(), line.getDashArray(), line.getDashPhase());
99            if (dashesLine != null) {
100                myDashLine = new BasicStroke(myWidth, dashesLine.getEndCap(), dashesLine.getLineJoin(),
101                        dashesLine.getMiterLimit(), dashesLine.getDashArray(), dashesLine.getDashPhase());
102            }
103        }
104
105        Color myColor = color;
106        if (defaultSelectedHandling && selected) {
107            myColor = paintSettings.getSelectedColor(color.getAlpha());
108        } else if (member || outermember) {
109            myColor = paintSettings.getRelationSelectedColor(color.getAlpha());
110        } else if (primitive.isDisabled()) {
111            myColor = paintSettings.getInactiveColor();
112            myDashedColor = paintSettings.getInactiveColor();
113        }
114
115        if (primitive instanceof Way) {
116            Way w = (Way) primitive;
117            painter.drawWay(w, myColor, myLine, myDashLine, myDashedColor, offset, showOrientation,
118                    showOnlyHeadArrowOnly, showOneway, onewayReversed);
119
120            if ((paintSettings.isShowOrderNumber() || (paintSettings.isShowOrderNumberOnSelectedWay() && selected))
121                    && !painter.isInactiveMode()) {
122                int orderNumber = 0;
123                lastN = null;
124                for (Node n : w.getNodes()) {
125                    if (lastN != null) {
126                        orderNumber++;
127                        painter.drawOrderNumber(lastN, n, orderNumber, myColor);
128                    }
129                    lastN = n;
130                }
131            }
132        }
133    }
134
135    @Override
136    public boolean isProperLineStyle() {
137        return !isModifier;
138    }
139
140    public String linejoinToString(int linejoin) {
141        switch (linejoin) {
142            case BasicStroke.JOIN_BEVEL: return "bevel";
143            case BasicStroke.JOIN_ROUND: return "round";
144            case BasicStroke.JOIN_MITER: return "miter";
145            default: return null;
146        }
147    }
148
149    public String linecapToString(int linecap) {
150        switch (linecap) {
151            case BasicStroke.CAP_BUTT: return "none";
152            case BasicStroke.CAP_ROUND: return "round";
153            case BasicStroke.CAP_SQUARE: return "square";
154            default: return null;
155        }
156    }
157
158    @Override
159    public boolean equals(Object obj) {
160        if (obj == null || getClass() != obj.getClass())
161            return false;
162        if (!super.equals(obj))
163            return false;
164        final LineElement other = (LineElement) obj;
165        return offset == other.offset &&
166               realWidth == other.realWidth &&
167               wayDirectionArrows == other.wayDirectionArrows &&
168               Objects.equals(line, other.line) &&
169               Objects.equals(color, other.color) &&
170               Objects.equals(dashesLine, other.dashesLine) &&
171               Objects.equals(dashesBackground, other.dashesBackground);
172    }
173
174    @Override
175    public int hashCode() {
176        return Objects.hash(super.hashCode(), line, color, dashesBackground, offset, realWidth, wayDirectionArrows, dashesLine);
177    }
178
179    @Override
180    public String toString() {
181        return "LineElemStyle{" + super.toString() + "width=" + line.getLineWidth() +
182            " realWidth=" + realWidth + " color=" + Utils.toString(color) +
183            " dashed=" + Arrays.toString(line.getDashArray()) +
184            (line.getDashPhase() == 0 ? "" : " dashesOffses=" + line.getDashPhase()) +
185            " dashedColor=" + Utils.toString(dashesBackground) +
186            " linejoin=" + linejoinToString(line.getLineJoin()) +
187            " linecap=" + linecapToString(line.getEndCap()) +
188            (offset == 0 ? "" : " offset=" + offset) +
189            '}';
190    }
191
192    /**
193     * Creates a simple line with default widt.
194     * @param color The color to use
195     * @param isAreaEdge If this is an edge for an area. Edges are drawn at lower Z-Index.
196     * @return The line style.
197     */
198    public static LineElement createSimpleLineStyle(Color color, boolean isAreaEdge) {
199        MultiCascade mc = new MultiCascade();
200        Cascade c = mc.getOrCreateCascade("default");
201        c.put(WIDTH, Keyword.DEFAULT);
202        c.put(COLOR, color != null ? color : PaintColors.UNTAGGED.get());
203        c.put(OPACITY, 1f);
204        if (isAreaEdge) {
205            c.put(Z_INDEX, -3f);
206        }
207        Way w = new Way();
208        return createLine(new Environment(w, mc, "default", null));
209    }
210
211    public static LineElement createLine(Environment env) {
212        return createImpl(env, LineType.NORMAL);
213    }
214
215    public static LineElement createLeftCasing(Environment env) {
216        LineElement leftCasing = createImpl(env, LineType.LEFT_CASING);
217        if (leftCasing != null) {
218            leftCasing.isModifier = true;
219        }
220        return leftCasing;
221    }
222
223    public static LineElement createRightCasing(Environment env) {
224        LineElement rightCasing = createImpl(env, LineType.RIGHT_CASING);
225        if (rightCasing != null) {
226            rightCasing.isModifier = true;
227        }
228        return rightCasing;
229    }
230
231    public static LineElement createCasing(Environment env) {
232        LineElement casing = createImpl(env, LineType.CASING);
233        if (casing != null) {
234            casing.isModifier = true;
235        }
236        return casing;
237    }
238
239    private static LineElement createImpl(Environment env, LineType type) {
240        Cascade c = env.mc.getCascade(env.layer);
241        Cascade cDef = env.mc.getCascade("default");
242        Float width = computeWidth(type, c, cDef);
243        if (width == null)
244            return null;
245
246        float realWidth = computeRealWidth(env, type, c);
247
248        Float offset = computeOffset(type, c, cDef, width);
249
250        int alpha = 255;
251        Color color = c.get(type.prefix + COLOR, null, Color.class);
252        if (color != null) {
253            alpha = color.getAlpha();
254        }
255        if (type == LineType.NORMAL && color == null) {
256            color = c.get(FILL_COLOR, null, Color.class);
257        }
258        if (color == null) {
259            color = PaintColors.UNTAGGED.get();
260        }
261
262        Integer pAlpha = Utils.colorFloat2int(c.get(type.prefix + OPACITY, null, Float.class));
263        if (pAlpha != null) {
264            alpha = pAlpha;
265        }
266        color = new Color(color.getRed(), color.getGreen(), color.getBlue(), alpha);
267
268        float[] dashes = c.get(type.prefix + DASHES, null, float[].class, true);
269        if (dashes != null) {
270            boolean hasPositive = false;
271            for (float f : dashes) {
272                if (f > 0) {
273                    hasPositive = true;
274                }
275                if (f < 0) {
276                    dashes = null;
277                    break;
278                }
279            }
280            if (!hasPositive || (dashes != null && dashes.length == 0)) {
281                dashes = null;
282            }
283        }
284        float dashesOffset = c.get(type.prefix + DASHES_OFFSET, 0f, Float.class);
285        Color dashesBackground = c.get(type.prefix + DASHES_BACKGROUND_COLOR, null, Color.class);
286        if (dashesBackground != null) {
287            pAlpha = Utils.colorFloat2int(c.get(type.prefix + DASHES_BACKGROUND_OPACITY, null, Float.class));
288            if (pAlpha != null) {
289                alpha = pAlpha;
290            }
291            dashesBackground = new Color(dashesBackground.getRed(), dashesBackground.getGreen(),
292                    dashesBackground.getBlue(), alpha);
293        }
294
295        Integer cap = null;
296        Keyword capKW = c.get(type.prefix + LINECAP, null, Keyword.class);
297        if (capKW != null) {
298            if ("none".equals(capKW.val)) {
299                cap = BasicStroke.CAP_BUTT;
300            } else if ("round".equals(capKW.val)) {
301                cap = BasicStroke.CAP_ROUND;
302            } else if ("square".equals(capKW.val)) {
303                cap = BasicStroke.CAP_SQUARE;
304            }
305        }
306        if (cap == null) {
307            cap = dashes != null ? BasicStroke.CAP_BUTT : BasicStroke.CAP_ROUND;
308        }
309
310        Integer join = null;
311        Keyword joinKW = c.get(type.prefix + LINEJOIN, null, Keyword.class);
312        if (joinKW != null) {
313            if ("round".equals(joinKW.val)) {
314                join = BasicStroke.JOIN_ROUND;
315            } else if ("miter".equals(joinKW.val)) {
316                join = BasicStroke.JOIN_MITER;
317            } else if ("bevel".equals(joinKW.val)) {
318                join = BasicStroke.JOIN_BEVEL;
319            }
320        }
321        if (join == null) {
322            join = BasicStroke.JOIN_ROUND;
323        }
324
325        float miterlimit = c.get(type.prefix + MITERLIMIT, 10f, Float.class);
326        if (miterlimit < 1f) {
327            miterlimit = 10f;
328        }
329
330        BasicStroke line = new BasicStroke(width, cap, join, miterlimit, dashes, dashesOffset);
331        BasicStroke dashesLine = null;
332
333        if (dashes != null && dashesBackground != null) {
334            float[] dashes2 = new float[dashes.length];
335            System.arraycopy(dashes, 0, dashes2, 1, dashes.length - 1);
336            dashes2[0] = dashes[dashes.length-1];
337            dashesLine = new BasicStroke(width, cap, join, miterlimit, dashes2, dashes2[0] + dashesOffset);
338        }
339
340        boolean wayDirectionArrows = c.get(type.prefix + WAY_DIRECTION_ARROWS, env.osm.isSelected(), Boolean.class);
341
342        return new LineElement(c, type.defaultMajorZIndex, line, color, dashesLine, dashesBackground,
343                offset, realWidth, wayDirectionArrows);
344    }
345
346    private static Float computeWidth(LineType type, Cascade c, Cascade cDef) {
347        Float width;
348        switch (type) {
349            case NORMAL:
350                width = getWidth(c, WIDTH, getWidth(cDef, WIDTH, null));
351                break;
352            case CASING:
353                Float casingWidth = c.get(type.prefix + WIDTH, null, Float.class, true);
354                if (casingWidth == null) {
355                    RelativeFloat relCasingWidth = c.get(type.prefix + WIDTH, null, RelativeFloat.class, true);
356                    if (relCasingWidth != null) {
357                        casingWidth = relCasingWidth.val / 2;
358                    }
359                }
360                if (casingWidth == null)
361                    return null;
362                width = Optional.ofNullable(getWidth(c, WIDTH, getWidth(cDef, WIDTH, null))).orElse(0f) + 2 * casingWidth;
363                break;
364            case LEFT_CASING:
365            case RIGHT_CASING:
366                width = getWidth(c, type.prefix + WIDTH, null);
367                break;
368            default:
369                throw new AssertionError();
370        }
371        return width;
372    }
373
374    private static float computeRealWidth(Environment env, LineType type, Cascade c) {
375        float realWidth = c.get(type.prefix + REAL_WIDTH, 0f, Float.class);
376        if (realWidth > 0 && MapPaintSettings.INSTANCE.isUseRealWidth()) {
377
378            /* if we have a "width" tag, try use it */
379            String widthTag = Optional.ofNullable(env.osm.get("width")).orElseGet(() -> env.osm.get("est_width"));
380            if (widthTag != null) {
381                try {
382                    realWidth = Float.parseFloat(widthTag);
383                } catch (NumberFormatException nfe) {
384                    Main.warn(nfe);
385                }
386            }
387        }
388        return realWidth;
389    }
390
391    private static Float computeOffset(LineType type, Cascade c, Cascade cDef, Float width) {
392        Float offset = c.get(OFFSET, 0f, Float.class);
393        switch (type) {
394            case NORMAL:
395                break;
396            case CASING:
397                offset += c.get(type.prefix + OFFSET, 0f, Float.class);
398                break;
399            case LEFT_CASING:
400            case RIGHT_CASING:
401                Float baseWidthOnDefault = getWidth(cDef, WIDTH, null);
402                Float baseWidth = getWidth(c, WIDTH, baseWidthOnDefault);
403                if (baseWidth == null || baseWidth < 2f) {
404                    baseWidth = 2f;
405                }
406                float casingOffset = c.get(type.prefix + OFFSET, 0f, Float.class);
407                casingOffset += baseWidth / 2 + width / 2;
408                /* flip sign for the right-casing-offset */
409                if (type == LineType.RIGHT_CASING) {
410                    casingOffset *= -1f;
411                }
412                offset += casingOffset;
413                break;
414        }
415        return offset;
416    }
417}
Note: See TracBrowser for help on using the repository browser.