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

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

fix #7979 - fix ArrayIndexOutOfBoundsException when rendering ways with the "quick and dirty" approach, and a TODO note to code a better approach on the next development cycle

  • Property svn:eol-style set to native
File size: 51.2 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.Font;
8import java.awt.FontMetrics;
9import java.awt.Graphics2D;
10import java.awt.Image;
11import java.awt.Point;
12import java.awt.Polygon;
13import java.awt.Rectangle;
14import java.awt.RenderingHints;
15import java.awt.Shape;
16import java.awt.TexturePaint;
17import java.awt.font.FontRenderContext;
18import java.awt.font.GlyphVector;
19import java.awt.font.LineMetrics;
20import java.awt.geom.AffineTransform;
21import java.awt.geom.GeneralPath;
22import java.awt.geom.Path2D;
23import java.awt.geom.Point2D;
24import java.awt.geom.Rectangle2D;
25import java.util.ArrayList;
26import java.util.Arrays;
27import java.util.Collection;
28import java.util.Collections;
29import java.util.Iterator;
30import java.util.List;
31
32import javax.swing.ImageIcon;
33
34import org.openstreetmap.josm.Main;
35import org.openstreetmap.josm.data.Bounds;
36import org.openstreetmap.josm.data.coor.EastNorth;
37import org.openstreetmap.josm.data.osm.BBox;
38import org.openstreetmap.josm.data.osm.DataSet;
39import org.openstreetmap.josm.data.osm.Node;
40import org.openstreetmap.josm.data.osm.OsmPrimitive;
41import org.openstreetmap.josm.data.osm.OsmUtils;
42import org.openstreetmap.josm.data.osm.Relation;
43import org.openstreetmap.josm.data.osm.RelationMember;
44import org.openstreetmap.josm.data.osm.Way;
45import org.openstreetmap.josm.data.osm.WaySegment;
46import org.openstreetmap.josm.data.osm.visitor.paint.relations.Multipolygon;
47import org.openstreetmap.josm.data.osm.visitor.paint.relations.Multipolygon.PolyData;
48import org.openstreetmap.josm.data.osm.visitor.paint.relations.MultipolygonCache;
49import org.openstreetmap.josm.gui.NavigatableComponent;
50import org.openstreetmap.josm.gui.mappaint.AreaElemStyle;
51import org.openstreetmap.josm.gui.mappaint.BoxTextElemStyle;
52import org.openstreetmap.josm.gui.mappaint.BoxTextElemStyle.HorizontalTextAlignment;
53import org.openstreetmap.josm.gui.mappaint.BoxTextElemStyle.VerticalTextAlignment;
54import org.openstreetmap.josm.gui.mappaint.ElemStyle;
55import org.openstreetmap.josm.gui.mappaint.ElemStyles;
56import org.openstreetmap.josm.gui.mappaint.MapImage;
57import org.openstreetmap.josm.gui.mappaint.MapPaintStyles;
58import org.openstreetmap.josm.gui.mappaint.NodeElemStyle;
59import org.openstreetmap.josm.gui.mappaint.NodeElemStyle.Symbol;
60import org.openstreetmap.josm.gui.mappaint.StyleCache.StyleList;
61import org.openstreetmap.josm.gui.mappaint.TextElement;
62import org.openstreetmap.josm.tools.ImageProvider;
63import org.openstreetmap.josm.tools.Pair;
64import org.openstreetmap.josm.tools.Utils;
65
66/**
67 * <p>A map renderer which renders a map according to style rules in a set of style sheets.</p>
68 *
69 */
70public class StyledMapRenderer extends AbstractMapRenderer {
71
72 /**
73 * Iterates over a list of Way Nodes and returns screen coordinates that
74 * represent a line that is shifted by a certain offset perpendicular
75 * to the way direction.
76 *
77 * There is no intention, to handle consecutive duplicate Nodes in a
78 * perfect way, but it is should not throw an exception.
79 */
80 private class OffsetIterator implements Iterator<Point> {
81
82 private List<Node> nodes;
83 private float offset;
84 private int idx;
85
86 private Point prev = null;
87 /* 'prev0' is a point that has distance 'offset' from 'prev' and the
88 * line from 'prev' to 'prev0' is perpendicular to the way segment from
89 * 'prev' to the next point.
90 */
91 private int x_prev0, y_prev0;
92
93 public OffsetIterator(List<Node> nodes, float offset) {
94 this.nodes = nodes;
95 this.offset = offset;
96 idx = 0;
97 }
98
99 @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}
Note: See TracBrowser for help on using the repository browser.