source: josm/trunk/src/org/openstreetmap/josm/data/osm/visitor/paint/MapPainter.java@ 4317

Last change on this file since 4317 was 4317, checked in by bastiK, 13 years ago

fix last commit

  • Property svn:eol-style set to native
  • Property svn:mime-type set to text/plain
File size: 41.7 KB
Line 
1// License: GPL. For details, see LICENSE file.
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.Shape;
15import java.awt.TexturePaint;
16import java.awt.font.FontRenderContext;
17import java.awt.font.GlyphVector;
18import java.awt.font.LineMetrics;
19import java.awt.geom.AffineTransform;
20import java.awt.geom.GeneralPath;
21import java.awt.geom.Rectangle2D;
22import java.awt.image.BufferedImage;
23import java.util.Arrays;
24import java.util.Collection;
25import java.util.Iterator;
26import java.util.List;
27
28import javax.swing.ImageIcon;
29
30import org.openstreetmap.josm.Main;
31import org.openstreetmap.josm.data.osm.Node;
32import org.openstreetmap.josm.data.osm.OsmPrimitive;
33import org.openstreetmap.josm.data.osm.OsmUtils;
34import org.openstreetmap.josm.data.osm.Relation;
35import org.openstreetmap.josm.data.osm.RelationMember;
36import org.openstreetmap.josm.data.osm.Way;
37import org.openstreetmap.josm.data.osm.visitor.paint.relations.Multipolygon;
38import org.openstreetmap.josm.data.osm.visitor.paint.relations.Multipolygon.PolyData;
39import org.openstreetmap.josm.gui.NavigatableComponent;
40import org.openstreetmap.josm.gui.mappaint.NodeElemStyle;
41import org.openstreetmap.josm.gui.mappaint.NodeElemStyle.HorizontalTextAlignment;
42import org.openstreetmap.josm.gui.mappaint.NodeElemStyle.NodeTextElement;
43import org.openstreetmap.josm.gui.mappaint.NodeElemStyle.Symbol;
44import org.openstreetmap.josm.gui.mappaint.NodeElemStyle.VerticalTextAlignment;
45import org.openstreetmap.josm.gui.mappaint.TextElement;
46import org.openstreetmap.josm.tools.ImageProvider;
47import org.openstreetmap.josm.tools.Pair;
48
49public class MapPainter {
50
51 private final Graphics2D g;
52 private final NavigatableComponent nc;
53 private final boolean inactive;
54 private final MapPaintSettings settings;
55
56 private final boolean useStrokes;
57 private final boolean showNames;
58 private final boolean showIcons;
59
60 private final boolean isOutlineOnly;
61
62 private final Color inactiveColor;
63 private final Color selectedColor;
64 private final Color relationSelectedColor;
65 private final Color nodeColor;
66 private final Color backgroundColor;
67
68 private final Font orderFont;
69 private final int virtualNodeSize;
70 private final int virtualNodeSpace;
71 private final int segmentNumberSpace;
72
73 private final double circum;
74
75 private final boolean leftHandTraffic;
76
77 private static final double PHI = Math.toRadians(20);
78 private static final double cosPHI = Math.cos(PHI);
79 private static final double sinPHI = Math.sin(PHI);
80
81 public MapPainter(MapPaintSettings settings, Graphics2D g,
82 boolean inactive, NavigatableComponent nc, boolean virtual,
83 double circum, boolean leftHandTraffic){
84 this.settings = settings;
85 this.g = g;
86 this.inactive = inactive;
87 this.nc = nc;
88 this.useStrokes = settings.getUseStrokesDistance() > circum;
89 this.showNames = settings.getShowNamesDistance() > circum;
90 this.showIcons = settings.getShowIconsDistance() > circum;
91
92 this.isOutlineOnly = settings.isOutlineOnly();
93
94 this.inactiveColor = PaintColors.INACTIVE.get();
95 this.selectedColor = PaintColors.SELECTED.get();
96 this.relationSelectedColor = PaintColors.RELATIONSELECTED.get();
97 this.nodeColor = PaintColors.NODE.get();
98 this.backgroundColor = PaintColors.getBackgroundColor();
99
100 this.orderFont = new Font(Main.pref.get("mappaint.font", "Helvetica"), Font.PLAIN, Main.pref.getInteger("mappaint.fontsize", 8));
101 this.virtualNodeSize = virtual ? Main.pref.getInteger("mappaint.node.virtual-size", 8) / 2 : 0;
102 this.virtualNodeSpace = Main.pref.getInteger("mappaint.node.virtual-space", 70);
103 this.segmentNumberSpace = Main.pref.getInteger("mappaint.segmentnumber.space", 40);
104
105 this.circum = circum;
106 this.leftHandTraffic = leftHandTraffic;
107 }
108
109 /**
110 * draw way
111 * @param showOrientation show arrows that indicate the technical orientation of
112 * the way (defined by order of nodes)
113 * @param showOneway show symbols that indicate the direction of the feature,
114 * e.g. oneway street or waterway
115 * @param onewayReversed for oneway=-1 and similar
116 */
117 public void drawWay(Way way, Color color, BasicStroke line, BasicStroke dashes, Color dashedColor, int offset,
118 boolean showOrientation, boolean showHeadArrowOnly,
119 boolean showOneway, boolean onewayReversed) {
120
121 GeneralPath path = new GeneralPath();
122 GeneralPath orientationArrows = showOrientation ? new GeneralPath() : null;
123 GeneralPath onewayArrows = showOneway ? new GeneralPath() : null;
124 GeneralPath onewayArrowsCasing = showOneway ? new GeneralPath() : null;
125 Rectangle bounds = g.getClipBounds();
126 bounds.grow(100, 100); // avoid arrow heads at the border
127
128 double wayLength = 0;
129 Point lastPoint = null;
130 boolean initialMoveToNeeded = true;
131 List<Node> wayNodes = way.getNodes();
132 if (wayNodes.size() < 2) return;
133
134 Iterator<Point> it = new OffsetIterator(way.getNodes(), offset);
135 while (it.hasNext()) {
136 Point p = it.next();
137 if (lastPoint != null) {
138 Point p1 = lastPoint;
139 Point p2 = p;
140
141 /**
142 * Do custom clipping to work around openjdk bug. It leads to
143 * drawing artefacts when zooming in a lot. (#4289, #4424)
144 * (Looks like int overflow.)
145 */
146 LineClip clip = new LineClip(p1, p2, bounds);
147 if (clip.execute()) {
148 if (!p1.equals(clip.getP1())) {
149 p1 = clip.getP1();
150 path.moveTo(p1.x, p1.y);
151 } else if (initialMoveToNeeded) {
152 initialMoveToNeeded = false;
153 path.moveTo(p1.x, p1.y);
154 }
155 p2 = clip.getP2();
156 path.lineTo(p2.x, p2.y);
157
158 /* draw arrow */
159 if (showHeadArrowOnly ? !it.hasNext() : showOrientation) {
160 final double segmentLength = p1.distance(p2);
161 if (segmentLength != 0.0) {
162 final double l = (10. + line.getLineWidth()) / segmentLength;
163
164 final double sx = l * (p1.x - p2.x);
165 final double sy = l * (p1.y - p2.y);
166
167 orientationArrows.moveTo (p2.x + cosPHI * sx - sinPHI * sy, p2.y + sinPHI * sx + cosPHI * sy);
168 orientationArrows.lineTo(p2.x, p2.y);
169 orientationArrows.lineTo (p2.x + cosPHI * sx + sinPHI * sy, p2.y - sinPHI * sx + cosPHI * sy);
170 }
171 }
172 if (showOneway) {
173 final double segmentLength = p1.distance(p2);
174 if (segmentLength != 0.0) {
175 final double nx = (p2.x - p1.x) / segmentLength;
176 final double ny = (p2.y - p1.y) / segmentLength;
177
178 final double interval = 60;
179 // distance from p1
180 double dist = interval - (wayLength % interval);
181
182 while (dist < segmentLength) {
183 for (Pair<Float, GeneralPath> sizeAndPath : Arrays.asList(new Pair[] {
184 new Pair<Float, GeneralPath>(3f, onewayArrowsCasing),
185 new Pair<Float, GeneralPath>(2f, onewayArrows)})) {
186
187 // scale such that border is 1 px
188 final double fac = - (onewayReversed ? -1 : 1) * sizeAndPath.a * (1 + sinPHI) / (sinPHI * cosPHI);
189 final double sx = nx * fac;
190 final double sy = ny * fac;
191
192 // Attach the triangle at the incenter and not at the tip.
193 // Makes the border even at all sides.
194 final double x = p1.x + nx * (dist + (onewayReversed ? -1 : 1) * (sizeAndPath.a / sinPHI));
195 final double y = p1.y + ny * (dist + (onewayReversed ? -1 : 1) * (sizeAndPath.a / sinPHI));
196
197 sizeAndPath.b.moveTo(x, y);
198 sizeAndPath.b.lineTo (x + cosPHI * sx - sinPHI * sy, y + sinPHI * sx + cosPHI * sy);
199 sizeAndPath.b.lineTo (x + cosPHI * sx + sinPHI * sy, y - sinPHI * sx + cosPHI * sy);
200 sizeAndPath.b.lineTo(x, y);
201 }
202 dist += interval;
203 }
204 }
205 wayLength += segmentLength;
206 }
207 }
208 }
209 lastPoint = p;
210 }
211 displaySegments(path, orientationArrows, onewayArrows, onewayArrowsCasing, color, line, dashes, dashedColor);
212 }
213
214 /**
215 * Iterates over a list of Way Nodes and returns screen coordinates that
216 * represent a line that is shifted by a certain offset perpendicular
217 * to the way direction.
218 *
219 * There is no intention, to handle consecutive duplicate Nodes in a
220 * perfect way, but it is should not throw an exception.
221 */
222 public class OffsetIterator implements Iterator<Point> {
223
224 private List<Node> nodes;
225 private int offset;
226 private int idx;
227
228 private Point prev = null;
229 /* 'prev0' is a point that has distance 'offset' from 'prev' and the
230 * line from 'prev' to 'prev0' is perpendicular to the way segment from
231 * 'prev' to the next point.
232 */
233 private int x_prev0, y_prev0;
234
235 public OffsetIterator(List<Node> nodes, int offset) {
236 this.nodes = nodes;
237 this.offset = offset;
238 idx = 0;
239 }
240
241 @Override
242 public boolean hasNext() {
243 return idx < nodes.size();
244 }
245
246 @Override
247 public Point next() {
248 if (offset == 0) return nc.getPoint(nodes.get(idx++));
249
250 Point current = nc.getPoint(nodes.get(idx));
251
252 if (idx == nodes.size() - 1) {
253 ++idx;
254 return new Point(x_prev0 + current.x - prev.x, y_prev0 + current.y - prev.y);
255 }
256
257 Point next = nc.getPoint(nodes.get(idx+1));
258
259 int dx_next = next.x - current.x;
260 int dy_next = next.y - current.y;
261 double len_next = Math.sqrt(dx_next*dx_next + dy_next*dy_next);
262
263 if (len_next == 0) {
264 len_next = 1; // value does not matter, because dy_next and dx_next is 0
265 }
266
267 int x_current0 = current.x + (int) Math.round(offset * dy_next / len_next);
268 int y_current0 = current.y - (int) Math.round(offset * dx_next / len_next);
269
270 if (idx==0) {
271 ++idx;
272 prev = current;
273 x_prev0 = x_current0;
274 y_prev0 = y_current0;
275 return new Point(x_current0, y_current0);
276 } else {
277 int dx_prev = current.x - prev.x;
278 int dy_prev = current.y - prev.y;
279
280 // determine intersection of the lines parallel to the two
281 // segments
282 int det = dx_next*dy_prev - dx_prev*dy_next;
283
284 if (det == 0) {
285 ++idx;
286 prev = current;
287 x_prev0 = x_current0;
288 y_prev0 = y_current0;
289 return new Point(x_current0, y_current0);
290 }
291
292 int m = dx_next*(y_current0 - y_prev0) - dy_next*(x_current0 - x_prev0);
293
294 int cx_ = x_prev0 + (int) Math.round(m * dx_prev / det);
295 int cy_ = y_prev0 + (int) Math.round(m * dy_prev / det);
296 ++idx;
297 prev = current;
298 x_prev0 = x_current0;
299 y_prev0 = y_current0;
300 return new Point(cx_, cy_);
301 }
302 }
303
304 @Override
305 public void remove() {
306 throw new UnsupportedOperationException();
307 }
308 }
309
310 private void displaySegments(GeneralPath path, GeneralPath orientationArrows, GeneralPath onewayArrows, GeneralPath onewayArrowsCasing,
311 Color color, BasicStroke line, BasicStroke dashes, Color dashedColor) {
312 g.setColor(inactive ? inactiveColor : color);
313 if (useStrokes) {
314 g.setStroke(line);
315 }
316 g.draw(path);
317
318 if(!inactive && useStrokes && dashes != null) {
319 g.setColor(dashedColor);
320 g.setStroke(dashes);
321 g.draw(path);
322 }
323
324 if (orientationArrows != null) {
325 g.setColor(inactive ? inactiveColor : color);
326 g.setStroke(new BasicStroke(line.getLineWidth(), line.getEndCap(), BasicStroke.JOIN_MITER, line.getMiterLimit()));
327 g.draw(orientationArrows);
328 }
329
330 if (onewayArrows != null) {
331 g.setStroke(new BasicStroke(1, line.getEndCap(), BasicStroke.JOIN_MITER, line.getMiterLimit()));
332 g.fill(onewayArrowsCasing);
333 g.setColor(inactive ? inactiveColor : backgroundColor);
334 g.fill(onewayArrows);
335 }
336
337 if(useStrokes) {
338 g.setStroke(new BasicStroke());
339 }
340 }
341
342 private boolean isSegmentVisible(Point p1, Point p2) {
343 if ((p1.x < 0) && (p2.x < 0)) return false;
344 if ((p1.y < 0) && (p2.y < 0)) return false;
345 if ((p1.x > nc.getWidth()) && (p2.x > nc.getWidth())) return false;
346 if ((p1.y > nc.getHeight()) && (p2.y > nc.getHeight())) return false;
347 return true;
348 }
349
350 public void drawTextOnPath(Way way, TextElement text) {
351 if (text == null)
352 return;
353 String name = text.getString(way);
354 if (name == null || name.equals(""))
355 return;
356
357 Polygon poly = new Polygon();
358 Point lastPoint = null;
359 Iterator<Node> it = way.getNodes().iterator();
360 double pathLength = 0;
361 int dx, dy;
362 while (it.hasNext()) {
363 Node n = it.next();
364 Point p = nc.getPoint(n);
365 poly.addPoint(p.x, p.y);
366
367 if(lastPoint != null) {
368 dx = p.x - lastPoint.x;
369 dy = p.y - lastPoint.y;
370 pathLength += Math.sqrt(dx*dx + dy*dy);
371 }
372 lastPoint = p;
373 }
374
375 FontMetrics fontMetrics = g.getFontMetrics(text.font); // if slow, use cache
376 Rectangle2D rec = fontMetrics.getStringBounds(name, g); // if slow, approximate by strlen()*maxcharbounds(font)
377
378 if (rec.getWidth() > pathLength)
379 return;
380
381 double t1 = (pathLength/2 - rec.getWidth()/2) / pathLength;
382 double t2 = (pathLength/2 + rec.getWidth()/2) / pathLength;
383
384 double[] p1 = pointAt(t1, poly, pathLength);
385 double[] p2 = pointAt(t2, poly, pathLength);
386
387 double angleOffset;
388 double offsetSign;
389 double tStart;
390
391 if (p1[0] < p2[0] &&
392 p1[2] < Math.PI/2 &&
393 p1[2] > -Math.PI/2) {
394 angleOffset = 0;
395 offsetSign = 1;
396 tStart = t1;
397 } else {
398 angleOffset = Math.PI;
399 offsetSign = -1;
400 tStart = t2;
401 }
402
403 FontRenderContext frc = g.getFontRenderContext();
404 GlyphVector gv = text.font.createGlyphVector(frc, name);
405
406 for (int i=0; i<gv.getNumGlyphs(); ++i) {
407 Rectangle2D rect = gv.getGlyphLogicalBounds(i).getBounds2D();
408 double t = tStart + offsetSign * (rect.getX() + rect.getWidth()/2) / pathLength;
409 double[] p = pointAt(t, poly, pathLength);
410 AffineTransform trfm = AffineTransform.getTranslateInstance(p[0] - rect.getX(), p[1]);
411 trfm.rotate(p[2]+angleOffset);
412 double off = -rect.getY() - rect.getHeight()/2 + text.yOffset;
413 trfm.translate(-rect.getWidth()/2, off);
414 gv.setGlyphTransform(i, trfm);
415 }
416 if (text.haloRadius != null) {
417 Shape textOutline = gv.getOutline();
418 g.setStroke(new BasicStroke(2*text.haloRadius, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND));
419 g.setColor(text.haloColor);
420 g.draw(textOutline);
421 g.setStroke(new BasicStroke());
422 g.setColor(text.color);
423 g.fill(textOutline);
424 } else {
425 g.setColor(text.color);
426 g.drawGlyphVector(gv, 0, 0);
427 }
428 }
429
430 private double[] pointAt(double t, Polygon poly, double pathLength) {
431 double totalLen = t * pathLength;
432 double curLen = 0;
433 int dx, dy;
434 double segLen;
435
436 // Yes, it is ineffecient to iterate from the beginning for each glyph.
437 // Can be optimized if it turns out to be slow.
438 for (int i = 1; i < poly.npoints; ++i) {
439 dx = poly.xpoints[i] - poly.xpoints[i-1];
440 dy = poly.ypoints[i] - poly.ypoints[i-1];
441 segLen = Math.sqrt(dx*dx + dy*dy);
442 if (totalLen > curLen + segLen) {
443 curLen += segLen;
444 continue;
445 }
446 return new double[] {poly.xpoints[i-1]+(totalLen - curLen)/segLen*dx,
447 poly.ypoints[i-1]+(totalLen - curLen)/segLen*dy,
448 Math.atan2(dy, dx)};
449 }
450 return null;
451 }
452
453 public void drawLinePattern(Way way, ImageIcon pattern) {
454 final int width = pattern.getIconWidth();
455 final int height = pattern.getIconHeight();
456
457 Point lastP = null;
458 double wayLength = 0;
459
460 Iterator<Node> it = way.getNodes().iterator();
461 while (it.hasNext()) {
462 Node n = it.next();
463 Point thisP = nc.getPoint(n);
464
465 if (lastP != null) {
466 final double segmentLength = thisP.distance(lastP);
467
468 final double dx = thisP.x - lastP.x;
469 final double dy = thisP.y - lastP.y;
470
471 double dist = wayLength == 0 ? 0 : width - (wayLength % width);
472
473 AffineTransform saveTransform = g.getTransform();
474 g.translate(lastP.x, lastP.y);
475 g.rotate(Math.atan2(dy, dx));
476
477 if (dist > 0) {
478 g.drawImage(pattern.getImage(), 0, 0, (int) dist, height,
479 width - (int) dist, 0, width, height, null);
480 }
481 while (dist < segmentLength) {
482 if (dist + width > segmentLength) {
483 g.drawImage(pattern.getImage(), (int) dist, 0, (int) segmentLength, height,
484 0, 0, (int) segmentLength - (int) dist, height, null);
485 } else {
486 pattern.paintIcon(nc, g, (int) dist, 0);
487 }
488 dist += width;
489 }
490 g.setTransform(saveTransform);
491
492 wayLength += segmentLength;
493 }
494 lastP = thisP;
495 }
496 }
497
498 public void drawNodeIcon(Node n, ImageIcon icon, float iconAlpha, boolean selected, boolean member, NodeTextElement text) {
499 Point p = nc.getPoint(n);
500 if ((p.x < 0) || (p.y < 0) || (p.x > nc.getWidth()) || (p.y > nc.getHeight())) return;
501
502 int w = icon.getIconWidth(), h=icon.getIconHeight();
503 if (iconAlpha != 1f) {
504 g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, iconAlpha));
505 }
506 icon.paintIcon ( nc, g, p.x-w/2, p.y-h/2 );
507 g.setPaintMode();
508 drawNodeText(n, text, p, w/2, h/2);
509 if (selected || member)
510 {
511 g.setColor(selected? selectedColor : relationSelectedColor);
512 g.drawRect(p.x-w/2-2, p.y-h/2-2, w+4, h+4);
513 }
514 }
515
516 private Polygon buildPolygon(Point center, int radius, int sides, double rotation) {
517 Polygon polygon = new Polygon();
518 for (int i = 0; i < sides; i++) {
519 double angle = ((2 * Math.PI / sides) * i) - rotation;
520 int x = (int) Math.round(center.x + radius * Math.cos(angle));
521 int y = (int) Math.round(center.y + radius * Math.sin(angle));
522 polygon.addPoint(x, y);
523 }
524 return polygon;
525 }
526
527 private Polygon buildPolygon(Point center, int radius, int sides) {
528 return buildPolygon(center, radius, sides, 0.0);
529 }
530
531 public void drawNodeSymbol(Node n, Symbol s, Color fillColor, Color strokeColor, NodeTextElement text) {
532 Point p = nc.getPoint(n);
533 if ((p.x < 0) || (p.y < 0) || (p.x > nc.getWidth()) || (p.y > nc.getHeight())) return;
534 int radius = s.size / 2;
535
536 if (fillColor != null) {
537 g.setColor(fillColor);
538 switch (s.symbol) {
539 case SQUARE:
540 g.fillRect(p.x - radius, p.y - radius, s.size, s.size);
541 break;
542 case CIRCLE:
543 g.fillOval(p.x - radius, p.y - radius, s.size, s.size);
544 break;
545 case TRIANGLE:
546 g.fillPolygon(buildPolygon(p, radius, 3, Math.PI / 2));
547 break;
548 case PENTAGON:
549 g.fillPolygon(buildPolygon(p, radius, 5, Math.PI / 2));
550 break;
551 case HEXAGON:
552 g.fillPolygon(buildPolygon(p, radius, 6));
553 break;
554 case HEPTAGON:
555 g.fillPolygon(buildPolygon(p, radius, 7, Math.PI / 2));
556 break;
557 case OCTAGON:
558 g.fillPolygon(buildPolygon(p, radius, 8, Math.PI / 8));
559 break;
560 case NONAGON:
561 g.fillPolygon(buildPolygon(p, radius, 9, Math.PI / 2));
562 break;
563 case DECAGON:
564 g.fillPolygon(buildPolygon(p, radius, 10));
565 break;
566 default:
567 throw new AssertionError();
568 }
569 }
570 if (s.stroke != null) {
571 g.setStroke(s.stroke);
572 g.setColor(strokeColor);
573 switch (s.symbol) {
574 case SQUARE:
575 g.drawRect(p.x - radius, p.y - radius, s.size - 1, s.size - 1);
576 break;
577 case CIRCLE:
578 g.drawOval(p.x - radius, p.y - radius, s.size - 1, s.size - 1);
579 break;
580 case TRIANGLE:
581 g.drawPolygon(buildPolygon(p, radius, 3, Math.PI / 2));
582 break;
583 case PENTAGON:
584 g.drawPolygon(buildPolygon(p, radius, 5, Math.PI / 2));
585 break;
586 case HEXAGON:
587 g.drawPolygon(buildPolygon(p, radius, 6));
588 break;
589 case HEPTAGON:
590 g.drawPolygon(buildPolygon(p, radius, 7, Math.PI / 2));
591 break;
592 case OCTAGON:
593 g.drawPolygon(buildPolygon(p, radius, 8, Math.PI / 8));
594 break;
595 case NONAGON:
596 g.drawPolygon(buildPolygon(p, radius, 9, Math.PI / 2));
597 break;
598 case DECAGON:
599 g.drawPolygon(buildPolygon(p, radius, 10));
600 break;
601 default:
602 throw new AssertionError();
603 }
604 g.setStroke(new BasicStroke());
605 }
606 drawNodeText(n, text, p, radius, radius);
607 }
608
609 /**
610 * Draw the node as small rectangle with the given color.
611 *
612 * @param n The node to draw.
613 * @param color The color of the node.
614 */
615 public void drawNode(Node n, Color color, int size, boolean fill, NodeTextElement text) {
616 if (size > 1) {
617 Point p = nc.getPoint(n);
618 if ((p.x < 0) || (p.y < 0) || (p.x > nc.getWidth()) || (p.y > nc.getHeight())) return;
619 int radius = size / 2;
620
621 if (inactive || n.isDisabled()) {
622 g.setColor(inactiveColor);
623 } else {
624 g.setColor(color);
625 }
626 if (fill) {
627 g.fillRect(p.x - radius, p.y - radius, size + 1, size + 1);
628 } else {
629 g.drawRect(p.x - radius, p.y - radius, size, size);
630 }
631
632 drawNodeText(n, text, p, radius, radius + 4);
633 }
634 }
635
636 private void drawNodeText(Node n, NodeTextElement text, Point p, int w_half, int h_half) {
637 if (!isShowNames() || text == null)
638 return;
639
640 /*
641 * abort if we can't compose the label to be rendered
642 */
643 if (text.labelCompositionStrategy == null) return;
644 String s = text.labelCompositionStrategy.compose(n);
645 if (s == null) return;
646
647 Font defaultFont = g.getFont();
648 g.setFont(text.font);
649
650 int x = p.x + text.xOffset;
651 int y = p.y + text.yOffset;
652 /**
653 *
654 * left-above __center-above___ right-above
655 * left-top| |right-top
656 * | |
657 * left-center| center-center |right-center
658 * | |
659 * left-bottom|_________________|right-bottom
660 * left-below center-below right-below
661 *
662 */
663 if (text.hAlign == HorizontalTextAlignment.RIGHT) {
664 x += w_half + 2;
665 } else {
666 FontRenderContext frc = g.getFontRenderContext();
667 Rectangle2D bounds = text.font.getStringBounds(s, frc);
668 int textWidth = (int) bounds.getWidth();
669 if (text.hAlign == HorizontalTextAlignment.CENTER) {
670 x -= textWidth / 2;
671 } else if (text.hAlign == HorizontalTextAlignment.LEFT) {
672 x -= w_half + 4 + textWidth;
673 } else throw new AssertionError();
674 }
675
676 if (text.vAlign == VerticalTextAlignment.BOTTOM) {
677 y += h_half - 2;
678 } else {
679 FontRenderContext frc = g.getFontRenderContext();
680 LineMetrics metrics = text.font.getLineMetrics(s, frc);
681 if (text.vAlign == VerticalTextAlignment.ABOVE) {
682 y -= h_half + metrics.getDescent();
683 } else if (text.vAlign == VerticalTextAlignment.TOP) {
684 y -= h_half - metrics.getAscent();
685 } else if (text.vAlign == VerticalTextAlignment.CENTER) {
686 y += (metrics.getAscent() - metrics.getDescent()) / 2;
687 } else if (text.vAlign == VerticalTextAlignment.BELOW) {
688 y += h_half + metrics.getAscent() + 2;
689 } else throw new AssertionError();
690 }
691 if (inactive || n.isDisabled()) {
692 g.setColor(inactiveColor);
693 } else {
694 g.setColor(text.color);
695 }
696 if (text.haloRadius != null) {
697 g.setStroke(new BasicStroke(2*text.haloRadius, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND));
698 g.setColor(text.haloColor);
699 FontRenderContext frc = g.getFontRenderContext();
700 GlyphVector gv = text.font.createGlyphVector(frc, s);
701 Shape textOutline = gv.getOutline(x, y);
702 g.draw(textOutline);
703 g.setStroke(new BasicStroke());
704 g.setColor(text.color);
705 g.fill(textOutline);
706 } else {
707 g.drawString(s, x, y);
708 }
709 g.setFont(defaultFont);
710 }
711
712 private Polygon getPolygon(Way w) {
713 Polygon polygon = new Polygon();
714
715 for (Node n : w.getNodes()) {
716 Point p = nc.getPoint(n);
717 polygon.addPoint(p.x,p.y);
718 }
719 return polygon;
720 }
721
722 public void drawArea(Way w, Color color, BufferedImage fillImage, float fillImageAlpha, TextElement text) {
723 Polygon polygon = getPolygon(w);
724 drawArea(w, polygon, color, fillImage, fillImageAlpha, text);
725 }
726
727 protected void drawArea(OsmPrimitive osm, Polygon polygon, Color color, BufferedImage fillImage, float fillImageAlpha, TextElement text) {
728
729 if (!isOutlineOnly) {
730 if (fillImage == null) {
731 g.setColor(color);
732 g.fillPolygon(polygon);
733 } else {
734 TexturePaint texture = new TexturePaint(fillImage,
735 new Rectangle(polygon.xpoints[0], polygon.ypoints[0], fillImage.getWidth(), fillImage.getHeight()));
736 g.setPaint(texture);
737 if (fillImageAlpha != 1f) {
738 g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, fillImageAlpha));
739 }
740 g.fill(polygon);
741 g.setPaintMode();
742 }
743 }
744
745 if (text != null && isShowNames()) {
746 /*
747 * abort if we can't compose the label to be rendered
748 */
749 if (text.labelCompositionStrategy == null) return;
750 String name = text.labelCompositionStrategy.compose(osm);
751 if (name == null) return;
752
753 Rectangle pb = polygon.getBounds();
754 FontMetrics fontMetrics = g.getFontMetrics(orderFont); // if slow, use cache
755 Rectangle2D nb = fontMetrics.getStringBounds(name, g); // if slow, approximate by strlen()*maxcharbounds(font)
756
757 // Point2D c = getCentroid(polygon);
758 // Using the Centroid is Nicer for buildings like: +--------+
759 // but this needs to be fast. As most houses are | 42 |
760 // boxes anyway, the center of the bounding box +---++---+
761 // will have to do. ++
762 // Centroids are not optimal either, just imagine a U-shaped house.
763 // Point2D c = new Point2D.Double(pb.x + pb.width / 2.0, pb.y + pb.height / 2.0);
764 // Rectangle2D.Double centeredNBounds =
765 // new Rectangle2D.Double(c.getX() - nb.getWidth()/2,
766 // c.getY() - nb.getHeight()/2,
767 // nb.getWidth(),
768 // nb.getHeight());
769
770 Rectangle centeredNBounds = new Rectangle(pb.x + (int)((pb.width - nb.getWidth())/2.0),
771 pb.y + (int)((pb.height - nb.getHeight())/2.0),
772 (int)nb.getWidth(),
773 (int)nb.getHeight());
774
775 if ((pb.width >= nb.getWidth() && pb.height >= nb.getHeight()) && // quick check
776 polygon.contains(centeredNBounds) // slow but nice
777 ) {
778 g.setColor(text.color);
779 Font defaultFont = g.getFont();
780 g.setFont (text.font);
781 g.drawString (name,
782 (int)(centeredNBounds.getMinX() - nb.getMinX()),
783 (int)(centeredNBounds.getMinY() - nb.getMinY()));
784 g.setFont(defaultFont);
785 }
786 }
787 }
788
789 public void drawArea(Relation r, Color color, BufferedImage fillImage, float fillImageAlpha, TextElement text) {
790 Multipolygon multipolygon = new Multipolygon(nc);
791 multipolygon.load(r);
792 if(!r.isDisabled() && !multipolygon.getOuterWays().isEmpty()) {
793 for (PolyData pd : multipolygon.getCombinedPolygons()) {
794 Polygon p = pd.get();
795 if(!isPolygonVisible(p)) {
796 continue;
797 }
798 drawArea(r, p,
799 pd.selected ? settings.getRelationSelectedColor(color.getAlpha()) : color,
800 fillImage, fillImageAlpha, text);
801 }
802 }
803 }
804
805 private boolean isPolygonVisible(Polygon polygon) {
806 Rectangle bounds = polygon.getBounds();
807 if (bounds.width == 0 && bounds.height == 0) return false;
808 if (bounds.x > nc.getWidth()) return false;
809 if (bounds.y > nc.getHeight()) return false;
810 if (bounds.x + bounds.width < 0) return false;
811 if (bounds.y + bounds.height < 0) return false;
812 return true;
813 }
814
815 public void drawRestriction(ImageIcon icon, Point pVia, double vx, double vx2, double vy, double vy2, double iconAngle, boolean selected) {
816 /* rotate icon with direction last node in from to */
817 ImageIcon rotatedIcon = ImageProvider.createRotatedImage(null /*icon2*/, icon, iconAngle);
818
819 /* scale down icon to 16*16 pixels */
820 ImageIcon smallIcon = new ImageIcon(rotatedIcon.getImage().getScaledInstance(16 , 16, Image.SCALE_SMOOTH));
821 int w = smallIcon.getIconWidth(), h=smallIcon.getIconHeight();
822 smallIcon.paintIcon (nc, g, (int)(pVia.x+vx+vx2)-w/2, (int)(pVia.y+vy+vy2)-h/2 );
823
824 if (selected) {
825 g.setColor(relationSelectedColor);
826 g.drawRect((int)(pVia.x+vx+vx2)-w/2-2,(int)(pVia.y+vy+vy2)-h/2-2, w+4, h+4);
827 }
828 }
829
830 public void drawRestriction(Relation r, NodeElemStyle icon) {
831
832 Way fromWay = null;
833 Way toWay = null;
834 OsmPrimitive via = null;
835
836 /* find the "from", "via" and "to" elements */
837 for (RelationMember m : r.getMembers())
838 {
839 if(m.getMember().isIncomplete())
840 return;
841 else
842 {
843 if(m.isWay())
844 {
845 Way w = m.getWay();
846 if(w.getNodesCount() < 2) {
847 continue;
848 }
849
850 if("from".equals(m.getRole())) {
851 if(fromWay == null) {
852 fromWay = w;
853 }
854 } else if("to".equals(m.getRole())) {
855 if(toWay == null) {
856 toWay = w;
857 }
858 } else if("via".equals(m.getRole())) {
859 if(via == null) {
860 via = w;
861 }
862 }
863 }
864 else if(m.isNode())
865 {
866 Node n = m.getNode();
867 if("via".equals(m.getRole()) && via == null) {
868 via = n;
869 }
870 }
871 }
872 }
873
874 if (fromWay == null || toWay == null || via == null)
875 return;
876
877 Node viaNode;
878 if(via instanceof Node)
879 {
880 viaNode = (Node) via;
881 if(!fromWay.isFirstLastNode(viaNode))
882 return;
883 }
884 else
885 {
886 Way viaWay = (Way) via;
887 Node firstNode = viaWay.firstNode();
888 Node lastNode = viaWay.lastNode();
889 Boolean onewayvia = false;
890
891 String onewayviastr = viaWay.get("oneway");
892 if(onewayviastr != null)
893 {
894 if("-1".equals(onewayviastr)) {
895 onewayvia = true;
896 Node tmp = firstNode;
897 firstNode = lastNode;
898 lastNode = tmp;
899 } else {
900 onewayvia = OsmUtils.getOsmBoolean(onewayviastr);
901 if (onewayvia == null) {
902 onewayvia = false;
903 }
904 }
905 }
906
907 if(fromWay.isFirstLastNode(firstNode)) {
908 viaNode = firstNode;
909 } else if (!onewayvia && fromWay.isFirstLastNode(lastNode)) {
910 viaNode = lastNode;
911 } else
912 return;
913 }
914
915 /* find the "direct" nodes before the via node */
916 Node fromNode = null;
917 if(fromWay.firstNode() == via) {
918 fromNode = fromWay.getNode(1);
919 } else {
920 fromNode = fromWay.getNode(fromWay.getNodesCount()-2);
921 }
922
923 Point pFrom = nc.getPoint(fromNode);
924 Point pVia = nc.getPoint(viaNode);
925
926 /* starting from via, go back the "from" way a few pixels
927 (calculate the vector vx/vy with the specified length and the direction
928 away from the "via" node along the first segment of the "from" way)
929 */
930 double distanceFromVia=14;
931 double dx = (pFrom.x >= pVia.x) ? (pFrom.x - pVia.x) : (pVia.x - pFrom.x);
932 double dy = (pFrom.y >= pVia.y) ? (pFrom.y - pVia.y) : (pVia.y - pFrom.y);
933
934 double fromAngle;
935 if(dx == 0.0) {
936 fromAngle = Math.PI/2;
937 } else {
938 fromAngle = Math.atan(dy / dx);
939 }
940 double fromAngleDeg = Math.toDegrees(fromAngle);
941
942 double vx = distanceFromVia * Math.cos(fromAngle);
943 double vy = distanceFromVia * Math.sin(fromAngle);
944
945 if(pFrom.x < pVia.x) {
946 vx = -vx;
947 }
948 if(pFrom.y < pVia.y) {
949 vy = -vy;
950 }
951
952 /* go a few pixels away from the way (in a right angle)
953 (calculate the vx2/vy2 vector with the specified length and the direction
954 90degrees away from the first segment of the "from" way)
955 */
956 double distanceFromWay=10;
957 double vx2 = 0;
958 double vy2 = 0;
959 double iconAngle = 0;
960
961 if(pFrom.x >= pVia.x && pFrom.y >= pVia.y) {
962 if(!leftHandTraffic) {
963 vx2 = distanceFromWay * Math.cos(Math.toRadians(fromAngleDeg - 90));
964 vy2 = distanceFromWay * Math.sin(Math.toRadians(fromAngleDeg - 90));
965 } else {
966 vx2 = distanceFromWay * Math.cos(Math.toRadians(fromAngleDeg + 90));
967 vy2 = distanceFromWay * Math.sin(Math.toRadians(fromAngleDeg + 90));
968 }
969 iconAngle = 270+fromAngleDeg;
970 }
971 if(pFrom.x < pVia.x && pFrom.y >= pVia.y) {
972 if(!leftHandTraffic) {
973 vx2 = distanceFromWay * Math.sin(Math.toRadians(fromAngleDeg));
974 vy2 = distanceFromWay * Math.cos(Math.toRadians(fromAngleDeg));
975 } else {
976 vx2 = distanceFromWay * Math.sin(Math.toRadians(fromAngleDeg + 180));
977 vy2 = distanceFromWay * Math.cos(Math.toRadians(fromAngleDeg + 180));
978 }
979 iconAngle = 90-fromAngleDeg;
980 }
981 if(pFrom.x < pVia.x && pFrom.y < pVia.y) {
982 if(!leftHandTraffic) {
983 vx2 = distanceFromWay * Math.cos(Math.toRadians(fromAngleDeg + 90));
984 vy2 = distanceFromWay * Math.sin(Math.toRadians(fromAngleDeg + 90));
985 } else {
986 vx2 = distanceFromWay * Math.cos(Math.toRadians(fromAngleDeg - 90));
987 vy2 = distanceFromWay * Math.sin(Math.toRadians(fromAngleDeg - 90));
988 }
989 iconAngle = 90+fromAngleDeg;
990 }
991 if(pFrom.x >= pVia.x && pFrom.y < pVia.y) {
992 if(!leftHandTraffic) {
993 vx2 = distanceFromWay * Math.sin(Math.toRadians(fromAngleDeg + 180));
994 vy2 = distanceFromWay * Math.cos(Math.toRadians(fromAngleDeg + 180));
995 } else {
996 vx2 = distanceFromWay * Math.sin(Math.toRadians(fromAngleDeg));
997 vy2 = distanceFromWay * Math.cos(Math.toRadians(fromAngleDeg));
998 }
999 iconAngle = 270-fromAngleDeg;
1000 }
1001
1002 drawRestriction(inactive || r.isDisabled() ? icon.getDisabledIcon() : icon.icon,
1003 pVia, vx, vx2, vy, vy2, iconAngle, r.isSelected());
1004 }
1005
1006 public void drawVirtualNodes(Collection<Way> ways) {
1007
1008 if (virtualNodeSize != 0) {
1009 GeneralPath path = new GeneralPath();
1010 for (Way osm: ways){
1011 if (osm.isUsable() && !osm.isDisabled()) {
1012 visitVirtual(path, osm);
1013 }
1014 }
1015 g.setColor(nodeColor);
1016 g.draw(path);
1017 }
1018 }
1019
1020 public void visitVirtual(GeneralPath path, Way w) {
1021 Iterator<Node> it = w.getNodes().iterator();
1022 if (it.hasNext()) {
1023 Point lastP = nc.getPoint(it.next());
1024 while(it.hasNext())
1025 {
1026 Point p = nc.getPoint(it.next());
1027 if(isSegmentVisible(lastP, p) && isLargeSegment(lastP, p, virtualNodeSpace))
1028 {
1029 int x = (p.x+lastP.x)/2;
1030 int y = (p.y+lastP.y)/2;
1031 path.moveTo(x-virtualNodeSize, y);
1032 path.lineTo(x+virtualNodeSize, y);
1033 path.moveTo(x, y-virtualNodeSize);
1034 path.lineTo(x, y+virtualNodeSize);
1035 }
1036 lastP = p;
1037 }
1038 }
1039 }
1040
1041 private static boolean isLargeSegment(Point p1, Point p2, int space) {
1042 int xd = p1.x-p2.x; if(xd < 0) {
1043 xd = -xd;
1044 }
1045 int yd = p1.y-p2.y; if(yd < 0) {
1046 yd = -yd;
1047 }
1048 return (xd+yd > space);
1049 }
1050
1051 /**
1052 * Draw a number of the order of the two consecutive nodes within the
1053 * parents way
1054 */
1055 public void drawOrderNumber(Node n1, Node n2, int orderNumber, Color clr) {
1056 Point p1 = nc.getPoint(n1);
1057 Point p2 = nc.getPoint(n2);
1058 drawOrderNumber(p1, p2, orderNumber, clr);
1059 }
1060
1061 /**
1062 * Draw an number of the order of the two consecutive nodes within the
1063 * parents way
1064 */
1065 protected void drawOrderNumber(Point p1, Point p2, int orderNumber, Color clr) {
1066 if (isSegmentVisible(p1, p2) && isLargeSegment(p1, p2, segmentNumberSpace)) {
1067 String on = Integer.toString(orderNumber);
1068 int strlen = on.length();
1069 int x = (p1.x+p2.x)/2 - 4*strlen;
1070 int y = (p1.y+p2.y)/2 + 4;
1071
1072 if(virtualNodeSize != 0 && isLargeSegment(p1, p2, virtualNodeSpace))
1073 {
1074 y = (p1.y+p2.y)/2 - virtualNodeSize - 3;
1075 }
1076
1077 g.setColor(backgroundColor);
1078 g.fillRect(x-1, y-12, 8*strlen+1, 14);
1079 g.setColor(clr);
1080 g.drawString(on, x, y);
1081 }
1082 }
1083
1084 public boolean isShowNames() {
1085 return showNames;
1086 }
1087
1088 public double getCircum() {
1089 return circum;
1090 }
1091
1092 public boolean isShowIcons() {
1093 return showIcons;
1094 }
1095
1096 public boolean isInactiveMode() {
1097 return inactive;
1098 }
1099}
Note: See TracBrowser for help on using the repository browser.