source: josm/trunk/src/org/openstreetmap/josm/data/osm/visitor/paint/SimplePaintVisitor.java@ 3565

Last change on this file since 3565 was 3565, checked in by bastiK, 14 years ago

Draw ways as a continuous line and not each segment separately. This improves dashes drawing and joining segments for higher width values. It now always uses Cohen-Sutherland line clipping (not only for OpenJDK).

  • Property svn:eol-style set to native
File size: 19.5 KB
Line 
1/* License: GPL. Copyright 2007 by Immanuel Scholz and others */
2package org.openstreetmap.josm.data.osm.visitor.paint;
3
4/* To enable debugging or profiling remove the double / signs */
5
6import java.awt.BasicStroke;
7import java.awt.Color;
8import java.awt.Graphics2D;
9import java.awt.Point;
10import java.awt.Polygon;
11import java.awt.Rectangle;
12import java.awt.RenderingHints;
13import java.awt.Stroke;
14import java.awt.geom.GeneralPath;
15import java.util.Collection;
16import java.util.Iterator;
17
18import org.openstreetmap.josm.Main;
19import org.openstreetmap.josm.data.Bounds;
20import org.openstreetmap.josm.data.osm.BBox;
21import org.openstreetmap.josm.data.osm.DataSet;
22import org.openstreetmap.josm.data.osm.Node;
23import org.openstreetmap.josm.data.osm.OsmPrimitive;
24import org.openstreetmap.josm.data.osm.Relation;
25import org.openstreetmap.josm.data.osm.RelationMember;
26import org.openstreetmap.josm.data.osm.Way;
27import org.openstreetmap.josm.data.osm.visitor.AbstractVisitor;
28import org.openstreetmap.josm.data.osm.visitor.paint.MapPaintSettings;
29import org.openstreetmap.josm.gui.NavigatableComponent;
30
31/**
32 * A visitor that paints a simple scheme of every primitive it visits to a
33 * previous set graphic environment.
34 *
35 * @author imi
36 */
37public class SimplePaintVisitor extends AbstractVisitor implements PaintVisitor {
38 /**
39 * The environment to paint to.
40 */
41 protected Graphics2D g;
42 /**
43 * MapView to get screen coordinates.
44 */
45 protected NavigatableComponent nc;
46
47 public boolean inactive;
48
49 /**
50 * Preferences
51 */
52 protected Color inactiveColor;
53 protected Color selectedColor;
54 protected Color nodeColor;
55 protected Color dfltWayColor;
56 protected Color relationColor;
57 protected Color untaggedWayColor;
58 protected Color incompleteColor;
59 protected Color backgroundColor;
60 protected Color highlightColor;
61 protected Color taggedColor;
62 protected Color connectionColor;
63 protected Color taggedConnectionColor;
64 protected boolean showDirectionArrow;
65 protected boolean showRelevantDirectionsOnly;
66 protected boolean showHeadArrowOnly;
67 protected boolean showOrderNumber;
68 protected boolean fillSelectedNode;
69 protected boolean fillUnselectedNode;
70 protected boolean fillTaggedNode;
71 protected boolean fillConnectionNode;
72 protected int selectedNodeSize;
73 protected int unselectedNodeSize;
74 protected int connectionNodeSize;
75 protected int taggedNodeSize;
76 protected int defaultSegmentWidth;
77 protected int virtualNodeSize;
78 protected int virtualNodeSpace;
79 protected int segmentNumberSpace;
80
81 /**
82 * Draw subsequent segments of same color as one Path
83 */
84 protected Color currentColor = null;
85 protected GeneralPath currentPath = new GeneralPath();
86
87 public void getColors()
88 {
89 inactiveColor = PaintColors.INACTIVE.get();
90 selectedColor = PaintColors.SELECTED.get();
91 nodeColor = PaintColors.NODE.get();
92 dfltWayColor = PaintColors.DEFAULT_WAY.get();
93 relationColor = PaintColors.RELATION.get();
94 untaggedWayColor = PaintColors.UNTAGGED_WAY.get();
95 incompleteColor = PaintColors.INCOMPLETE_WAY.get();
96 backgroundColor = PaintColors.BACKGROUND.get();
97 highlightColor = PaintColors.HIGHLIGHT.get();
98 taggedColor = PaintColors.TAGGED.get();
99 connectionColor = PaintColors.CONNECTION.get();
100
101 if (taggedColor != nodeColor) {
102 taggedConnectionColor = taggedColor;
103 } else {
104 taggedConnectionColor = connectionColor;
105 }
106 }
107
108 protected void getSettings(boolean virtual) {
109 MapPaintSettings settings = MapPaintSettings.INSTANCE;
110 showDirectionArrow = settings.isShowDirectionArrow();
111 showRelevantDirectionsOnly = settings.isShowRelevantDirectionsOnly();
112 showHeadArrowOnly = settings.isShowHeadArrowOnly();
113 showOrderNumber = settings.isShowOrderNumber();
114 selectedNodeSize = settings.getSelectedNodeSize();
115 unselectedNodeSize = settings.getUnselectedNodeSize();
116 connectionNodeSize = settings.getConnectionNodeSize();
117 taggedNodeSize = settings.getTaggedNodeSize();
118 defaultSegmentWidth = settings.getDefaultSegmentWidth();
119 fillSelectedNode = settings.isFillSelectedNode();
120 fillUnselectedNode = settings.isFillUnselectedNode();
121 fillConnectionNode = settings.isFillConnectionNode();
122 fillTaggedNode = settings.isFillTaggedNode();
123 virtualNodeSize = virtual ? Main.pref.getInteger("mappaint.node.virtual-size", 8) / 2 : 0;
124 virtualNodeSpace = Main.pref.getInteger("mappaint.node.virtual-space", 70);
125 segmentNumberSpace = Main.pref.getInteger("mappaint.segmentnumber.space", 40);
126 getColors();
127
128 g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
129 Main.pref.getBoolean("mappaint.use-antialiasing", false) ?
130 RenderingHints.VALUE_ANTIALIAS_ON : RenderingHints.VALUE_ANTIALIAS_OFF);
131 }
132
133 DataSet ds;
134 public void visitAll(DataSet data, boolean virtual, Bounds bounds) {
135 BBox bbox = new BBox(bounds);
136 this.ds = data;
137 //boolean profiler = Main.pref.getBoolean("simplepaint.profiler",false);
138 //long profilerStart = java.lang.System.currentTimeMillis();
139 //long profilerLast = profilerStart;
140 //int profilerN = 0;
141 //if(profiler)
142 // System.out.println("Simplepaint Profiler");
143
144 getSettings(virtual);
145
146 //if(profiler)
147 //{
148 // System.out.format("Prepare : %4dms\n", (java.lang.System.currentTimeMillis()-profilerLast));
149 // profilerLast = java.lang.System.currentTimeMillis();
150 //}
151
152 /* draw tagged ways first, then untagged ways. takes
153 time to iterate through list twice, OTOH does not
154 require changing the colour while painting... */
155 //profilerN = 0;
156 for (final OsmPrimitive osm: data.searchRelations(bbox)) {
157 if (!osm.isDeleted() && !ds.isSelected(osm) && !osm.isDisabledAndHidden()) {
158 osm.visit(this);
159 // profilerN++;
160 }
161 }
162
163 //if(profiler)
164 //{
165 // System.out.format("Relations: %4dms, n=%5d\n", (java.lang.System.currentTimeMillis()-profilerLast), profilerN);
166 // profilerLast = java.lang.System.currentTimeMillis();
167 //}
168
169 //profilerN = 0;
170 for (final OsmPrimitive osm:data.searchWays(bbox)){
171 if (!osm.isDeleted() && !ds.isSelected(osm) && !osm.isDisabledAndHidden() && osm.isTagged()) {
172 osm.visit(this);
173 // profilerN++;
174 }
175 }
176 displaySegments();
177
178 for (final OsmPrimitive osm:data.searchWays(bbox)){
179 if (!osm.isDeleted() && !ds.isSelected(osm) && !osm.isDisabledAndHidden() && !osm.isTagged()) {
180 osm.visit(this);
181 // profilerN++;
182 }
183 }
184 displaySegments();
185
186 //if(profiler)
187 //{
188 // System.out.format("Ways : %4dms, n=%5d\n",
189 // (java.lang.System.currentTimeMillis()-profilerLast), profilerN);
190 // profilerLast = java.lang.System.currentTimeMillis();
191 //}
192
193 //profilerN = 0;
194 for (final OsmPrimitive osm : data.getSelected())
195 if (!osm.isDeleted()) {
196 osm.visit(this);
197 // profilerN++;
198 }
199 displaySegments();
200
201 //if(profiler)
202 //{
203 // System.out.format("Selected : %4dms, n=%5d\n", (java.lang.System.currentTimeMillis()-profilerLast), profilerN);
204 // profilerLast = java.lang.System.currentTimeMillis();
205 //}
206
207 //profilerN = 0;
208 for (final OsmPrimitive osm: data.searchNodes(bbox)) {
209 if (!osm.isDeleted() && !ds.isSelected(osm) && !osm.isDisabledAndHidden())
210 {
211 osm.visit(this);
212 // profilerN++;
213 }
214 }
215
216 //if(profiler)
217 //{
218 // System.out.format("Nodes : %4dms, n=%5d\n",
219 // (java.lang.System.currentTimeMillis()-profilerLast), profilerN);
220 // profilerLast = java.lang.System.currentTimeMillis();
221 //}
222
223 drawVirtualNodes(data.searchWays(bbox));
224
225 //if(profiler)
226 //{
227 // System.out.format("All : %4dms\n", (profilerLast-profilerStart));
228 //}
229 }
230
231 private static final int max(int a, int b, int c, int d) {
232 return Math.max(Math.max(a, b), Math.max(c, d));
233 }
234
235 /**
236 * Draw a small rectangle.
237 * White if selected (as always) or red otherwise.
238 *
239 * @param n The node to draw.
240 */
241 public void visit(Node n) {
242 if (n.isIncomplete()) return;
243
244 if (n.isHighlighted()) {
245 drawNode(n, highlightColor, selectedNodeSize, fillSelectedNode);
246 } else {
247 Color color;
248
249 if (inactive || n.isDisabled()) {
250 color = inactiveColor;
251 } else if (ds.isSelected(n)) {
252 color = selectedColor;
253 } else if (n.isConnectionNode()) {
254 if (n.isTagged()) {
255 color = taggedConnectionColor;
256 } else {
257 color = connectionColor;
258 }
259 } else {
260 if (n.isTagged()) {
261 color = taggedColor;
262 } else {
263 color = nodeColor;
264 }
265 }
266
267 final int size = max((ds.isSelected(n) ? selectedNodeSize : 0),
268 (n.isTagged() ? taggedNodeSize : 0),
269 (n.isConnectionNode() ? connectionNodeSize : 0),
270 unselectedNodeSize);
271
272 final boolean fill = (ds.isSelected(n) && fillSelectedNode) ||
273 (n.isTagged() && fillTaggedNode) ||
274 (n.isConnectionNode() && fillConnectionNode) ||
275 fillUnselectedNode;
276
277 drawNode(n, color, size, fill);
278 }
279 }
280
281 public static boolean isLargeSegment(Point p1, Point p2, int space)
282 {
283 int xd = p1.x-p2.x; if(xd < 0) {
284 xd = -xd;
285 }
286 int yd = p1.y-p2.y; if(yd < 0) {
287 yd = -yd;
288 }
289 return (xd+yd > space);
290 }
291
292 public void drawVirtualNodes(Collection<Way> ways) {
293
294 if (virtualNodeSize != 0) {
295 GeneralPath path = new GeneralPath();
296 for (Way osm: ways){
297 if (osm.isUsable() && !osm.isDisabledAndHidden() && !osm.isDisabled()) {
298 visitVirtual(path, osm);
299 }
300 }
301 g.setColor(nodeColor);
302 g.draw(path);
303 }
304 }
305
306 public void visitVirtual(GeneralPath path, Way w) {
307 Iterator<Node> it = w.getNodes().iterator();
308 if (it.hasNext()) {
309 Point lastP = nc.getPoint(it.next());
310 while(it.hasNext())
311 {
312 Point p = nc.getPoint(it.next());
313 if(isSegmentVisible(lastP, p) && isLargeSegment(lastP, p, virtualNodeSpace))
314 {
315 int x = (p.x+lastP.x)/2;
316 int y = (p.y+lastP.y)/2;
317 path.moveTo(x-virtualNodeSize, y);
318 path.lineTo(x+virtualNodeSize, y);
319 path.moveTo(x, y-virtualNodeSize);
320 path.lineTo(x, y+virtualNodeSize);
321 }
322 lastP = p;
323 }
324 }
325 }
326
327 /**
328 * Draw a darkblue line for all segments.
329 * @param w The way to draw.
330 */
331 public void visit(Way w) {
332 if (w.isIncomplete() || w.getNodesCount() < 2)
333 return;
334
335 /* show direction arrows, if draw.segment.relevant_directions_only is not set, the way is tagged with a direction key
336 (even if the tag is negated as in oneway=false) or the way is selected */
337
338 boolean showThisDirectionArrow = ds.isSelected(w)
339 || (showDirectionArrow && (!showRelevantDirectionsOnly || w.hasDirectionKeys()));
340 /* head only takes over control if the option is true,
341 the direction should be shown at all and not only because it's selected */
342 boolean showOnlyHeadArrowOnly = showThisDirectionArrow && !ds.isSelected(w) && showHeadArrowOnly;
343 Color wayColor;
344
345 if (inactive || w.isDisabled()) {
346 wayColor = inactiveColor;
347 } else if(w.isHighlighted()) {
348 wayColor = highlightColor;
349 } else if(ds.isSelected(w)) {
350 wayColor = selectedColor;
351 } else if (!w.isTagged()) {
352 wayColor = untaggedWayColor;
353 } else {
354 wayColor = dfltWayColor;
355 }
356
357 Iterator<Node> it = w.getNodes().iterator();
358 if (it.hasNext()) {
359 Point lastP = nc.getPoint(it.next());
360 for (int orderNumber = 1; it.hasNext(); orderNumber++) {
361 Point p = nc.getPoint(it.next());
362 drawSegment(lastP, p, wayColor,
363 showOnlyHeadArrowOnly ? !it.hasNext() : showThisDirectionArrow);
364 if (showOrderNumber) {
365 drawOrderNumber(lastP, p, orderNumber);
366 }
367 lastP = p;
368 }
369 }
370 }
371
372 private Stroke relatedWayStroke = new BasicStroke(
373 4, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_BEVEL);
374 public void visit(Relation r) {
375 if (r.isIncomplete()) return;
376
377 Color col;
378 if (inactive || r.isDisabled()) {
379 col = inactiveColor;
380 } else if (ds.isSelected(r)) {
381 col = selectedColor;
382 } else {
383 col = relationColor;
384 }
385 g.setColor(col);
386
387 for (RelationMember m : r.getMembers()) {
388 if (m.getMember().isIncomplete() || m.getMember().isDeleted()) {
389 continue;
390 }
391
392 if (m.isNode()) {
393 Point p = nc.getPoint(m.getNode());
394 if (p.x < 0 || p.y < 0
395 || p.x > nc.getWidth() || p.y > nc.getHeight()) {
396 continue;
397 }
398
399 g.drawOval(p.x-3, p.y-3, 6, 6);
400 } else if (m.isWay()) {
401 GeneralPath path = new GeneralPath();
402
403 boolean first = true;
404 for (Node n : m.getWay().getNodes()) {
405 if (n.isIncomplete() || n.isDeleted()) {
406 continue;
407 }
408 Point p = nc.getPoint(n);
409 if (first) {
410 path.moveTo(p.x, p.y);
411 first = false;
412 } else {
413 path.lineTo(p.x, p.y);
414 }
415 }
416
417 g.draw(relatedWayStroke.createStrokedShape(path));
418 }
419 }
420 }
421
422 /**
423 * Draw an number of the order of the two consecutive nodes within the
424 * parents way
425 */
426 protected void drawOrderNumber(Point p1, Point p2, int orderNumber) {
427 if (isSegmentVisible(p1, p2) && isLargeSegment(p1, p2, segmentNumberSpace)) {
428 String on = Integer.toString(orderNumber);
429 int strlen = on.length();
430 int x = (p1.x+p2.x)/2 - 4*strlen;
431 int y = (p1.y+p2.y)/2 + 4;
432
433 if(virtualNodeSize != 0 && isLargeSegment(p1, p2, virtualNodeSpace))
434 {
435 y = (p1.y+p2.y)/2 - virtualNodeSize - 3;
436 }
437
438 displaySegments(); /* draw nodes on top! */
439 Color c = g.getColor();
440 g.setColor(backgroundColor);
441 g.fillRect(x-1, y-12, 8*strlen+1, 14);
442 g.setColor(c);
443 g.drawString(on, x, y);
444 }
445 }
446
447 /**
448 * Draw the node as small rectangle with the given color.
449 *
450 * @param n The node to draw.
451 * @param color The color of the node.
452 */
453 public void drawNode(Node n, Color color, int size, boolean fill) {
454 if (size > 1) {
455 int radius = size / 2;
456 Point p = nc.getPoint(n);
457 if ((p.x < 0) || (p.y < 0) || (p.x > nc.getWidth())
458 || (p.y > nc.getHeight()))
459 return;
460 g.setColor(color);
461 if (fill) {
462 g.fillRect(p.x - radius, p.y - radius, size, size);
463 g.drawRect(p.x - radius, p.y - radius, size, size);
464 } else {
465 g.drawRect(p.x - radius, p.y - radius, size, size);
466 }
467 }
468 }
469
470 private static final double PHI = Math.toRadians(20);
471 private static final double cosPHI = Math.cos(PHI);
472 private static final double sinPHI = Math.sin(PHI);
473
474 protected void drawSegment(GeneralPath path, Point p1, Point p2, boolean showDirection) {
475 boolean drawIt = false;
476 if (Main.isOpenjdk) {
477 /**
478 * Work around openjdk bug. It leads to drawing artefacts when zooming in a lot. (#4289, #4424)
479 * (It looks like int overflow when clipping.) We do custom clipping.
480 */
481 Rectangle bounds = g.getClipBounds();
482 bounds.grow(100, 100); // avoid arrow heads at the border
483 LineClip clip = new LineClip(p1, p2, bounds);
484 drawIt = clip.execute();
485 p1 = clip.getP1();
486 p2 = clip.getP2();
487 } else {
488 drawIt = isSegmentVisible(p1, p2);
489 }
490 if (drawIt) {
491 path.moveTo(p1.x, p1.y);
492 path.lineTo(p2.x, p2.y);
493
494 if (showDirection) {
495 final double l = 10. / p1.distance(p2);
496
497 final double sx = l * (p1.x - p2.x);
498 final double sy = l * (p1.y - p2.y);
499
500 path.lineTo (p2.x + (int) Math.round(cosPHI * sx - sinPHI * sy), p2.y + (int) Math.round(sinPHI * sx + cosPHI * sy));
501 path.moveTo (p2.x + (int) Math.round(cosPHI * sx + sinPHI * sy), p2.y + (int) Math.round(- sinPHI * sx + cosPHI * sy));
502 path.lineTo(p2.x, p2.y);
503 }
504 }
505 }
506
507 /**
508 * Draw a line with the given color.
509 */
510 protected void drawSegment(Point p1, Point p2, Color col, boolean showDirection) {
511 if (col != currentColor) {
512 displaySegments(col);
513 }
514 drawSegment(currentPath, p1, p2, showDirection);
515 }
516
517 protected boolean isSegmentVisible(Point p1, Point p2) {
518 if ((p1.x < 0) && (p2.x < 0)) return false;
519 if ((p1.y < 0) && (p2.y < 0)) return false;
520 if ((p1.x > nc.getWidth()) && (p2.x > nc.getWidth())) return false;
521 if ((p1.y > nc.getHeight()) && (p2.y > nc.getHeight())) return false;
522 return true;
523 }
524
525 protected boolean isPolygonVisible(Polygon polygon) {
526 Rectangle bounds = polygon.getBounds();
527 if (bounds.width == 0 && bounds.height == 0) return false;
528 if (bounds.x > nc.getWidth()) return false;
529 if (bounds.y > nc.getHeight()) return false;
530 if (bounds.x + bounds.width < 0) return false;
531 if (bounds.y + bounds.height < 0) return false;
532 return true;
533 }
534
535 public void setGraphics(Graphics2D g) {
536 this.g = g;
537 }
538
539 public void setNavigatableComponent(NavigatableComponent nc) {
540 this.nc = nc;
541 }
542
543 protected void displaySegments() {
544 displaySegments(null);
545 }
546 protected void displaySegments(Color newColor) {
547 if (currentPath != null) {
548 g.setColor(currentColor);
549 g.draw(currentPath);
550 currentPath = new GeneralPath();
551 currentColor = newColor;
552 }
553 }
554
555 public void setInactive(boolean inactive) {
556 this.inactive = inactive;
557 }
558}
Note: See TracBrowser for help on using the repository browser.