source: josm/trunk/src/org/openstreetmap/josm/data/osm/visitor/paint/StyledMapRenderer.java@ 6203

Last change on this file since 6203 was 6203, checked in by Don-vip, 11 years ago

fix #9024 - bbox/bounds memory optimizations (modified patch by shinigami) + javadoc

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