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.MapPainter; 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, MapPainter 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 }