source: josm/trunk/src/org/openstreetmap/josm/data/osm/visitor/paint/WireframeMapRenderer.java@ 5421

Last change on this file since 5421 was 5421, checked in by stoecker, 12 years ago

fix javadoc, remove unused code

  • Property svn:eol-style set to native
File size: 23.0 KB
Line 
1/* License: GPL. Copyright 2007 by Immanuel Scholz and others */
2package org.openstreetmap.josm.data.osm.visitor.paint;
3
4import java.awt.BasicStroke;
5import java.awt.Color;
6import java.awt.Graphics2D;
7import java.awt.Point;
8import java.awt.Polygon;
9import java.awt.Rectangle;
10import java.awt.RenderingHints;
11import java.awt.Stroke;
12import java.awt.geom.GeneralPath;
13import java.awt.geom.Point2D;
14import java.util.Collection;
15import java.util.Iterator;
16
17import org.openstreetmap.josm.Main;
18import org.openstreetmap.josm.data.Bounds;
19import org.openstreetmap.josm.data.osm.BBox;
20import org.openstreetmap.josm.data.osm.Changeset;
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.WaySegment;
28import org.openstreetmap.josm.data.osm.visitor.Visitor;
29import org.openstreetmap.josm.gui.NavigatableComponent;
30
31/**
32 * A map renderer that paints a simple scheme of every primitive it visits to a
33 * previous set graphic environment.
34 */
35public class WireframeMapRenderer extends AbstractMapRenderer implements Visitor {
36
37 /** Color Preference for inactive objects */
38 protected Color inactiveColor;
39 /** Color Preference for selected objects */
40 protected Color selectedColor;
41 /** Color Preference for nodes */
42 protected Color nodeColor;
43 /** Color Preference for ways not matching any other group */
44 protected Color dfltWayColor;
45 /** Color Preference for relations */
46 protected Color relationColor;
47 /** Color Preference for untagged ways */
48 protected Color untaggedWayColor;
49 /** Color Preference for background */
50 protected Color backgroundColor;
51 /** Color Preference for hightlighted objects */
52 protected Color highlightColor;
53 /** Color Preference for tagged nodes */
54 protected Color taggedColor;
55 /** Color Preference for multiply connected nodes */
56 protected Color connectionColor;
57 /** Color Preference for tagged and multiply connected nodes */
58 protected Color taggedConnectionColor;
59 /** Preference: should directional arrows be displayed */
60 protected boolean showDirectionArrow;
61 /** Preference: should arrows for oneways be displayed */
62 protected boolean showOnewayArrow;
63 /** Preference: should only the last arrow of a way be displayed */
64 protected boolean showHeadArrowOnly;
65 /** Preference: should the segement numbers of ways be displayed */
66 protected boolean showOrderNumber;
67 /** Preference: should selected nodes be filled */
68 protected boolean fillSelectedNode;
69 /** Preference: should unselected nodes be filled */
70 protected boolean fillUnselectedNode;
71 /** Preference: should tagged nodes be filled */
72 protected boolean fillTaggedNode;
73 /** Preference: should multiply connected nodes be filled */
74 protected boolean fillConnectionNode;
75 /** Preference: size of selected nodes */
76 protected int selectedNodeSize;
77 /** Preference: size of unselected nodes */
78 protected int unselectedNodeSize;
79 /** Preference: size of multiply connected nodes */
80 protected int connectionNodeSize;
81 /** Preference: size of tagged nodes */
82 protected int taggedNodeSize;
83 /** Preference: size of virtual nodes (0 displayes display) */
84 protected int virtualNodeSize;
85 /** Preference: minimum space (displayed way length) to display virtual nodes */
86 protected int virtualNodeSpace;
87 /** Preference: minimum space (displayed way length) to display segment numbers */
88 protected int segmentNumberSpace;
89
90 /** Color cache to draw subsequent segments of same color as one <code>Path</code>. */
91 protected Color currentColor = null;
92 /** Path store to draw subsequent segments of same color as one <code>Path</code>. */
93 protected GeneralPath currentPath = new GeneralPath();
94 /**
95 * <code>DataSet</code> passed to the @{link render} function to overcome the argument
96 * limitations of @{link Visitor} interface. Only valid until end of rendering call.
97 */
98 private DataSet ds;
99
100 /** Helper variable for {@link #drawSgement} */
101 private static final double PHI = Math.toRadians(20);
102 /** Helper variable for {@link #drawSgement} */
103 private static final double cosPHI = Math.cos(PHI);
104 /** Helper variable for {@link #drawSgement} */
105 private static final double sinPHI = Math.sin(PHI);
106
107 /** Helper variable for {@link #visit(Relation) */
108 private Stroke relatedWayStroke = new BasicStroke(
109 4, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_BEVEL);
110
111 /**
112 * Creates an wireframe render
113 *
114 * @param g the graphics context. Must not be null.
115 * @param nc the map viewport. Must not be null.
116 * @param isInactiveMode if true, the paint visitor shall render OSM objects such that they
117 * look inactive. Example: rendering of data in an inactive layer using light gray as color only.
118 * @throws IllegalArgumentException thrown if {@code g} is null
119 * @throws IllegalArgumentException thrown if {@code nc} is null
120 */
121 public WireframeMapRenderer(Graphics2D g, NavigatableComponent nc, boolean isInactiveMode) {
122 super(g, nc, isInactiveMode);
123 }
124
125 /**
126 * Reads the color definitions from preferences. This function is <code>public</code>, so that
127 * color names in preferences can be displayed even without calling the wireframe display before.
128 */
129 public void getColors()
130 {
131 inactiveColor = PaintColors.INACTIVE.get();
132 selectedColor = PaintColors.SELECTED.get();
133 nodeColor = PaintColors.NODE.get();
134 dfltWayColor = PaintColors.DEFAULT_WAY.get();
135 relationColor = PaintColors.RELATION.get();
136 untaggedWayColor = PaintColors.UNTAGGED_WAY.get();
137 backgroundColor = PaintColors.BACKGROUND.get();
138 highlightColor = PaintColors.HIGHLIGHT_WIREFRAME.get();
139 taggedColor = PaintColors.TAGGED.get();
140 connectionColor = PaintColors.CONNECTION.get();
141
142 if (taggedColor != nodeColor) {
143 taggedConnectionColor = taggedColor;
144 } else {
145 taggedConnectionColor = connectionColor;
146 }
147 }
148
149 /**
150 * Reads all the settings from preferences. Calls the @{link #getColors}
151 * function.
152 *
153 * @param virtual <code>true</code> if virtual nodes are used
154 */
155 protected void getSettings(boolean virtual) {
156 MapPaintSettings settings = MapPaintSettings.INSTANCE;
157 showDirectionArrow = settings.isShowDirectionArrow();
158 showOnewayArrow = settings.isShowOnewayArrow();
159 showHeadArrowOnly = settings.isShowHeadArrowOnly();
160 showOrderNumber = settings.isShowOrderNumber();
161 selectedNodeSize = settings.getSelectedNodeSize();
162 unselectedNodeSize = settings.getUnselectedNodeSize();
163 connectionNodeSize = settings.getConnectionNodeSize();
164 taggedNodeSize = settings.getTaggedNodeSize();
165 fillSelectedNode = settings.isFillSelectedNode();
166 fillUnselectedNode = settings.isFillUnselectedNode();
167 fillConnectionNode = settings.isFillConnectionNode();
168 fillTaggedNode = settings.isFillTaggedNode();
169 virtualNodeSize = virtual ? Main.pref.getInteger("mappaint.node.virtual-size", 8) / 2 : 0;
170 virtualNodeSpace = Main.pref.getInteger("mappaint.node.virtual-space", 70);
171 segmentNumberSpace = Main.pref.getInteger("mappaint.segmentnumber.space", 40);
172 getColors();
173
174 g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
175 Main.pref.getBoolean("mappaint.wireframe.use-antialiasing", false) ?
176 RenderingHints.VALUE_ANTIALIAS_ON : RenderingHints.VALUE_ANTIALIAS_OFF);
177 }
178
179 /**
180 * Renders the dataset for display.
181 *
182 * @param data <code>DataSet</code> to display
183 * @param virtual <code>true</code> if virtual nodes are used
184 * @param bounds display boundaries
185 */
186 public void render(DataSet data, boolean virtual, Bounds bounds) {
187 BBox bbox = new BBox(bounds);
188 this.ds = data;
189 getSettings(virtual);
190
191 /* draw tagged ways first, then untagged ways. takes
192 time to iterate through list twice, OTOH does not
193 require changing the colour while painting... */
194 for (final OsmPrimitive osm: data.searchRelations(bbox)) {
195 if (osm.isDrawable() && !ds.isSelected(osm) && !osm.isDisabledAndHidden()) {
196 osm.visit(this);
197 }
198 }
199
200 for (final OsmPrimitive osm:data.searchWays(bbox)){
201 if (osm.isDrawable() && !ds.isSelected(osm) && !osm.isDisabledAndHidden() && osm.isTagged()) {
202 osm.visit(this);
203 }
204 }
205 displaySegments();
206
207 for (final OsmPrimitive osm:data.searchWays(bbox)){
208 if (osm.isDrawable() && !ds.isSelected(osm) && !osm.isDisabledAndHidden() && !osm.isTagged()) {
209 osm.visit(this);
210 }
211 }
212 displaySegments();
213 for (final OsmPrimitive osm : data.getSelected()) {
214 if (osm.isDrawable()) {
215 osm.visit(this);
216 }
217 }
218 displaySegments();
219
220 for (final OsmPrimitive osm: data.searchNodes(bbox)) {
221 if (osm.isDrawable() && !ds.isSelected(osm) && !osm.isDisabledAndHidden())
222 {
223 osm.visit(this);
224 }
225 }
226 drawVirtualNodes(data.searchWays(bbox), data.getHighlightedVirtualNodes());
227
228 // draw highlighted way segments over the already drawn ways. Otherwise each
229 // way would have to be checked if it contains a way segment to highlight when
230 // in most of the cases there won't be more than one segment. Since the wireframe
231 // renderer does not feature any transparency there should be no visual difference.
232 for(final WaySegment wseg : data.getHighlightedWaySegments()) {
233 drawSegment(nc.getPoint(wseg.getFirstNode()), nc.getPoint(wseg.getSecondNode()), highlightColor, false);
234 }
235 displaySegments();
236 }
237
238 /**
239 * Helper function to calculate maximum of 4 values.
240 *
241 * @param a First value
242 * @param b Second value
243 * @param c Third value
244 * @param d Fourth value
245 */
246 private static final int max(int a, int b, int c, int d) {
247 return Math.max(Math.max(a, b), Math.max(c, d));
248 }
249
250 /**
251 * Draw a small rectangle.
252 * White if selected (as always) or red otherwise.
253 *
254 * @param n The node to draw.
255 */
256 @Override
257 public void visit(Node n) {
258 if (n.isIncomplete()) return;
259
260 if (n.isHighlighted()) {
261 drawNode(n, highlightColor, selectedNodeSize, fillSelectedNode);
262 } else {
263 Color color;
264
265 if (isInactiveMode || n.isDisabled()) {
266 color = inactiveColor;
267 } else if (ds.isSelected(n)) {
268 color = selectedColor;
269 } else if (n.isConnectionNode()) {
270 if (n.isTagged()) {
271 color = taggedConnectionColor;
272 } else {
273 color = connectionColor;
274 }
275 } else {
276 if (n.isTagged()) {
277 color = taggedColor;
278 } else {
279 color = nodeColor;
280 }
281 }
282
283 final int size = max((ds.isSelected(n) ? selectedNodeSize : 0),
284 (n.isTagged() ? taggedNodeSize : 0),
285 (n.isConnectionNode() ? connectionNodeSize : 0),
286 unselectedNodeSize);
287
288 final boolean fill = (ds.isSelected(n) && fillSelectedNode) ||
289 (n.isTagged() && fillTaggedNode) ||
290 (n.isConnectionNode() && fillConnectionNode) ||
291 fillUnselectedNode;
292
293 drawNode(n, color, size, fill);
294 }
295 }
296
297 /**
298 * Checks if a way segemnt is large enough for additional information display.
299 *
300 * @param p1 First point of the way segment.
301 * @param p2 Second point of the way segment.
302 * @param space The free space to check against.
303 * @return <code>true</code> if segment is larger than required space
304 */
305 public static boolean isLargeSegment(Point2D p1, Point2D p2, int space)
306 {
307 double xd = Math.abs(p1.getX()-p2.getX());
308 double yd = Math.abs(p1.getY()-p2.getY());
309 return (xd+yd > space);
310 }
311
312 /**
313 * Draws virtual nodes.
314 *
315 * @param ways The ways to draw nodes for.
316 * @param highlightVirtualNodes Way segements, where nodesshould be highlighted.
317 */
318 public void drawVirtualNodes(Collection<Way> ways, Collection<WaySegment> highlightVirtualNodes) {
319 if (virtualNodeSize == 0)
320 return;
321 // print normal virtual nodes
322 GeneralPath path = new GeneralPath();
323 for (Way osm : ways) {
324 if (osm.isUsable() && !osm.isDisabledAndHidden() && !osm.isDisabled()) {
325 visitVirtual(path, osm);
326 }
327 }
328 g.setColor(nodeColor);
329 g.draw(path);
330 // print highlighted virtual nodes. Since only the color changes, simply
331 // drawing them over the existing ones works fine (at least in their current
332 // simple style)
333 path = new GeneralPath();
334 for (WaySegment wseg: highlightVirtualNodes){
335 if (wseg.way.isUsable() && !wseg.way.isDisabled()) {
336 visitVirtual(path, wseg.toWay());
337 }
338 }
339 g.setColor(highlightColor);
340 g.draw(path);
341 }
342
343 /**
344 * Creates path for drawing virtual nodes for one way.
345 *
346 * @param path The path to append drawing to.
347 * @param w The ways to draw node for.
348 */
349 public void visitVirtual(GeneralPath path, Way w) {
350 Iterator<Node> it = w.getNodes().iterator();
351 if (it.hasNext()) {
352 Point lastP = nc.getPoint(it.next());
353 while(it.hasNext())
354 {
355 Point p = nc.getPoint(it.next());
356 if(isSegmentVisible(lastP, p) && isLargeSegment(lastP, p, virtualNodeSpace))
357 {
358 int x = (p.x+lastP.x)/2;
359 int y = (p.y+lastP.y)/2;
360 path.moveTo(x-virtualNodeSize, y);
361 path.lineTo(x+virtualNodeSize, y);
362 path.moveTo(x, y-virtualNodeSize);
363 path.lineTo(x, y+virtualNodeSize);
364 }
365 lastP = p;
366 }
367 }
368 }
369
370 /**
371 * Draw a line for all way segments.
372 * @param w The way to draw.
373 */
374 @Override
375 public void visit(Way w) {
376 if (w.isIncomplete() || w.getNodesCount() < 2)
377 return;
378
379 /* show direction arrows, if draw.segment.relevant_directions_only is not set, the way is tagged with a direction key
380 (even if the tag is negated as in oneway=false) or the way is selected */
381
382 boolean showThisDirectionArrow = ds.isSelected(w) || showDirectionArrow;
383 /* head only takes over control if the option is true,
384 the direction should be shown at all and not only because it's selected */
385 boolean showOnlyHeadArrowOnly = showThisDirectionArrow && !ds.isSelected(w) && showHeadArrowOnly;
386 Color wayColor;
387
388 if (isInactiveMode || w.isDisabled()) {
389 wayColor = inactiveColor;
390 } else if(w.isHighlighted()) {
391 wayColor = highlightColor;
392 } else if(ds.isSelected(w)) {
393 wayColor = selectedColor;
394 } else if (!w.isTagged()) {
395 wayColor = untaggedWayColor;
396 } else {
397 wayColor = dfltWayColor;
398 }
399
400 Iterator<Node> it = w.getNodes().iterator();
401 if (it.hasNext()) {
402 Point lastP = nc.getPoint(it.next());
403 for (int orderNumber = 1; it.hasNext(); orderNumber++) {
404 Point p = nc.getPoint(it.next());
405 drawSegment(lastP, p, wayColor,
406 showOnlyHeadArrowOnly ? !it.hasNext() : showThisDirectionArrow);
407 if (showOrderNumber && !isInactiveMode) {
408 drawOrderNumber(lastP, p, orderNumber);
409 }
410 lastP = p;
411 }
412 }
413 }
414
415 /**
416 * Draw objects used in relations.
417 * @param r The relation to draw.
418 */
419 @Override
420 public void visit(Relation r) {
421 if (r.isIncomplete()) return;
422
423 Color col;
424 if (isInactiveMode || r.isDisabled()) {
425 col = inactiveColor;
426 } else if (ds.isSelected(r)) {
427 col = selectedColor;
428 } else {
429 col = relationColor;
430 }
431 g.setColor(col);
432
433 for (RelationMember m : r.getMembers()) {
434 if (m.getMember().isIncomplete() || !m.getMember().isDrawable()) {
435 continue;
436 }
437
438 if (m.isNode()) {
439 Point p = nc.getPoint(m.getNode());
440 if (p.x < 0 || p.y < 0
441 || p.x > nc.getWidth() || p.y > nc.getHeight()) {
442 continue;
443 }
444
445 g.drawOval(p.x-3, p.y-3, 6, 6);
446 } else if (m.isWay()) {
447 GeneralPath path = new GeneralPath();
448
449 boolean first = true;
450 for (Node n : m.getWay().getNodes()) {
451 if (!n.isDrawable()) {
452 continue;
453 }
454 Point p = nc.getPoint(n);
455 if (first) {
456 path.moveTo(p.x, p.y);
457 first = false;
458 } else {
459 path.lineTo(p.x, p.y);
460 }
461 }
462
463 g.draw(relatedWayStroke.createStrokedShape(path));
464 }
465 }
466 }
467
468 /**
469 * Visitor for changesets not used in this class
470 * @param cs The changeset for inspection.
471 */
472 @Override
473 public void visit(Changeset cs) {/* ignore */}
474
475 /**
476 * Draw an number of the order of the two consecutive nodes within the
477 * parents way
478 *
479 * @param p1 First point of the way segment.
480 * @param p2 Second point of the way segment.
481 * @param orderNumber The number of the segment in the way.
482 */
483 protected void drawOrderNumber(Point p1, Point p2, int orderNumber) {
484 if (isSegmentVisible(p1, p2) && isLargeSegment(p1, p2, segmentNumberSpace)) {
485 String on = Integer.toString(orderNumber);
486 int strlen = on.length();
487 int x = (p1.x+p2.x)/2 - 4*strlen;
488 int y = (p1.y+p2.y)/2 + 4;
489
490 if(virtualNodeSize != 0 && isLargeSegment(p1, p2, virtualNodeSpace))
491 {
492 y = (p1.y+p2.y)/2 - virtualNodeSize - 3;
493 }
494
495 displaySegments(); /* draw nodes on top! */
496 Color c = g.getColor();
497 g.setColor(backgroundColor);
498 g.fillRect(x-1, y-12, 8*strlen+1, 14);
499 g.setColor(c);
500 g.drawString(on, x, y);
501 }
502 }
503
504 /**
505 * Draw the node as small rectangle with the given color.
506 *
507 * @param n The node to draw.
508 * @param color The color of the node.
509 */
510 public void drawNode(Node n, Color color, int size, boolean fill) {
511 if (size > 1) {
512 int radius = size / 2;
513 Point p = nc.getPoint(n);
514 if ((p.x < 0) || (p.y < 0) || (p.x > nc.getWidth())
515 || (p.y > nc.getHeight()))
516 return;
517 g.setColor(color);
518 if (fill) {
519 g.fillRect(p.x - radius, p.y - radius, size, size);
520 g.drawRect(p.x - radius, p.y - radius, size, size);
521 } else {
522 g.drawRect(p.x - radius, p.y - radius, size, size);
523 }
524 }
525 }
526
527 /**
528 * Draw a line with the given color.
529 *
530 * @param path The path to append this segment.
531 * @param p1 First point of the way segment.
532 * @param p2 Second point of the way segment.
533 * @param showDirection <code>true</code> if segment direction should be indicated
534 */
535 protected void drawSegment(GeneralPath path, Point p1, Point p2, boolean showDirection) {
536 Rectangle bounds = g.getClipBounds();
537 bounds.grow(100, 100); // avoid arrow heads at the border
538 LineClip clip = new LineClip(p1, p2, bounds);
539 if (clip.execute()) {
540 p1 = clip.getP1();
541 p2 = clip.getP2();
542 path.moveTo(p1.x, p1.y);
543 path.lineTo(p2.x, p2.y);
544
545 if (showDirection) {
546 final double l = 10. / p1.distance(p2);
547
548 final double sx = l * (p1.x - p2.x);
549 final double sy = l * (p1.y - p2.y);
550
551 path.lineTo (p2.x + (int) Math.round(cosPHI * sx - sinPHI * sy), p2.y + (int) Math.round(sinPHI * sx + cosPHI * sy));
552 path.moveTo (p2.x + (int) Math.round(cosPHI * sx + sinPHI * sy), p2.y + (int) Math.round(- sinPHI * sx + cosPHI * sy));
553 path.lineTo(p2.x, p2.y);
554 }
555 }
556 }
557
558 /**
559 * Draw a line with the given color.
560 *
561 * @param p1 First point of the way segment.
562 * @param p2 Second point of the way segment.
563 * @param col The color to use for drawing line.
564 * @param showDirection <code>true</code> if segment direction should be indicated.
565 */
566 protected void drawSegment(Point p1, Point p2, Color col, boolean showDirection) {
567 if (col != currentColor) {
568 displaySegments(col);
569 }
570 drawSegment(currentPath, p1, p2, showDirection);
571 }
572
573 /**
574 * Checks if segment is visible in display.
575 *
576 * @param p1 First point of the way segment.
577 * @param p2 Second point of the way segment.
578 * @return <code>true</code> if segment is visible.
579 */
580 protected boolean isSegmentVisible(Point p1, Point p2) {
581 if ((p1.x < 0) && (p2.x < 0)) return false;
582 if ((p1.y < 0) && (p2.y < 0)) return false;
583 if ((p1.x > nc.getWidth()) && (p2.x > nc.getWidth())) return false;
584 if ((p1.y > nc.getHeight()) && (p2.y > nc.getHeight())) return false;
585 return true;
586 }
587
588 /**
589 * Checks if a polygon is visible in display.
590 *
591 * @param polygon The polygon to check.
592 * @return <code>true</code> if polygon is visible.
593 */
594 protected boolean isPolygonVisible(Polygon polygon) {
595 Rectangle bounds = polygon.getBounds();
596 if (bounds.width == 0 && bounds.height == 0) return false;
597 if (bounds.x > nc.getWidth()) return false;
598 if (bounds.y > nc.getHeight()) return false;
599 if (bounds.x + bounds.width < 0) return false;
600 if (bounds.y + bounds.height < 0) return false;
601 return true;
602 }
603
604 /**
605 * Finally display all segments in currect path.
606 */
607 protected void displaySegments() {
608 displaySegments(null);
609 }
610
611 /**
612 * Finally display all segments in currect path.
613 *
614 * @param newColor This color is set after the path is drawn.
615 */
616 protected void displaySegments(Color newColor) {
617 if (currentPath != null) {
618 g.setColor(currentColor);
619 g.draw(currentPath);
620 currentPath = new GeneralPath();
621 currentColor = newColor;
622 }
623 }
624}
Note: See TracBrowser for help on using the repository browser.