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

Last change on this file was 17744, checked in by simon04, 3 years ago

see #20739 - Extract Environment.getCascade

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