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

Last change on this file since 11090 was 11090, checked in by simon04, 8 years ago

fix #13743 - Draw segment order numbers on selected way

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