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

Last change on this file since 5705 was 5705, checked in by bastiK, 11 years ago

mapcss: rework of eval expressions; significant performance improvement; fixes a bug w.r.t. overloading of the 'length' function.

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