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