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

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

improve potlatch 2 style

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