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

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

mapcss: several small fixes

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