001 // License: GPL. Copyright 2007 by Immanuel Scholz and others 002 package org.openstreetmap.josm.data.osm.visitor.paint; 003 004 import java.awt.AlphaComposite; 005 import java.awt.BasicStroke; 006 import java.awt.Color; 007 import java.awt.Font; 008 import java.awt.FontMetrics; 009 import java.awt.Graphics2D; 010 import java.awt.Image; 011 import java.awt.Point; 012 import java.awt.Polygon; 013 import java.awt.Rectangle; 014 import java.awt.RenderingHints; 015 import java.awt.Shape; 016 import java.awt.TexturePaint; 017 import java.awt.font.FontRenderContext; 018 import java.awt.font.GlyphVector; 019 import java.awt.font.LineMetrics; 020 import java.awt.geom.AffineTransform; 021 import java.awt.geom.GeneralPath; 022 import java.awt.geom.Path2D; 023 import java.awt.geom.Point2D; 024 import java.awt.geom.Rectangle2D; 025 import java.util.ArrayList; 026 import java.util.Arrays; 027 import java.util.Collection; 028 import java.util.Collections; 029 import java.util.Iterator; 030 import java.util.List; 031 032 import javax.swing.ImageIcon; 033 034 import org.openstreetmap.josm.Main; 035 import org.openstreetmap.josm.data.Bounds; 036 import org.openstreetmap.josm.data.coor.EastNorth; 037 import org.openstreetmap.josm.data.osm.BBox; 038 import org.openstreetmap.josm.data.osm.DataSet; 039 import org.openstreetmap.josm.data.osm.Node; 040 import org.openstreetmap.josm.data.osm.OsmPrimitive; 041 import org.openstreetmap.josm.data.osm.OsmUtils; 042 import org.openstreetmap.josm.data.osm.Relation; 043 import org.openstreetmap.josm.data.osm.RelationMember; 044 import org.openstreetmap.josm.data.osm.Way; 045 import org.openstreetmap.josm.data.osm.WaySegment; 046 import org.openstreetmap.josm.data.osm.visitor.paint.relations.Multipolygon; 047 import org.openstreetmap.josm.data.osm.visitor.paint.relations.Multipolygon.PolyData; 048 import org.openstreetmap.josm.data.osm.visitor.paint.relations.MultipolygonCache; 049 import org.openstreetmap.josm.gui.NavigatableComponent; 050 import org.openstreetmap.josm.gui.mappaint.AreaElemStyle; 051 import org.openstreetmap.josm.gui.mappaint.BoxTextElemStyle; 052 import org.openstreetmap.josm.gui.mappaint.BoxTextElemStyle.HorizontalTextAlignment; 053 import org.openstreetmap.josm.gui.mappaint.BoxTextElemStyle.VerticalTextAlignment; 054 import org.openstreetmap.josm.gui.mappaint.ElemStyle; 055 import org.openstreetmap.josm.gui.mappaint.ElemStyles; 056 import org.openstreetmap.josm.gui.mappaint.MapImage; 057 import org.openstreetmap.josm.gui.mappaint.MapPaintStyles; 058 import org.openstreetmap.josm.gui.mappaint.NodeElemStyle; 059 import org.openstreetmap.josm.gui.mappaint.NodeElemStyle.Symbol; 060 import org.openstreetmap.josm.gui.mappaint.StyleCache.StyleList; 061 import org.openstreetmap.josm.gui.mappaint.TextElement; 062 import org.openstreetmap.josm.tools.ImageProvider; 063 import org.openstreetmap.josm.tools.Pair; 064 import org.openstreetmap.josm.tools.Utils; 065 066 /** 067 * <p>A map renderer which renders a map according to style rules in a set of style sheets.</p> 068 * 069 */ 070 public class StyledMapRenderer extends AbstractMapRenderer { 071 072 /** 073 * Iterates over a list of Way Nodes and returns screen coordinates that 074 * represent a line that is shifted by a certain offset perpendicular 075 * to the way direction. 076 * 077 * There is no intention, to handle consecutive duplicate Nodes in a 078 * perfect way, but it is should not throw an exception. 079 */ 080 private class OffsetIterator implements Iterator<Point> { 081 082 private List<Node> nodes; 083 private float offset; 084 private int idx; 085 086 private Point prev = null; 087 /* 'prev0' is a point that has distance 'offset' from 'prev' and the 088 * line from 'prev' to 'prev0' is perpendicular to the way segment from 089 * 'prev' to the next point. 090 */ 091 private int x_prev0, y_prev0; 092 093 public OffsetIterator(List<Node> nodes, float offset) { 094 this.nodes = nodes; 095 this.offset = offset; 096 idx = 0; 097 } 098 099 @Override 100 public boolean hasNext() { 101 return idx < nodes.size(); 102 } 103 104 @Override 105 public Point next() { 106 if (Math.abs(offset) < 0.1f) return nc.getPoint(nodes.get(idx++)); 107 108 Point current = nc.getPoint(nodes.get(idx)); 109 110 if (idx == nodes.size() - 1) { 111 ++idx; 112 return new Point(x_prev0 + current.x - prev.x, y_prev0 + current.y - prev.y); 113 } 114 115 Point next = nc.getPoint(nodes.get(idx+1)); 116 117 int dx_next = next.x - current.x; 118 int dy_next = next.y - current.y; 119 double len_next = Math.sqrt(dx_next*dx_next + dy_next*dy_next); 120 121 if (len_next == 0) { 122 len_next = 1; // value does not matter, because dy_next and dx_next is 0 123 } 124 125 int x_current0 = current.x + (int) Math.round(offset * dy_next / len_next); 126 int y_current0 = current.y - (int) Math.round(offset * dx_next / len_next); 127 128 if (idx==0) { 129 ++idx; 130 prev = current; 131 x_prev0 = x_current0; 132 y_prev0 = y_current0; 133 return new Point(x_current0, y_current0); 134 } else { 135 int dx_prev = current.x - prev.x; 136 int dy_prev = current.y - prev.y; 137 138 // determine intersection of the lines parallel to the two 139 // segments 140 int det = dx_next*dy_prev - dx_prev*dy_next; 141 142 if (det == 0) { 143 ++idx; 144 prev = current; 145 x_prev0 = x_current0; 146 y_prev0 = y_current0; 147 return new Point(x_current0, y_current0); 148 } 149 150 int m = dx_next*(y_current0 - y_prev0) - dy_next*(x_current0 - x_prev0); 151 152 int cx_ = x_prev0 + Math.round((float)m * dx_prev / det); 153 int cy_ = y_prev0 + Math.round((float)m * dy_prev / det); 154 ++idx; 155 prev = current; 156 x_prev0 = x_current0; 157 y_prev0 = y_current0; 158 return new Point(cx_, cy_); 159 } 160 } 161 162 @Override 163 public void remove() { 164 throw new UnsupportedOperationException(); 165 } 166 } 167 168 private class StyleCollector { 169 private final boolean drawArea; 170 private final boolean drawMultipolygon; 171 private final boolean drawRestriction; 172 173 private final List<StyleRecord> styleElems; 174 175 public StyleCollector(boolean drawArea, boolean drawMultipolygon, boolean drawRestriction) { 176 this.drawArea = drawArea; 177 this.drawMultipolygon = drawMultipolygon; 178 this.drawRestriction = drawRestriction; 179 styleElems = new ArrayList<StyleRecord>(); 180 } 181 182 public void add(Node osm, int flags) { 183 StyleList sl = styles.get(osm, circum, nc); 184 for (ElemStyle s : sl) { 185 styleElems.add(new StyleRecord(s, osm, flags)); 186 } 187 } 188 189 public void add(Relation osm, int flags) { 190 StyleList sl = styles.get(osm, circum, nc); 191 for (ElemStyle s : sl) { 192 if (drawMultipolygon && drawArea && s instanceof AreaElemStyle && (flags & FLAG_DISABLED) == 0) { 193 styleElems.add(new StyleRecord(s, osm, flags)); 194 } else if (drawRestriction && s instanceof NodeElemStyle) { 195 styleElems.add(new StyleRecord(s, osm, flags)); 196 } 197 } 198 } 199 200 public void add(Way osm, int flags) { 201 StyleList sl = styles.get(osm, circum, nc); 202 for (ElemStyle s : sl) { 203 if (!(drawArea && (flags & FLAG_DISABLED) == 0) && s instanceof AreaElemStyle) { 204 continue; 205 } 206 styleElems.add(new StyleRecord(s, osm, flags)); 207 } 208 } 209 210 public void drawAll() { 211 Collections.sort(styleElems); 212 for (StyleRecord r : styleElems) { 213 r.style.paintPrimitive( 214 r.osm, 215 paintSettings, 216 StyledMapRenderer.this, 217 (r.flags & FLAG_SELECTED) != 0, 218 (r.flags & FLAG_MEMBER_OF_SELECTED) != 0 219 ); 220 } 221 } 222 } 223 224 private static class StyleRecord implements Comparable<StyleRecord> { 225 final ElemStyle style; 226 final OsmPrimitive osm; 227 final int flags; 228 229 public StyleRecord(ElemStyle style, OsmPrimitive osm, int flags) { 230 this.style = style; 231 this.osm = osm; 232 this.flags = flags; 233 } 234 235 @Override 236 public int compareTo(StyleRecord other) { 237 if ((this.flags & FLAG_DISABLED) != 0 && (other.flags & FLAG_DISABLED) == 0) 238 return -1; 239 if ((this.flags & FLAG_DISABLED) == 0 && (other.flags & FLAG_DISABLED) != 0) 240 return 1; 241 242 int d0 = Float.compare(this.style.major_z_index, other.style.major_z_index); 243 if (d0 != 0) 244 return d0; 245 246 // selected on top of member of selected on top of unselected 247 // FLAG_DISABLED bit is the same at this point 248 if (this.flags > other.flags) 249 return 1; 250 if (this.flags < other.flags) 251 return -1; 252 253 int dz = Float.compare(this.style.z_index, other.style.z_index); 254 if (dz != 0) 255 return dz; 256 257 // simple node on top of icons and shapes 258 if (this.style == NodeElemStyle.SIMPLE_NODE_ELEMSTYLE && other.style != NodeElemStyle.SIMPLE_NODE_ELEMSTYLE) 259 return 1; 260 if (this.style != NodeElemStyle.SIMPLE_NODE_ELEMSTYLE && other.style == NodeElemStyle.SIMPLE_NODE_ELEMSTYLE) 261 return -1; 262 263 // newer primitives to the front 264 long id = this.osm.getUniqueId() - other.osm.getUniqueId(); 265 if (id > 0) 266 return 1; 267 if (id < 0) 268 return -1; 269 270 return Float.compare(this.style.object_z_index, other.style.object_z_index); 271 } 272 } 273 274 private static Boolean IS_GLYPH_VECTOR_DOUBLE_TRANSLATION_BUG = null; 275 276 /** 277 * Check, if this System has the GlyphVector double translation bug. 278 * 279 * With this bug, <code>gv.setGlyphTransform(i, trfm)</code> has a different 280 * effect than on most other systems, namely the translation components 281 * ("m02" & "m12", {@link AffineTransform}) appear to be twice as large, as 282 * they actually are. The rotation is unaffected (scale & shear not tested 283 * so far). 284 * 285 * This bug has only been observed on Mac OS X, see #7841. 286 * 287 * @return true, if the GlyphVector double translation bug is present on 288 * this System 289 */ 290 public static boolean isGlyphVectorDoubleTranslationBug() { 291 if (IS_GLYPH_VECTOR_DOUBLE_TRANSLATION_BUG != null) 292 return IS_GLYPH_VECTOR_DOUBLE_TRANSLATION_BUG; 293 FontRenderContext frc = new FontRenderContext(null, false, false); 294 Font font = new Font("Dialog", Font.PLAIN, 12); 295 GlyphVector gv = font.createGlyphVector(frc, "x"); 296 gv.setGlyphTransform(0, AffineTransform.getTranslateInstance(1000, 1000)); 297 Shape shape = gv.getGlyphOutline(0); 298 // x is about 1000 on normal stystems and about 2000 when the bug occurs 299 int x = shape.getBounds().x; 300 IS_GLYPH_VECTOR_DOUBLE_TRANSLATION_BUG = x > 1500; 301 return IS_GLYPH_VECTOR_DOUBLE_TRANSLATION_BUG; 302 } 303 304 private ElemStyles styles; 305 private double circum; 306 307 private MapPaintSettings paintSettings; 308 309 private Color relationSelectedColor; 310 private Color highlightColorTransparent; 311 312 private static int FLAG_NORMAL = 0; 313 private static int FLAG_DISABLED = 1; 314 private static int FLAG_MEMBER_OF_SELECTED = 2; 315 private static int FLAG_SELECTED = 4; 316 317 private static final double PHI = Math.toRadians(20); 318 private static final double cosPHI = Math.cos(PHI); 319 private static final double sinPHI = Math.sin(PHI); 320 321 private Collection<WaySegment> highlightWaySegments; 322 323 private boolean useStrokes; 324 private boolean showNames; 325 private boolean showIcons; 326 private boolean isOutlineOnly; 327 328 private Font orderFont; 329 330 private boolean leftHandTraffic; 331 332 /** 333 * {@inheritDoc} 334 */ 335 public StyledMapRenderer(Graphics2D g, NavigatableComponent nc, boolean isInactiveMode) { 336 super(g, nc, isInactiveMode); 337 } 338 339 private Polygon buildPolygon(Point center, int radius, int sides) { 340 return buildPolygon(center, radius, sides, 0.0); 341 } 342 343 private Polygon buildPolygon(Point center, int radius, int sides, double rotation) { 344 Polygon polygon = new Polygon(); 345 for (int i = 0; i < sides; i++) { 346 double angle = ((2 * Math.PI / sides) * i) - rotation; 347 int x = (int) Math.round(center.x + radius * Math.cos(angle)); 348 int y = (int) Math.round(center.y + radius * Math.sin(angle)); 349 polygon.addPoint(x, y); 350 } 351 return polygon; 352 } 353 354 private void collectNodeStyles(DataSet data, StyleCollector sc, BBox bbox) { 355 for (final Node n: data.searchNodes(bbox)) { 356 if (n.isDrawable()) { 357 if (n.isDisabled()) { 358 sc.add(n, FLAG_DISABLED); 359 } else if (data.isSelected(n)) { 360 sc.add(n, FLAG_SELECTED); 361 } else if (n.isMemberOfSelected()) { 362 sc.add(n, FLAG_MEMBER_OF_SELECTED); 363 } else { 364 sc.add(n, FLAG_NORMAL); 365 } 366 } 367 } 368 } 369 370 private void collectRelationStyles(DataSet data, StyleCollector sc, BBox bbox) { 371 for (Relation r: data.searchRelations(bbox)) { 372 if (r.isDrawable()) { 373 if (r.isDisabled()) { 374 sc.add(r, FLAG_DISABLED); 375 } else if (data.isSelected(r)) { 376 sc.add(r, FLAG_SELECTED); 377 } else { 378 sc.add(r, FLAG_NORMAL); 379 } 380 } 381 } 382 } 383 384 private void collectWayStyles(DataSet data, StyleCollector sc, BBox bbox) { 385 for (final Way w : data.searchWays(bbox)) { 386 if (w.isDrawable()) { 387 if (w.isDisabled()) { 388 sc.add(w, FLAG_DISABLED); 389 } else if (data.isSelected(w)) { 390 sc.add(w, FLAG_SELECTED); 391 } else if (w.isMemberOfSelected()) { 392 sc.add(w, FLAG_MEMBER_OF_SELECTED); 393 } else { 394 sc.add(w, FLAG_NORMAL); 395 } 396 } 397 } 398 } 399 400 private void displaySegments(GeneralPath path, GeneralPath orientationArrows, GeneralPath onewayArrows, GeneralPath onewayArrowsCasing, 401 Color color, BasicStroke line, BasicStroke dashes, Color dashedColor) { 402 g.setColor(isInactiveMode ? inactiveColor : color); 403 if (useStrokes) { 404 g.setStroke(line); 405 } 406 g.draw(path); 407 408 if(!isInactiveMode && useStrokes && dashes != null) { 409 g.setColor(dashedColor); 410 g.setStroke(dashes); 411 g.draw(path); 412 } 413 414 if (orientationArrows != null) { 415 g.setColor(isInactiveMode ? inactiveColor : color); 416 g.setStroke(new BasicStroke(line.getLineWidth(), line.getEndCap(), BasicStroke.JOIN_MITER, line.getMiterLimit())); 417 g.draw(orientationArrows); 418 } 419 420 if (onewayArrows != null) { 421 g.setStroke(new BasicStroke(1, line.getEndCap(), BasicStroke.JOIN_MITER, line.getMiterLimit())); 422 g.fill(onewayArrowsCasing); 423 g.setColor(isInactiveMode ? inactiveColor : backgroundColor); 424 g.fill(onewayArrows); 425 } 426 427 if (useStrokes) { 428 g.setStroke(new BasicStroke()); 429 } 430 } 431 432 protected void drawArea(OsmPrimitive osm, Path2D.Double path, Color color, MapImage fillImage, TextElement text) { 433 434 Shape area = path.createTransformedShape(nc.getAffineTransform()); 435 436 if (!isOutlineOnly) { 437 if (fillImage == null) { 438 if (isInactiveMode) { 439 g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.33f)); 440 } 441 g.setColor(color); 442 g.fill(area); 443 } else { 444 TexturePaint texture = new TexturePaint(fillImage.getImage(), 445 // new Rectangle(polygon.xpoints[0], polygon.ypoints[0], fillImage.getWidth(), fillImage.getHeight())); 446 new Rectangle(0, 0, fillImage.getWidth(), fillImage.getHeight())); 447 g.setPaint(texture); 448 Float alpha = Utils.color_int2float(fillImage.alpha); 449 if (alpha != 1f) { 450 g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha)); 451 } 452 g.fill(area); 453 g.setPaintMode(); 454 } 455 } 456 457 if (text != null && isShowNames()) { 458 /* 459 * abort if we can't compose the label to be rendered 460 */ 461 if (text.labelCompositionStrategy == null) return; 462 String name = text.labelCompositionStrategy.compose(osm); 463 if (name == null) return; 464 465 Rectangle pb = area.getBounds(); 466 FontMetrics fontMetrics = g.getFontMetrics(orderFont); // if slow, use cache 467 Rectangle2D nb = fontMetrics.getStringBounds(name, g); // if slow, approximate by strlen()*maxcharbounds(font) 468 469 // Point2D c = getCentroid(polygon); 470 // Using the Centroid is Nicer for buildings like: +--------+ 471 // but this needs to be fast. As most houses are | 42 | 472 // boxes anyway, the center of the bounding box +---++---+ 473 // will have to do. ++ 474 // Centroids are not optimal either, just imagine a U-shaped house. 475 // Point2D c = new Point2D.Double(pb.x + pb.width / 2.0, pb.y + pb.height / 2.0); 476 // Rectangle2D.Double centeredNBounds = 477 // new Rectangle2D.Double(c.getX() - nb.getWidth()/2, 478 // c.getY() - nb.getHeight()/2, 479 // nb.getWidth(), 480 // nb.getHeight()); 481 482 Rectangle centeredNBounds = new Rectangle(pb.x + (int)((pb.width - nb.getWidth())/2.0), 483 pb.y + (int)((pb.height - nb.getHeight())/2.0), 484 (int)nb.getWidth(), 485 (int)nb.getHeight()); 486 487 if ((pb.width >= nb.getWidth() && pb.height >= nb.getHeight()) && // quick check 488 area.contains(centeredNBounds) // slow but nice 489 ) { 490 if (isInactiveMode || osm.isDisabled()) { 491 g.setColor(inactiveColor); 492 } else { 493 g.setColor(text.color); 494 } 495 Font defaultFont = g.getFont(); 496 g.setFont (text.font); 497 g.drawString (name, 498 (int)(centeredNBounds.getMinX() - nb.getMinX()), 499 (int)(centeredNBounds.getMinY() - nb.getMinY())); 500 g.setFont(defaultFont); 501 } 502 } 503 } 504 505 public void drawArea(Relation r, Color color, MapImage fillImage, TextElement text) { 506 Multipolygon multipolygon = MultipolygonCache.getInstance().get(nc, r); 507 if (!r.isDisabled() && !multipolygon.getOuterWays().isEmpty()) { 508 for (PolyData pd : multipolygon.getCombinedPolygons()) { 509 Path2D.Double p = pd.get(); 510 if (!isAreaVisible(p)) { 511 continue; 512 } 513 drawArea(r, p, 514 pd.selected ? paintSettings.getRelationSelectedColor(color.getAlpha()) : color, 515 fillImage, text); 516 } 517 } 518 } 519 520 public void drawArea(Way w, Color color, MapImage fillImage, TextElement text) { 521 drawArea(w, getPath(w), color, fillImage, text); 522 } 523 524 public void drawBoxText(Node n, BoxTextElemStyle bs) { 525 if (!isShowNames() || bs == null) 526 return; 527 528 Point p = nc.getPoint(n); 529 TextElement text = bs.text; 530 String s = text.labelCompositionStrategy.compose(n); 531 if (s == null) return; 532 533 Font defaultFont = g.getFont(); 534 g.setFont(text.font); 535 536 int x = p.x + text.xOffset; 537 int y = p.y + text.yOffset; 538 /** 539 * 540 * left-above __center-above___ right-above 541 * left-top| |right-top 542 * | | 543 * left-center| center-center |right-center 544 * | | 545 * left-bottom|_________________|right-bottom 546 * left-below center-below right-below 547 * 548 */ 549 Rectangle box = bs.getBox(); 550 if (bs.hAlign == HorizontalTextAlignment.RIGHT) { 551 x += box.x + box.width + 2; 552 } else { 553 FontRenderContext frc = g.getFontRenderContext(); 554 Rectangle2D bounds = text.font.getStringBounds(s, frc); 555 int textWidth = (int) bounds.getWidth(); 556 if (bs.hAlign == HorizontalTextAlignment.CENTER) { 557 x -= textWidth / 2; 558 } else if (bs.hAlign == HorizontalTextAlignment.LEFT) { 559 x -= - box.x + 4 + textWidth; 560 } else throw new AssertionError(); 561 } 562 563 if (bs.vAlign == VerticalTextAlignment.BOTTOM) { 564 y += box.y + box.height; 565 } else { 566 FontRenderContext frc = g.getFontRenderContext(); 567 LineMetrics metrics = text.font.getLineMetrics(s, frc); 568 if (bs.vAlign == VerticalTextAlignment.ABOVE) { 569 y -= - box.y + metrics.getDescent(); 570 } else if (bs.vAlign == VerticalTextAlignment.TOP) { 571 y -= - box.y - metrics.getAscent(); 572 } else if (bs.vAlign == VerticalTextAlignment.CENTER) { 573 y += (metrics.getAscent() - metrics.getDescent()) / 2; 574 } else if (bs.vAlign == VerticalTextAlignment.BELOW) { 575 y += box.y + box.height + metrics.getAscent() + 2; 576 } else throw new AssertionError(); 577 } 578 if (isInactiveMode || n.isDisabled()) { 579 g.setColor(inactiveColor); 580 } else { 581 g.setColor(text.color); 582 } 583 if (text.haloRadius != null) { 584 g.setStroke(new BasicStroke(2*text.haloRadius, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND)); 585 g.setColor(text.haloColor); 586 FontRenderContext frc = g.getFontRenderContext(); 587 GlyphVector gv = text.font.createGlyphVector(frc, s); 588 Shape textOutline = gv.getOutline(x, y); 589 g.draw(textOutline); 590 g.setStroke(new BasicStroke()); 591 g.setColor(text.color); 592 g.fill(textOutline); 593 } else { 594 g.drawString(s, x, y); 595 } 596 g.setFont(defaultFont); 597 } 598 599 public void drawLinePattern(Way way, Image pattern) { 600 final int width = pattern.getWidth(null); 601 final int height = pattern.getHeight(null); 602 603 Point lastP = null; 604 double wayLength = 0; 605 606 Iterator<Node> it = way.getNodes().iterator(); 607 while (it.hasNext()) { 608 Node n = it.next(); 609 Point thisP = nc.getPoint(n); 610 611 if (lastP != null) { 612 final double segmentLength = thisP.distance(lastP); 613 614 final double dx = thisP.x - lastP.x; 615 final double dy = thisP.y - lastP.y; 616 617 double dist = wayLength == 0 ? 0 : width - (wayLength % width); 618 619 AffineTransform saveTransform = g.getTransform(); 620 g.translate(lastP.x, lastP.y); 621 g.rotate(Math.atan2(dy, dx)); 622 623 if (dist > 0) { 624 g.drawImage(pattern, 0, 0, (int) dist, height, 625 width - (int) dist, 0, width, height, null); 626 } 627 while (dist < segmentLength) { 628 if (dist + width > segmentLength) { 629 g.drawImage(pattern, (int) dist, 0, (int) segmentLength, height, 630 0, 0, (int) segmentLength - (int) dist, height, null); 631 } else { 632 g.drawImage(pattern, (int) dist, 0, nc); 633 } 634 dist += width; 635 } 636 g.setTransform(saveTransform); 637 638 wayLength += segmentLength; 639 } 640 lastP = thisP; 641 } 642 } 643 644 @Override 645 public void drawNode(Node n, Color color, int size, boolean fill) { 646 if(size <= 0 && !n.isHighlighted()) 647 return; 648 649 Point p = nc.getPoint(n); 650 651 if(n.isHighlighted()) { 652 drawPointHighlight(p, size); 653 } 654 655 if (size > 1) { 656 if ((p.x < 0) || (p.y < 0) || (p.x > nc.getWidth()) || (p.y > nc.getHeight())) return; 657 int radius = size / 2; 658 659 if (isInactiveMode || n.isDisabled()) { 660 g.setColor(inactiveColor); 661 } else { 662 g.setColor(color); 663 } 664 if (fill) { 665 g.fillRect(p.x-radius-1, p.y-radius-1, size + 1, size + 1); 666 } else { 667 g.drawRect(p.x-radius-1, p.y-radius-1, size, size); 668 } 669 } 670 } 671 672 public void drawNodeIcon(Node n, Image img, float alpha, boolean selected, boolean member) { 673 Point p = nc.getPoint(n); 674 675 final int w = img.getWidth(null), h=img.getHeight(null); 676 if(n.isHighlighted()) { 677 drawPointHighlight(p, Math.max(w, h)); 678 } 679 680 if (alpha != 1f) { 681 g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha)); 682 } 683 g.drawImage(img, p.x-w/2, p.y-h/2, nc); 684 g.setPaintMode(); 685 if (selected || member) 686 { 687 Color color = null; 688 if (isInactiveMode || n.isDisabled()) { 689 color = inactiveColor; 690 } else if (selected) { 691 color = selectedColor; 692 } else { 693 color = relationSelectedColor; 694 } 695 g.setColor(color); 696 g.drawRect(p.x-w/2-2, p.y-h/2-2, w+4, h+4); 697 } 698 } 699 700 public void drawNodeSymbol(Node n, Symbol s, Color fillColor, Color strokeColor) { 701 Point p = nc.getPoint(n); 702 int radius = s.size / 2; 703 704 if(n.isHighlighted()) { 705 drawPointHighlight(p, s.size); 706 } 707 708 if (fillColor != null) { 709 g.setColor(fillColor); 710 switch (s.symbol) { 711 case SQUARE: 712 g.fillRect(p.x - radius, p.y - radius, s.size, s.size); 713 break; 714 case CIRCLE: 715 g.fillOval(p.x - radius, p.y - radius, s.size, s.size); 716 break; 717 case TRIANGLE: 718 g.fillPolygon(buildPolygon(p, radius, 3, Math.PI / 2)); 719 break; 720 case PENTAGON: 721 g.fillPolygon(buildPolygon(p, radius, 5, Math.PI / 2)); 722 break; 723 case HEXAGON: 724 g.fillPolygon(buildPolygon(p, radius, 6)); 725 break; 726 case HEPTAGON: 727 g.fillPolygon(buildPolygon(p, radius, 7, Math.PI / 2)); 728 break; 729 case OCTAGON: 730 g.fillPolygon(buildPolygon(p, radius, 8, Math.PI / 8)); 731 break; 732 case NONAGON: 733 g.fillPolygon(buildPolygon(p, radius, 9, Math.PI / 2)); 734 break; 735 case DECAGON: 736 g.fillPolygon(buildPolygon(p, radius, 10)); 737 break; 738 default: 739 throw new AssertionError(); 740 } 741 } 742 if (s.stroke != null) { 743 g.setStroke(s.stroke); 744 g.setColor(strokeColor); 745 switch (s.symbol) { 746 case SQUARE: 747 g.drawRect(p.x - radius, p.y - radius, s.size - 1, s.size - 1); 748 break; 749 case CIRCLE: 750 g.drawOval(p.x - radius, p.y - radius, s.size - 1, s.size - 1); 751 break; 752 case TRIANGLE: 753 g.drawPolygon(buildPolygon(p, radius, 3, Math.PI / 2)); 754 break; 755 case PENTAGON: 756 g.drawPolygon(buildPolygon(p, radius, 5, Math.PI / 2)); 757 break; 758 case HEXAGON: 759 g.drawPolygon(buildPolygon(p, radius, 6)); 760 break; 761 case HEPTAGON: 762 g.drawPolygon(buildPolygon(p, radius, 7, Math.PI / 2)); 763 break; 764 case OCTAGON: 765 g.drawPolygon(buildPolygon(p, radius, 8, Math.PI / 8)); 766 break; 767 case NONAGON: 768 g.drawPolygon(buildPolygon(p, radius, 9, Math.PI / 2)); 769 break; 770 case DECAGON: 771 g.drawPolygon(buildPolygon(p, radius, 10)); 772 break; 773 default: 774 throw new AssertionError(); 775 } 776 g.setStroke(new BasicStroke()); 777 } 778 } 779 780 /** 781 * Draw a number of the order of the two consecutive nodes within the 782 * parents way 783 */ 784 public void drawOrderNumber(Node n1, Node n2, int orderNumber, Color clr) { 785 Point p1 = nc.getPoint(n1); 786 Point p2 = nc.getPoint(n2); 787 StyledMapRenderer.this.drawOrderNumber(p1, p2, orderNumber, clr); 788 } 789 790 /** 791 * highlights a given GeneralPath using the settings from BasicStroke to match the line's 792 * style. Width of the highlight is hard coded. 793 * @param path 794 * @param line 795 */ 796 private void drawPathHighlight(GeneralPath path, BasicStroke line) { 797 if(path == null) 798 return; 799 g.setColor(highlightColorTransparent); 800 float w = (line.getLineWidth() + 4); 801 while(w >= line.getLineWidth()) { 802 g.setStroke(new BasicStroke(w, line.getEndCap(), line.getLineJoin(), line.getMiterLimit())); 803 g.draw(path); 804 w -= 4; 805 } 806 } 807 /** 808 * highlights a given point by drawing a rounded rectangle around it. Give the 809 * size of the object you want to be highlighted, width is added automatically. 810 */ 811 private void drawPointHighlight(Point p, int size) { 812 g.setColor(highlightColorTransparent); 813 int s = size + 7; 814 while(s >= size) { 815 int r = (int) Math.floor(s/2); 816 g.fillRoundRect(p.x-r, p.y-r, s, s, r, r); 817 s -= 4; 818 } 819 } 820 821 public void drawRestriction(Image img, Point pVia, double vx, double vx2, double vy, double vy2, double angle, boolean selected) { 822 /* rotate image with direction last node in from to */ 823 Image rotatedImg = ImageProvider.createRotatedImage(null , img, angle); 824 825 /* scale down image to 16*16 pixels */ 826 Image smallImg = new ImageIcon(rotatedImg.getScaledInstance(16 , 16, Image.SCALE_SMOOTH)).getImage(); 827 int w = smallImg.getWidth(null), h=smallImg.getHeight(null); 828 g.drawImage(smallImg, (int)(pVia.x+vx+vx2)-w/2, (int)(pVia.y+vy+vy2)-h/2, nc); 829 830 if (selected) { 831 g.setColor(isInactiveMode ? inactiveColor : relationSelectedColor); 832 g.drawRect((int)(pVia.x+vx+vx2)-w/2-2,(int)(pVia.y+vy+vy2)-h/2-2, w+4, h+4); 833 } 834 } 835 836 public void drawRestriction(Relation r, MapImage icon) { 837 Way fromWay = null; 838 Way toWay = null; 839 OsmPrimitive via = null; 840 841 /* find the "from", "via" and "to" elements */ 842 for (RelationMember m : r.getMembers()) 843 { 844 if(m.getMember().isIncomplete()) 845 return; 846 else 847 { 848 if(m.isWay()) 849 { 850 Way w = m.getWay(); 851 if(w.getNodesCount() < 2) { 852 continue; 853 } 854 855 if("from".equals(m.getRole())) { 856 if(fromWay == null) { 857 fromWay = w; 858 } 859 } else if("to".equals(m.getRole())) { 860 if(toWay == null) { 861 toWay = w; 862 } 863 } else if("via".equals(m.getRole())) { 864 if(via == null) { 865 via = w; 866 } 867 } 868 } 869 else if(m.isNode()) 870 { 871 Node n = m.getNode(); 872 if("via".equals(m.getRole()) && via == null) { 873 via = n; 874 } 875 } 876 } 877 } 878 879 if (fromWay == null || toWay == null || via == null) 880 return; 881 882 Node viaNode; 883 if(via instanceof Node) 884 { 885 viaNode = (Node) via; 886 if(!fromWay.isFirstLastNode(viaNode)) 887 return; 888 } 889 else 890 { 891 Way viaWay = (Way) via; 892 Node firstNode = viaWay.firstNode(); 893 Node lastNode = viaWay.lastNode(); 894 Boolean onewayvia = false; 895 896 String onewayviastr = viaWay.get("oneway"); 897 if(onewayviastr != null) 898 { 899 if("-1".equals(onewayviastr)) { 900 onewayvia = true; 901 Node tmp = firstNode; 902 firstNode = lastNode; 903 lastNode = tmp; 904 } else { 905 onewayvia = OsmUtils.getOsmBoolean(onewayviastr); 906 if (onewayvia == null) { 907 onewayvia = false; 908 } 909 } 910 } 911 912 if(fromWay.isFirstLastNode(firstNode)) { 913 viaNode = firstNode; 914 } else if (!onewayvia && fromWay.isFirstLastNode(lastNode)) { 915 viaNode = lastNode; 916 } else 917 return; 918 } 919 920 /* find the "direct" nodes before the via node */ 921 Node fromNode = null; 922 if(fromWay.firstNode() == via) { 923 fromNode = fromWay.getNode(1); 924 } else { 925 fromNode = fromWay.getNode(fromWay.getNodesCount()-2); 926 } 927 928 Point pFrom = nc.getPoint(fromNode); 929 Point pVia = nc.getPoint(viaNode); 930 931 /* starting from via, go back the "from" way a few pixels 932 (calculate the vector vx/vy with the specified length and the direction 933 away from the "via" node along the first segment of the "from" way) 934 */ 935 double distanceFromVia=14; 936 double dx = (pFrom.x >= pVia.x) ? (pFrom.x - pVia.x) : (pVia.x - pFrom.x); 937 double dy = (pFrom.y >= pVia.y) ? (pFrom.y - pVia.y) : (pVia.y - pFrom.y); 938 939 double fromAngle; 940 if(dx == 0.0) { 941 fromAngle = Math.PI/2; 942 } else { 943 fromAngle = Math.atan(dy / dx); 944 } 945 double fromAngleDeg = Math.toDegrees(fromAngle); 946 947 double vx = distanceFromVia * Math.cos(fromAngle); 948 double vy = distanceFromVia * Math.sin(fromAngle); 949 950 if(pFrom.x < pVia.x) { 951 vx = -vx; 952 } 953 if(pFrom.y < pVia.y) { 954 vy = -vy; 955 } 956 957 /* go a few pixels away from the way (in a right angle) 958 (calculate the vx2/vy2 vector with the specified length and the direction 959 90degrees away from the first segment of the "from" way) 960 */ 961 double distanceFromWay=10; 962 double vx2 = 0; 963 double vy2 = 0; 964 double iconAngle = 0; 965 966 if(pFrom.x >= pVia.x && pFrom.y >= pVia.y) { 967 if(!leftHandTraffic) { 968 vx2 = distanceFromWay * Math.cos(Math.toRadians(fromAngleDeg - 90)); 969 vy2 = distanceFromWay * Math.sin(Math.toRadians(fromAngleDeg - 90)); 970 } else { 971 vx2 = distanceFromWay * Math.cos(Math.toRadians(fromAngleDeg + 90)); 972 vy2 = distanceFromWay * Math.sin(Math.toRadians(fromAngleDeg + 90)); 973 } 974 iconAngle = 270+fromAngleDeg; 975 } 976 if(pFrom.x < pVia.x && pFrom.y >= pVia.y) { 977 if(!leftHandTraffic) { 978 vx2 = distanceFromWay * Math.sin(Math.toRadians(fromAngleDeg)); 979 vy2 = distanceFromWay * Math.cos(Math.toRadians(fromAngleDeg)); 980 } else { 981 vx2 = distanceFromWay * Math.sin(Math.toRadians(fromAngleDeg + 180)); 982 vy2 = distanceFromWay * Math.cos(Math.toRadians(fromAngleDeg + 180)); 983 } 984 iconAngle = 90-fromAngleDeg; 985 } 986 if(pFrom.x < pVia.x && pFrom.y < pVia.y) { 987 if(!leftHandTraffic) { 988 vx2 = distanceFromWay * Math.cos(Math.toRadians(fromAngleDeg + 90)); 989 vy2 = distanceFromWay * Math.sin(Math.toRadians(fromAngleDeg + 90)); 990 } else { 991 vx2 = distanceFromWay * Math.cos(Math.toRadians(fromAngleDeg - 90)); 992 vy2 = distanceFromWay * Math.sin(Math.toRadians(fromAngleDeg - 90)); 993 } 994 iconAngle = 90+fromAngleDeg; 995 } 996 if(pFrom.x >= pVia.x && pFrom.y < pVia.y) { 997 if(!leftHandTraffic) { 998 vx2 = distanceFromWay * Math.sin(Math.toRadians(fromAngleDeg + 180)); 999 vy2 = distanceFromWay * Math.cos(Math.toRadians(fromAngleDeg + 180)); 1000 } else { 1001 vx2 = distanceFromWay * Math.sin(Math.toRadians(fromAngleDeg)); 1002 vy2 = distanceFromWay * Math.cos(Math.toRadians(fromAngleDeg)); 1003 } 1004 iconAngle = 270-fromAngleDeg; 1005 } 1006 1007 drawRestriction(isInactiveMode || r.isDisabled() ? icon.getDisabled() : icon.getImage(), 1008 pVia, vx, vx2, vy, vy2, iconAngle, r.isSelected()); 1009 } 1010 1011 public void drawTextOnPath(Way way, TextElement text) { 1012 if (way == null || text == null) 1013 return; 1014 String name = text.getString(way); 1015 if (name == null || name.isEmpty()) 1016 return; 1017 1018 Polygon poly = new Polygon(); 1019 Point lastPoint = null; 1020 Iterator<Node> it = way.getNodes().iterator(); 1021 double pathLength = 0; 1022 long dx, dy; 1023 while (it.hasNext()) { 1024 Node n = it.next(); 1025 Point p = nc.getPoint(n); 1026 poly.addPoint(p.x, p.y); 1027 1028 if(lastPoint != null) { 1029 dx = p.x - lastPoint.x; 1030 dy = p.y - lastPoint.y; 1031 pathLength += Math.sqrt(dx*dx + dy*dy); 1032 } 1033 lastPoint = p; 1034 } 1035 1036 FontMetrics fontMetrics = g.getFontMetrics(text.font); // if slow, use cache 1037 Rectangle2D rec = fontMetrics.getStringBounds(name, g); // if slow, approximate by strlen()*maxcharbounds(font) 1038 1039 if (rec.getWidth() > pathLength) 1040 return; 1041 1042 double t1 = (pathLength/2 - rec.getWidth()/2) / pathLength; 1043 double t2 = (pathLength/2 + rec.getWidth()/2) / pathLength; 1044 1045 double[] p1 = pointAt(t1, poly, pathLength); 1046 double[] p2 = pointAt(t2, poly, pathLength); 1047 1048 if (p1 == null || p2 == null) 1049 return; 1050 1051 double angleOffset; 1052 double offsetSign; 1053 double tStart; 1054 1055 if (p1[0] < p2[0] && 1056 p1[2] < Math.PI/2 && 1057 p1[2] > -Math.PI/2) { 1058 angleOffset = 0; 1059 offsetSign = 1; 1060 tStart = t1; 1061 } else { 1062 angleOffset = Math.PI; 1063 offsetSign = -1; 1064 tStart = t2; 1065 } 1066 1067 FontRenderContext frc = g.getFontRenderContext(); 1068 GlyphVector gv = text.font.createGlyphVector(frc, name); 1069 1070 for (int i=0; i<gv.getNumGlyphs(); ++i) { 1071 Rectangle2D rect = gv.getGlyphLogicalBounds(i).getBounds2D(); 1072 double t = tStart + offsetSign * (rect.getX() + rect.getWidth()/2) / pathLength; 1073 double[] p = pointAt(t, poly, pathLength); 1074 if (p != null) { 1075 AffineTransform trfm = AffineTransform.getTranslateInstance(p[0] - rect.getX(), p[1]); 1076 trfm.rotate(p[2]+angleOffset); 1077 double off = -rect.getY() - rect.getHeight()/2 + text.yOffset; 1078 trfm.translate(-rect.getWidth()/2, off); 1079 if (isGlyphVectorDoubleTranslationBug()) { 1080 // scale the translation components by one half 1081 AffineTransform tmp = AffineTransform.getTranslateInstance(-0.5 * trfm.getTranslateX(), -0.5 * trfm.getTranslateY()); 1082 tmp.concatenate(trfm); 1083 trfm = tmp; 1084 } 1085 gv.setGlyphTransform(i, trfm); 1086 } 1087 } 1088 if (text.haloRadius != null) { 1089 Shape textOutline = gv.getOutline(); 1090 g.setStroke(new BasicStroke(2*text.haloRadius, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND)); 1091 g.setColor(text.haloColor); 1092 g.draw(textOutline); 1093 g.setStroke(new BasicStroke()); 1094 g.setColor(text.color); 1095 g.fill(textOutline); 1096 } else { 1097 g.setColor(text.color); 1098 g.drawGlyphVector(gv, 0, 0); 1099 } 1100 } 1101 1102 /** 1103 * draw way 1104 * @param showOrientation show arrows that indicate the technical orientation of 1105 * the way (defined by order of nodes) 1106 * @param showOneway show symbols that indicate the direction of the feature, 1107 * e.g. oneway street or waterway 1108 * @param onewayReversed for oneway=-1 and similar 1109 */ 1110 public void drawWay(Way way, Color color, BasicStroke line, BasicStroke dashes, Color dashedColor, float offset, 1111 boolean showOrientation, boolean showHeadArrowOnly, 1112 boolean showOneway, boolean onewayReversed) { 1113 1114 GeneralPath path = new GeneralPath(); 1115 GeneralPath orientationArrows = showOrientation ? new GeneralPath() : null; 1116 GeneralPath onewayArrows = showOneway ? new GeneralPath() : null; 1117 GeneralPath onewayArrowsCasing = showOneway ? new GeneralPath() : null; 1118 Rectangle bounds = g.getClipBounds(); 1119 bounds.grow(100, 100); // avoid arrow heads at the border 1120 1121 double wayLength = 0; 1122 Point lastPoint = null; 1123 boolean initialMoveToNeeded = true; 1124 List<Node> wayNodes = way.getNodes(); 1125 if (wayNodes.size() < 2) return; 1126 1127 // only highlight the segment if the way itself is not highlighted 1128 if (!way.isHighlighted()) { 1129 GeneralPath highlightSegs = null; 1130 for (WaySegment ws : highlightWaySegments) { 1131 if (ws.way != way || ws.lowerIndex < offset) { 1132 continue; 1133 } 1134 if(highlightSegs == null) { 1135 highlightSegs = new GeneralPath(); 1136 } 1137 1138 Point p1 = nc.getPoint(ws.getFirstNode()); 1139 Point p2 = nc.getPoint(ws.getSecondNode()); 1140 highlightSegs.moveTo(p1.x, p1.y); 1141 highlightSegs.lineTo(p2.x, p2.y); 1142 } 1143 1144 drawPathHighlight(highlightSegs, line); 1145 } 1146 1147 Iterator<Point> it = new OffsetIterator(wayNodes, offset); 1148 while (it.hasNext()) { 1149 Point p = it.next(); 1150 if (lastPoint != null) { 1151 Point p1 = lastPoint; 1152 Point p2 = p; 1153 1154 /** 1155 * Do custom clipping to work around openjdk bug. It leads to 1156 * drawing artefacts when zooming in a lot. (#4289, #4424) 1157 * (Looks like int overflow.) 1158 */ 1159 LineClip clip = new LineClip(p1, p2, bounds); 1160 if (clip.execute()) { 1161 if (!p1.equals(clip.getP1())) { 1162 p1 = clip.getP1(); 1163 path.moveTo(p1.x, p1.y); 1164 } else if (initialMoveToNeeded) { 1165 initialMoveToNeeded = false; 1166 path.moveTo(p1.x, p1.y); 1167 } 1168 p2 = clip.getP2(); 1169 path.lineTo(p2.x, p2.y); 1170 1171 /* draw arrow */ 1172 if (showHeadArrowOnly ? !it.hasNext() : showOrientation) { 1173 final double segmentLength = p1.distance(p2); 1174 if (segmentLength != 0.0) { 1175 final double l = (10. + line.getLineWidth()) / segmentLength; 1176 1177 final double sx = l * (p1.x - p2.x); 1178 final double sy = l * (p1.y - p2.y); 1179 1180 orientationArrows.moveTo (p2.x + cosPHI * sx - sinPHI * sy, p2.y + sinPHI * sx + cosPHI * sy); 1181 orientationArrows.lineTo(p2.x, p2.y); 1182 orientationArrows.lineTo (p2.x + cosPHI * sx + sinPHI * sy, p2.y - sinPHI * sx + cosPHI * sy); 1183 } 1184 } 1185 if (showOneway) { 1186 final double segmentLength = p1.distance(p2); 1187 if (segmentLength != 0.0) { 1188 final double nx = (p2.x - p1.x) / segmentLength; 1189 final double ny = (p2.y - p1.y) / segmentLength; 1190 1191 final double interval = 60; 1192 // distance from p1 1193 double dist = interval - (wayLength % interval); 1194 1195 while (dist < segmentLength) { 1196 for (Pair<Float, GeneralPath> sizeAndPath : Arrays.asList(new Pair[] { 1197 new Pair<Float, GeneralPath>(3f, onewayArrowsCasing), 1198 new Pair<Float, GeneralPath>(2f, onewayArrows)})) { 1199 1200 // scale such that border is 1 px 1201 final double fac = - (onewayReversed ? -1 : 1) * sizeAndPath.a * (1 + sinPHI) / (sinPHI * cosPHI); 1202 final double sx = nx * fac; 1203 final double sy = ny * fac; 1204 1205 // Attach the triangle at the incenter and not at the tip. 1206 // Makes the border even at all sides. 1207 final double x = p1.x + nx * (dist + (onewayReversed ? -1 : 1) * (sizeAndPath.a / sinPHI)); 1208 final double y = p1.y + ny * (dist + (onewayReversed ? -1 : 1) * (sizeAndPath.a / sinPHI)); 1209 1210 sizeAndPath.b.moveTo(x, y); 1211 sizeAndPath.b.lineTo (x + cosPHI * sx - sinPHI * sy, y + sinPHI * sx + cosPHI * sy); 1212 sizeAndPath.b.lineTo (x + cosPHI * sx + sinPHI * sy, y - sinPHI * sx + cosPHI * sy); 1213 sizeAndPath.b.lineTo(x, y); 1214 } 1215 dist += interval; 1216 } 1217 } 1218 wayLength += segmentLength; 1219 } 1220 } 1221 } 1222 lastPoint = p; 1223 } 1224 if(way.isHighlighted()) { 1225 drawPathHighlight(path, line); 1226 } 1227 displaySegments(path, orientationArrows, onewayArrows, onewayArrowsCasing, color, line, dashes, dashedColor); 1228 } 1229 1230 public double getCircum() { 1231 return circum; 1232 } 1233 1234 @Override 1235 public void getColors() { 1236 super.getColors(); 1237 this.relationSelectedColor = PaintColors.RELATIONSELECTED.get(); 1238 this.highlightColorTransparent = new Color(highlightColor.getRed(), highlightColor.getGreen(), highlightColor.getBlue(), 100); 1239 this.backgroundColor = PaintColors.getBackgroundColor(); 1240 } 1241 1242 @Override 1243 protected void getSettings(boolean virtual) { 1244 super.getSettings(virtual); 1245 paintSettings = MapPaintSettings.INSTANCE; 1246 1247 circum = nc.getDist100Pixel(); 1248 1249 leftHandTraffic = Main.pref.getBoolean("mappaint.lefthandtraffic", false); 1250 1251 useStrokes = paintSettings.getUseStrokesDistance() > circum; 1252 showNames = paintSettings.getShowNamesDistance() > circum; 1253 showIcons = paintSettings.getShowIconsDistance() > circum; 1254 isOutlineOnly = paintSettings.isOutlineOnly(); 1255 orderFont = new Font(Main.pref.get("mappaint.font", "Helvetica"), Font.PLAIN, Main.pref.getInteger("mappaint.fontsize", 8)); 1256 1257 g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, 1258 Main.pref.getBoolean("mappaint.use-antialiasing", true) ? 1259 RenderingHints.VALUE_ANTIALIAS_ON : RenderingHints.VALUE_ANTIALIAS_OFF); 1260 } 1261 1262 private Path2D.Double getPath(Way w) { 1263 Path2D.Double path = new Path2D.Double(); 1264 boolean initial = true; 1265 for (Node n : w.getNodes()) { 1266 Point2D p = n.getEastNorth(); 1267 if (p != null) { 1268 if (initial) { 1269 path.moveTo(p.getX(), p.getY()); 1270 initial = false; 1271 } else { 1272 path.lineTo(p.getX(), p.getY()); 1273 } 1274 } 1275 } 1276 return path; 1277 } 1278 1279 private boolean isAreaVisible(Path2D.Double area) { 1280 Rectangle2D bounds = area.getBounds2D(); 1281 if (bounds.isEmpty()) return false; 1282 Point2D p = nc.getPoint2D(new EastNorth(bounds.getX(), bounds.getY())); 1283 if (p.getX() > nc.getWidth()) return false; 1284 if (p.getY() < 0) return false; 1285 p = nc.getPoint2D(new EastNorth(bounds.getX() + bounds.getWidth(), bounds.getY() + bounds.getHeight())); 1286 if (p.getX() < 0) return false; 1287 if (p.getY() > nc.getHeight()) return false; 1288 return true; 1289 } 1290 1291 public boolean isInactiveMode() { 1292 return isInactiveMode; 1293 } 1294 1295 public boolean isShowIcons() { 1296 return showIcons; 1297 } 1298 1299 public boolean isShowNames() { 1300 return showNames; 1301 } 1302 1303 private double[] pointAt(double t, Polygon poly, double pathLength) { 1304 double totalLen = t * pathLength; 1305 double curLen = 0; 1306 long dx, dy; 1307 double segLen; 1308 1309 // Yes, it is inefficient to iterate from the beginning for each glyph. 1310 // Can be optimized if it turns out to be slow. 1311 for (int i = 1; i < poly.npoints; ++i) { 1312 dx = poly.xpoints[i] - poly.xpoints[i-1]; 1313 dy = poly.ypoints[i] - poly.ypoints[i-1]; 1314 segLen = Math.sqrt(dx*dx + dy*dy); 1315 if (totalLen > curLen + segLen) { 1316 curLen += segLen; 1317 continue; 1318 } 1319 return new double[] { 1320 poly.xpoints[i-1]+(totalLen - curLen)/segLen*dx, 1321 poly.ypoints[i-1]+(totalLen - curLen)/segLen*dy, 1322 Math.atan2(dy, dx)}; 1323 } 1324 return null; 1325 } 1326 1327 @Override 1328 public void render(final DataSet data, boolean renderVirtualNodes, Bounds bounds) { 1329 //long start = System.currentTimeMillis(); 1330 BBox bbox = new BBox(bounds); 1331 getSettings(renderVirtualNodes); 1332 1333 boolean drawArea = circum <= Main.pref.getInteger("mappaint.fillareas", 10000000); 1334 boolean drawMultipolygon = drawArea && Main.pref.getBoolean("mappaint.multipolygon", true); 1335 boolean drawRestriction = Main.pref.getBoolean("mappaint.restriction", true); 1336 1337 styles = MapPaintStyles.getStyles(); 1338 styles.setDrawMultipolygon(drawMultipolygon); 1339 1340 highlightWaySegments = data.getHighlightedWaySegments(); 1341 1342 StyleCollector sc = new StyleCollector(drawArea, drawMultipolygon, drawRestriction); 1343 collectNodeStyles(data, sc, bbox); 1344 collectWayStyles(data, sc, bbox); 1345 collectRelationStyles(data, sc, bbox); 1346 //long phase1 = System.currentTimeMillis(); 1347 sc.drawAll(); 1348 sc = null; 1349 drawVirtualNodes(data, bbox); 1350 1351 //long now = System.currentTimeMillis(); 1352 //System.err.println(String.format("PAINTING TOOK %d [PHASE1 took %d] (at scale %s)", now - start, phase1 - start, circum)); 1353 } 1354 }