001 // License: GPL. For details, see LICENSE file.
002 package org.openstreetmap.josm.gui.mappaint;
003
004 import static org.openstreetmap.josm.tools.Utils.equal;
005
006 import java.awt.BasicStroke;
007 import java.awt.Color;
008 import java.util.Arrays;
009
010 import org.openstreetmap.josm.data.osm.Node;
011 import org.openstreetmap.josm.data.osm.OsmPrimitive;
012 import org.openstreetmap.josm.data.osm.Way;
013 import org.openstreetmap.josm.data.osm.visitor.paint.MapPaintSettings;
014 import org.openstreetmap.josm.data.osm.visitor.paint.StyledMapRenderer;
015 import org.openstreetmap.josm.data.osm.visitor.paint.PaintColors;
016 import org.openstreetmap.josm.gui.mappaint.mapcss.Instruction.RelativeFloat;
017 import org.openstreetmap.josm.tools.Utils;
018
019 public class LineElemStyle extends ElemStyle {
020
021 public static LineElemStyle createSimpleLineStyle(Color color, boolean isAreaEdge) {
022 MultiCascade mc = new MultiCascade();
023 Cascade c = mc.getOrCreateCascade("default");
024 c.put(WIDTH, Keyword.DEFAULT);
025 c.put(COLOR, color != null ? color : PaintColors.UNTAGGED.get());
026 if (isAreaEdge) {
027 c.put(Z_INDEX, -3f);
028 }
029 return createLine(new Environment(null, mc, "default", null));
030 }
031 public static final LineElemStyle UNTAGGED_WAY = createSimpleLineStyle(null, false);
032
033 private BasicStroke line;
034 public Color color;
035 public Color dashesBackground;
036 public float offset;
037 public float realWidth; // the real width of this line in meter
038
039 private BasicStroke dashesLine;
040
041 protected enum LineType {
042 NORMAL("", 3f),
043 CASING("casing-", 2f),
044 LEFT_CASING("left-casing-", 2.1f),
045 RIGHT_CASING("right-casing-", 2.1f);
046
047 public final String prefix;
048 public final float default_major_z_index;
049
050 LineType(String prefix, float default_major_z_index) {
051 this.prefix = prefix;
052 this.default_major_z_index = default_major_z_index;
053 }
054 }
055
056 protected LineElemStyle(Cascade c, float default_major_z_index, BasicStroke line, Color color, BasicStroke dashesLine, Color dashesBackground, float offset, float realWidth) {
057 super(c, default_major_z_index);
058 this.line = line;
059 this.color = color;
060 this.dashesLine = dashesLine;
061 this.dashesBackground = dashesBackground;
062 this.offset = offset;
063 this.realWidth = realWidth;
064 }
065
066 public static LineElemStyle createLine(Environment env) {
067 return createImpl(env, LineType.NORMAL);
068 }
069
070 public static LineElemStyle createLeftCasing(Environment env) {
071 LineElemStyle leftCasing = createImpl(env, LineType.LEFT_CASING);
072 if (leftCasing != null) {
073 leftCasing.isModifier = true;
074 }
075 return leftCasing;
076 }
077
078 public static LineElemStyle createRightCasing(Environment env) {
079 LineElemStyle rightCasing = createImpl(env, LineType.RIGHT_CASING);
080 if (rightCasing != null) {
081 rightCasing.isModifier = true;
082 }
083 return rightCasing;
084 }
085
086 public static LineElemStyle createCasing(Environment env) {
087 LineElemStyle casing = createImpl(env, LineType.CASING);
088 if (casing != null) {
089 casing.isModifier = true;
090 }
091 return casing;
092 }
093
094 private static LineElemStyle createImpl(Environment env, LineType type) {
095 Cascade c = env.mc.getCascade(env.layer);
096 Cascade c_def = env.mc.getCascade("default");
097 Float width;
098 switch (type) {
099 case NORMAL:
100 {
101 Float widthOnDefault = getWidth(c_def, WIDTH, null);
102 width = getWidth(c, WIDTH, widthOnDefault);
103 break;
104 }
105 case CASING:
106 {
107 Float casingWidth = c.get(type.prefix + WIDTH, null, Float.class, true);
108 if (casingWidth == null) {
109 RelativeFloat rel_casingWidth = c.get(type.prefix + WIDTH, null, RelativeFloat.class, true);
110 if (rel_casingWidth != null) {
111 casingWidth = rel_casingWidth.val / 2;
112 }
113 }
114 if (casingWidth == null)
115 return null;
116 Float widthOnDefault = getWidth(c_def, WIDTH, null);
117 width = getWidth(c, WIDTH, widthOnDefault);
118 if (width == null) {
119 width = 0f;
120 }
121 width += 2 * casingWidth;
122 break;
123 }
124 case LEFT_CASING:
125 case RIGHT_CASING:
126 width = getWidth(c, type.prefix + WIDTH, null);
127 break;
128 default:
129 throw new AssertionError();
130 }
131 if (width == null)
132 return null;
133
134 float realWidth = c.get(type.prefix + REAL_WIDTH, 0f, Float.class);
135 if (realWidth > 0 && MapPaintSettings.INSTANCE.isUseRealWidth()) {
136
137 /* if we have a "width" tag, try use it */
138 String widthTag = env.osm.get("width");
139 if(widthTag == null) {
140 widthTag = env.osm.get("est_width");
141 }
142 if(widthTag != null) {
143 try {
144 realWidth = Float.valueOf(widthTag);
145 }
146 catch(NumberFormatException nfe) {
147 }
148 }
149 }
150
151 Float offset = c.get(OFFSET, 0f, Float.class);
152 switch (type) {
153 case NORMAL:
154 break;
155 case CASING:
156 offset += c.get(type.prefix + OFFSET, 0f, Float.class);
157 break;
158 case LEFT_CASING:
159 case RIGHT_CASING:
160 {
161 Float baseWidthOnDefault = getWidth(c_def, WIDTH, null);
162 Float baseWidth = getWidth(c, WIDTH, baseWidthOnDefault);
163 if (baseWidth == null || baseWidth < 2f) {
164 baseWidth = 2f;
165 }
166 float casingOffset = c.get(type.prefix + OFFSET, 0f, Float.class);
167 casingOffset += baseWidth / 2 + width / 2;
168 /* flip sign for the right-casing-offset */
169 if (type == LineType.RIGHT_CASING) {
170 casingOffset *= -1f;
171 }
172 offset += casingOffset;
173 break;
174 }
175 }
176
177 Color color = c.get(type.prefix + COLOR, null, Color.class);
178 if (type == LineType.NORMAL && color == null) {
179 color = c.get(FILL_COLOR, null, Color.class);
180 }
181 if (color == null) {
182 color = PaintColors.UNTAGGED.get();
183 }
184
185 int alpha = 255;
186 Integer pAlpha = Utils.color_float2int(c.get(OPACITY, null, Float.class));
187 if (pAlpha != null) {
188 alpha = pAlpha;
189 }
190 color = new Color(color.getRed(), color.getGreen(), color.getBlue(), alpha);
191
192 float[] dashes = c.get(type.prefix + DASHES, null, float[].class);
193 if (dashes != null) {
194 boolean hasPositive = false;
195 for (float f : dashes) {
196 if (f > 0) {
197 hasPositive = true;
198 }
199 if (f < 0) {
200 dashes = null;
201 break;
202 }
203 }
204 if (!hasPositive || (dashes != null && dashes.length == 0)) {
205 dashes = null;
206 }
207 }
208 float dashesOffset = c.get(type.prefix + DASHES_OFFSET, 0f, Float.class);
209 Color dashesBackground = c.get(type.prefix + DASHES_BACKGROUND_COLOR, null, Color.class);
210 if (dashesBackground != null) {
211 pAlpha = Utils.color_float2int(c.get(type.prefix + DASHES_BACKGROUND_OPACITY, null, Float.class));
212 if (pAlpha != null) {
213 alpha = pAlpha;
214 }
215 dashesBackground = new Color(dashesBackground.getRed(), dashesBackground.getGreen(),
216 dashesBackground.getBlue(), alpha);
217 }
218
219 Integer cap = null;
220 Keyword capKW = c.get(type.prefix + "linecap", null, Keyword.class);
221 if (capKW != null) {
222 if (equal(capKW.val, "none")) {
223 cap = BasicStroke.CAP_BUTT;
224 } else if (equal(capKW.val, "round")) {
225 cap = BasicStroke.CAP_ROUND;
226 } else if (equal(capKW.val, "square")) {
227 cap = BasicStroke.CAP_SQUARE;
228 }
229 }
230 if (cap == null) {
231 cap = dashes != null ? BasicStroke.CAP_BUTT : BasicStroke.CAP_ROUND;
232 }
233
234 Integer join = null;
235 Keyword joinKW = c.get(type.prefix + "linejoin", null, Keyword.class);
236 if (joinKW != null) {
237 if (equal(joinKW.val, "round")) {
238 join = BasicStroke.JOIN_ROUND;
239 } else if (equal(joinKW.val, "miter")) {
240 join = BasicStroke.JOIN_MITER;
241 } else if (equal(joinKW.val, "bevel")) {
242 join = BasicStroke.JOIN_BEVEL;
243 }
244 }
245 if (join == null) {
246 join = BasicStroke.JOIN_ROUND;
247 }
248
249 float miterlimit = c.get(type.prefix + "miterlimit", 10f, Float.class);
250 if (miterlimit < 1f) {
251 miterlimit = 10f;
252 }
253
254 BasicStroke line = new BasicStroke(width, cap, join, miterlimit, dashes, dashesOffset);
255 BasicStroke dashesLine = null;
256
257 if (dashes != null && dashesBackground != null) {
258 float[] dashes2 = new float[dashes.length];
259 System.arraycopy(dashes, 0, dashes2, 1, dashes.length - 1);
260 dashes2[0] = dashes[dashes.length-1];
261 dashesLine = new BasicStroke(width, cap, join, miterlimit, dashes2, dashes2[0] + dashesOffset);
262 }
263
264 return new LineElemStyle(c, type.default_major_z_index, line, color, dashesLine, dashesBackground, offset, realWidth);
265 }
266
267 @Override
268 public void paintPrimitive(OsmPrimitive primitive, MapPaintSettings paintSettings, StyledMapRenderer painter, boolean selected, boolean member) {
269 Way w = (Way)primitive;
270 /* show direction arrows, if draw.segment.relevant_directions_only is not set,
271 the way is tagged with a direction key
272 (even if the tag is negated as in oneway=false) or the way is selected */
273 boolean showOrientation = !isModifier && (selected || paintSettings.isShowDirectionArrow()) && !paintSettings.isUseRealWidth();
274 boolean showOneway = !isModifier && !selected &&
275 !paintSettings.isUseRealWidth() &&
276 paintSettings.isShowOnewayArrow() && w.hasDirectionKeys();
277 boolean onewayReversed = w.reversedDirection();
278 /* head only takes over control if the option is true,
279 the direction should be shown at all and not only because it's selected */
280 boolean showOnlyHeadArrowOnly = showOrientation && !selected && paintSettings.isShowHeadArrowOnly();
281 Node lastN;
282
283 Color myDashedColor = dashesBackground;
284 BasicStroke myLine = line, myDashLine = dashesLine;
285 if (realWidth > 0 && paintSettings.isUseRealWidth() && !showOrientation) {
286 float myWidth = (int) (100 / (float) (painter.getCircum() / realWidth));
287 if (myWidth < line.getLineWidth()) {
288 myWidth = line.getLineWidth();
289 }
290 myLine = new BasicStroke(myWidth, line.getEndCap(), line.getLineJoin(),
291 line.getMiterLimit(), line.getDashArray(), line.getDashPhase());
292 if (dashesLine != null) {
293 myDashLine = new BasicStroke(myWidth, dashesLine.getEndCap(), dashesLine.getLineJoin(),
294 dashesLine.getMiterLimit(), dashesLine.getDashArray(), dashesLine.getDashPhase());
295 }
296 }
297
298 Color myColor = color;
299 if (selected) {
300 myColor = paintSettings.getSelectedColor(color.getAlpha());
301 } else if (member) {
302 myColor = paintSettings.getRelationSelectedColor(color.getAlpha());
303 } else if(w.isDisabled()) {
304 myColor = paintSettings.getInactiveColor();
305 myDashedColor = paintSettings.getInactiveColor();
306 }
307
308 painter.drawWay(w, myColor, myLine, myDashLine, myDashedColor, offset, showOrientation,
309 showOnlyHeadArrowOnly, showOneway, onewayReversed);
310
311 if(paintSettings.isShowOrderNumber() && !painter.isInactiveMode()) {
312 int orderNumber = 0;
313 lastN = null;
314 for(Node n : w.getNodes()) {
315 if(lastN != null) {
316 orderNumber++;
317 painter.drawOrderNumber(lastN, n, orderNumber, myColor);
318 }
319 lastN = n;
320 }
321 }
322 }
323
324 @Override
325 public boolean isProperLineStyle() {
326 return !isModifier;
327 }
328
329 @Override
330 public boolean equals(Object obj) {
331 if (obj == null || getClass() != obj.getClass())
332 return false;
333 if (!super.equals(obj))
334 return false;
335 final LineElemStyle other = (LineElemStyle) obj;
336 return equal(line, other.line) &&
337 equal(color, other.color) &&
338 equal(dashesLine, other.dashesLine) &&
339 equal(dashesBackground, other.dashesBackground) &&
340 offset == other.offset &&
341 realWidth == other.realWidth;
342 }
343
344 @Override
345 public int hashCode() {
346 int hash = super.hashCode();
347 hash = 29 * hash + line.hashCode();
348 hash = 29 * hash + color.hashCode();
349 hash = 29 * hash + (dashesLine != null ? dashesLine.hashCode() : 0);
350 hash = 29 * hash + (dashesBackground != null ? dashesBackground.hashCode() : 0);
351 hash = 29 * hash + Float.floatToIntBits(offset);
352 hash = 29 * hash + Float.floatToIntBits(realWidth);
353 return hash;
354 }
355
356 @Override
357 public String toString() {
358 return "LineElemStyle{" + super.toString() + "width=" + line.getLineWidth() +
359 " realWidth=" + realWidth + " color=" + Utils.toString(color) +
360 " dashed=" + Arrays.toString(line.getDashArray()) +
361 (line.getDashPhase() == 0f ? "" : " dashesOffses=" + line.getDashPhase()) +
362 " dashedColor=" + Utils.toString(dashesBackground) +
363 " linejoin=" + linejoinToString(line.getLineJoin()) +
364 " linecap=" + linecapToString(line.getEndCap()) +
365 (offset == 0 ? "" : " offset=" + offset) +
366 '}';
367 }
368
369 public String linejoinToString(int linejoin) {
370 switch (linejoin) {
371 case BasicStroke.JOIN_BEVEL: return "bevel";
372 case BasicStroke.JOIN_ROUND: return "round";
373 case BasicStroke.JOIN_MITER: return "miter";
374 default: return null;
375 }
376 }
377 public String linecapToString(int linecap) {
378 switch (linecap) {
379 case BasicStroke.CAP_BUTT: return "none";
380 case BasicStroke.CAP_ROUND: return "round";
381 case BasicStroke.CAP_SQUARE: return "square";
382 default: return null;
383 }
384 }
385 }