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

Last change on this file since 12378 was 12303, checked in by michael2402, 7 years ago

Javadoc for package org.openstreetmap.josm.gui.mappaint.styleelement

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