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

Last change on this file since 4952 was 4952, checked in by xeen, 12 years ago

fix #6891

  • Property svn:eol-style set to native
File size: 17.9 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 *
35 * @author imi
36 */
37public class WireframeMapRenderer extends AbstractMapRenderer implements Visitor {
38
39 /**
40 * Preferences
41 */
42 protected Color inactiveColor;
43 protected Color selectedColor;
44 protected Color nodeColor;
45 protected Color dfltWayColor;
46 protected Color relationColor;
47 protected Color untaggedWayColor;
48 protected Color incompleteColor;
49 protected Color backgroundColor;
50 protected Color highlightColor;
51 protected Color taggedColor;
52 protected Color connectionColor;
53 protected Color taggedConnectionColor;
54 protected boolean showDirectionArrow;
55 protected boolean showOnewayArrow;
56 protected boolean showHeadArrowOnly;
57 protected boolean showOrderNumber;
58 protected boolean fillSelectedNode;
59 protected boolean fillUnselectedNode;
60 protected boolean fillTaggedNode;
61 protected boolean fillConnectionNode;
62 protected int selectedNodeSize;
63 protected int unselectedNodeSize;
64 protected int connectionNodeSize;
65 protected int taggedNodeSize;
66 protected int defaultSegmentWidth;
67 protected int virtualNodeSize;
68 protected int virtualNodeSpace;
69 protected int segmentNumberSpace;
70
71 /**
72 * Draw subsequent segments of same color as one Path
73 */
74 protected Color currentColor = null;
75 protected GeneralPath currentPath = new GeneralPath();
76
77 /**
78 * {@inheritDoc}
79 */
80 public WireframeMapRenderer(Graphics2D g, NavigatableComponent nc, boolean isInactiveMode) {
81 super(g, nc, isInactiveMode);
82 }
83
84 public void getColors()
85 {
86 inactiveColor = PaintColors.INACTIVE.get();
87 selectedColor = PaintColors.SELECTED.get();
88 nodeColor = PaintColors.NODE.get();
89 dfltWayColor = PaintColors.DEFAULT_WAY.get();
90 relationColor = PaintColors.RELATION.get();
91 untaggedWayColor = PaintColors.UNTAGGED_WAY.get();
92 incompleteColor = PaintColors.INCOMPLETE_WAY.get();
93 backgroundColor = PaintColors.BACKGROUND.get();
94 highlightColor = PaintColors.HIGHLIGHT_WIREFRAME.get();
95 taggedColor = PaintColors.TAGGED.get();
96 connectionColor = PaintColors.CONNECTION.get();
97
98 if (taggedColor != nodeColor) {
99 taggedConnectionColor = taggedColor;
100 } else {
101 taggedConnectionColor = connectionColor;
102 }
103 }
104
105 protected void getSettings(boolean virtual) {
106 MapPaintSettings settings = MapPaintSettings.INSTANCE;
107 showDirectionArrow = settings.isShowDirectionArrow();
108 showOnewayArrow = settings.isShowOnewayArrow();
109 showHeadArrowOnly = settings.isShowHeadArrowOnly();
110 showOrderNumber = settings.isShowOrderNumber();
111 selectedNodeSize = settings.getSelectedNodeSize();
112 unselectedNodeSize = settings.getUnselectedNodeSize();
113 connectionNodeSize = settings.getConnectionNodeSize();
114 taggedNodeSize = settings.getTaggedNodeSize();
115 defaultSegmentWidth = settings.getDefaultSegmentWidth();
116 fillSelectedNode = settings.isFillSelectedNode();
117 fillUnselectedNode = settings.isFillUnselectedNode();
118 fillConnectionNode = settings.isFillConnectionNode();
119 fillTaggedNode = settings.isFillTaggedNode();
120 virtualNodeSize = virtual ? Main.pref.getInteger("mappaint.node.virtual-size", 8) / 2 : 0;
121 virtualNodeSpace = Main.pref.getInteger("mappaint.node.virtual-space", 70);
122 segmentNumberSpace = Main.pref.getInteger("mappaint.segmentnumber.space", 40);
123 getColors();
124
125 g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
126 Main.pref.getBoolean("mappaint.wireframe.use-antialiasing", false) ?
127 RenderingHints.VALUE_ANTIALIAS_ON : RenderingHints.VALUE_ANTIALIAS_OFF);
128 }
129
130 DataSet ds;
131 public void render(DataSet data, boolean virtual, Bounds bounds) {
132 BBox bbox = new BBox(bounds);
133 this.ds = data;
134 getSettings(virtual);
135
136 /* draw tagged ways first, then untagged ways. takes
137 time to iterate through list twice, OTOH does not
138 require changing the colour while painting... */
139 for (final OsmPrimitive osm: data.searchRelations(bbox)) {
140 if (!osm.isDeleted() && !ds.isSelected(osm) && !osm.isDisabledAndHidden()) {
141 osm.visit(this);
142 }
143 }
144
145 for (final OsmPrimitive osm:data.searchWays(bbox)){
146 if (!osm.isDeleted() && !ds.isSelected(osm) && !osm.isDisabledAndHidden() && osm.isTagged()) {
147 osm.visit(this);
148 }
149 }
150 displaySegments();
151
152 for (final OsmPrimitive osm:data.searchWays(bbox)){
153 if (!osm.isDeleted() && !ds.isSelected(osm) && !osm.isDisabledAndHidden() && !osm.isTagged()) {
154 osm.visit(this);
155 }
156 }
157 displaySegments();
158 for (final OsmPrimitive osm : data.getSelected()) {
159 if (!osm.isDeleted()) {
160 osm.visit(this);
161 }
162 }
163 displaySegments();
164
165 for (final OsmPrimitive osm: data.searchNodes(bbox)) {
166 if (!osm.isDeleted() && !ds.isSelected(osm) && !osm.isDisabledAndHidden())
167 {
168 osm.visit(this);
169 }
170 }
171 drawVirtualNodes(data.searchWays(bbox), data.getHighlightedVirtualNodes());
172
173 // draw highlighted way segments over the already drawn ways. Otherwise each
174 // way would have to be checked if it contains a way segment to highlight when
175 // in most of the cases there won't be more than one segment. Since the wireframe
176 // renderer does not feature any transparency there should be no visual difference.
177 for(final WaySegment wseg : data.getHighlightedWaySegments()) {
178 drawSegment(nc.getPoint(wseg.getFirstNode()), nc.getPoint(wseg.getSecondNode()), highlightColor, false);
179 }
180 displaySegments();
181 }
182
183 private static final int max(int a, int b, int c, int d) {
184 return Math.max(Math.max(a, b), Math.max(c, d));
185 }
186
187 /**
188 * Draw a small rectangle.
189 * White if selected (as always) or red otherwise.
190 *
191 * @param n The node to draw.
192 */
193 public void visit(Node n) {
194 if (n.isIncomplete()) return;
195
196 if (n.isHighlighted()) {
197 drawNode(n, highlightColor, selectedNodeSize, fillSelectedNode);
198 } else {
199 Color color;
200
201 if (isInactiveMode || n.isDisabled()) {
202 color = inactiveColor;
203 } else if (ds.isSelected(n)) {
204 color = selectedColor;
205 } else if (n.isConnectionNode()) {
206 if (n.isTagged()) {
207 color = taggedConnectionColor;
208 } else {
209 color = connectionColor;
210 }
211 } else {
212 if (n.isTagged()) {
213 color = taggedColor;
214 } else {
215 color = nodeColor;
216 }
217 }
218
219 final int size = max((ds.isSelected(n) ? selectedNodeSize : 0),
220 (n.isTagged() ? taggedNodeSize : 0),
221 (n.isConnectionNode() ? connectionNodeSize : 0),
222 unselectedNodeSize);
223
224 final boolean fill = (ds.isSelected(n) && fillSelectedNode) ||
225 (n.isTagged() && fillTaggedNode) ||
226 (n.isConnectionNode() && fillConnectionNode) ||
227 fillUnselectedNode;
228
229 drawNode(n, color, size, fill);
230 }
231 }
232
233 public static boolean isLargeSegment(Point2D p1, Point2D p2, int space)
234 {
235 double xd = Math.abs(p1.getX()-p2.getX());
236 double yd = Math.abs(p1.getY()-p2.getY());
237 return (xd+yd > space);
238 }
239
240 public void drawVirtualNodes(Collection<Way> ways, Collection<WaySegment> highlightVirtualNodes) {
241 if (virtualNodeSize == 0)
242 return;
243 // print normal virtual nodes
244 GeneralPath path = new GeneralPath();
245 for (Way osm : ways) {
246 if (osm.isUsable() && !osm.isDisabledAndHidden() && !osm.isDisabled()) {
247 visitVirtual(path, osm);
248 }
249 }
250 g.setColor(nodeColor);
251 g.draw(path);
252 // print highlighted virtual nodes. Since only the color changes, simply
253 // drawing them over the existing ones works fine (at least in their current
254 // simple style)
255 path = new GeneralPath();
256 for (WaySegment wseg: highlightVirtualNodes){
257 if (wseg.way.isUsable() && !wseg.way.isDisabled()) {
258 visitVirtual(path, wseg.toWay());
259 }
260 }
261 g.setColor(highlightColor);
262 g.draw(path);
263 }
264
265 public void visitVirtual(GeneralPath path, Way w) {
266 Iterator<Node> it = w.getNodes().iterator();
267 if (it.hasNext()) {
268 Point lastP = nc.getPoint(it.next());
269 while(it.hasNext())
270 {
271 Point p = nc.getPoint(it.next());
272 if(isSegmentVisible(lastP, p) && isLargeSegment(lastP, p, virtualNodeSpace))
273 {
274 int x = (p.x+lastP.x)/2;
275 int y = (p.y+lastP.y)/2;
276 path.moveTo(x-virtualNodeSize, y);
277 path.lineTo(x+virtualNodeSize, y);
278 path.moveTo(x, y-virtualNodeSize);
279 path.lineTo(x, y+virtualNodeSize);
280 }
281 lastP = p;
282 }
283 }
284 }
285
286 /**
287 * Draw a darkblue line for all segments.
288 * @param w The way to draw.
289 */
290 public void visit(Way w) {
291 if (w.isIncomplete() || w.getNodesCount() < 2)
292 return;
293
294 /* show direction arrows, if draw.segment.relevant_directions_only is not set, the way is tagged with a direction key
295 (even if the tag is negated as in oneway=false) or the way is selected */
296
297 boolean showThisDirectionArrow = ds.isSelected(w) || showDirectionArrow;
298 /* head only takes over control if the option is true,
299 the direction should be shown at all and not only because it's selected */
300 boolean showOnlyHeadArrowOnly = showThisDirectionArrow && !ds.isSelected(w) && showHeadArrowOnly;
301 Color wayColor;
302
303 if (isInactiveMode || w.isDisabled()) {
304 wayColor = inactiveColor;
305 } else if(w.isHighlighted()) {
306 wayColor = highlightColor;
307 } else if(ds.isSelected(w)) {
308 wayColor = selectedColor;
309 } else if (!w.isTagged()) {
310 wayColor = untaggedWayColor;
311 } else {
312 wayColor = dfltWayColor;
313 }
314
315 Iterator<Node> it = w.getNodes().iterator();
316 if (it.hasNext()) {
317 Point lastP = nc.getPoint(it.next());
318 for (int orderNumber = 1; it.hasNext(); orderNumber++) {
319 Point p = nc.getPoint(it.next());
320 drawSegment(lastP, p, wayColor,
321 showOnlyHeadArrowOnly ? !it.hasNext() : showThisDirectionArrow);
322 if (showOrderNumber && !isInactiveMode) {
323 drawOrderNumber(lastP, p, orderNumber);
324 }
325 lastP = p;
326 }
327 }
328 }
329
330 private Stroke relatedWayStroke = new BasicStroke(
331 4, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_BEVEL);
332 public void visit(Relation r) {
333 if (r.isIncomplete()) return;
334
335 Color col;
336 if (isInactiveMode || r.isDisabled()) {
337 col = inactiveColor;
338 } else if (ds.isSelected(r)) {
339 col = selectedColor;
340 } else {
341 col = relationColor;
342 }
343 g.setColor(col);
344
345 for (RelationMember m : r.getMembers()) {
346 if (m.getMember().isIncomplete() || m.getMember().isDeleted()) {
347 continue;
348 }
349
350 if (m.isNode()) {
351 Point p = nc.getPoint(m.getNode());
352 if (p.x < 0 || p.y < 0
353 || p.x > nc.getWidth() || p.y > nc.getHeight()) {
354 continue;
355 }
356
357 g.drawOval(p.x-3, p.y-3, 6, 6);
358 } else if (m.isWay()) {
359 GeneralPath path = new GeneralPath();
360
361 boolean first = true;
362 for (Node n : m.getWay().getNodes()) {
363 if (n.isIncomplete() || n.isDeleted()) {
364 continue;
365 }
366 Point p = nc.getPoint(n);
367 if (first) {
368 path.moveTo(p.x, p.y);
369 first = false;
370 } else {
371 path.lineTo(p.x, p.y);
372 }
373 }
374
375 g.draw(relatedWayStroke.createStrokedShape(path));
376 }
377 }
378 }
379
380 @Override
381 public void visit(Changeset cs) {/* ignore */}
382
383 /**
384 * Draw an number of the order of the two consecutive nodes within the
385 * parents way
386 */
387 protected void drawOrderNumber(Point p1, Point p2, int orderNumber) {
388 if (isSegmentVisible(p1, p2) && isLargeSegment(p1, p2, segmentNumberSpace)) {
389 String on = Integer.toString(orderNumber);
390 int strlen = on.length();
391 int x = (p1.x+p2.x)/2 - 4*strlen;
392 int y = (p1.y+p2.y)/2 + 4;
393
394 if(virtualNodeSize != 0 && isLargeSegment(p1, p2, virtualNodeSpace))
395 {
396 y = (p1.y+p2.y)/2 - virtualNodeSize - 3;
397 }
398
399 displaySegments(); /* draw nodes on top! */
400 Color c = g.getColor();
401 g.setColor(backgroundColor);
402 g.fillRect(x-1, y-12, 8*strlen+1, 14);
403 g.setColor(c);
404 g.drawString(on, x, y);
405 }
406 }
407
408 /**
409 * Draw the node as small rectangle with the given color.
410 *
411 * @param n The node to draw.
412 * @param color The color of the node.
413 */
414 public void drawNode(Node n, Color color, int size, boolean fill) {
415 if (size > 1) {
416 int radius = size / 2;
417 Point p = nc.getPoint(n);
418 if ((p.x < 0) || (p.y < 0) || (p.x > nc.getWidth())
419 || (p.y > nc.getHeight()))
420 return;
421 g.setColor(color);
422 if (fill) {
423 g.fillRect(p.x - radius, p.y - radius, size, size);
424 g.drawRect(p.x - radius, p.y - radius, size, size);
425 } else {
426 g.drawRect(p.x - radius, p.y - radius, size, size);
427 }
428 }
429 }
430
431 private static final double PHI = Math.toRadians(20);
432 private static final double cosPHI = Math.cos(PHI);
433 private static final double sinPHI = Math.sin(PHI);
434
435 protected void drawSegment(GeneralPath path, Point p1, Point p2, boolean showDirection) {
436 Rectangle bounds = g.getClipBounds();
437 bounds.grow(100, 100); // avoid arrow heads at the border
438 LineClip clip = new LineClip(p1, p2, bounds);
439 if (clip.execute()) {
440 p1 = clip.getP1();
441 p2 = clip.getP2();
442 path.moveTo(p1.x, p1.y);
443 path.lineTo(p2.x, p2.y);
444
445 if (showDirection) {
446 final double l = 10. / p1.distance(p2);
447
448 final double sx = l * (p1.x - p2.x);
449 final double sy = l * (p1.y - p2.y);
450
451 path.lineTo (p2.x + (int) Math.round(cosPHI * sx - sinPHI * sy), p2.y + (int) Math.round(sinPHI * sx + cosPHI * sy));
452 path.moveTo (p2.x + (int) Math.round(cosPHI * sx + sinPHI * sy), p2.y + (int) Math.round(- sinPHI * sx + cosPHI * sy));
453 path.lineTo(p2.x, p2.y);
454 }
455 }
456 }
457
458 /**
459 * Draw a line with the given color.
460 */
461 protected void drawSegment(Point p1, Point p2, Color col, boolean showDirection) {
462 if (col != currentColor) {
463 displaySegments(col);
464 }
465 drawSegment(currentPath, p1, p2, showDirection);
466 }
467
468 protected boolean isSegmentVisible(Point p1, Point p2) {
469 if ((p1.x < 0) && (p2.x < 0)) return false;
470 if ((p1.y < 0) && (p2.y < 0)) return false;
471 if ((p1.x > nc.getWidth()) && (p2.x > nc.getWidth())) return false;
472 if ((p1.y > nc.getHeight()) && (p2.y > nc.getHeight())) return false;
473 return true;
474 }
475
476 protected boolean isPolygonVisible(Polygon polygon) {
477 Rectangle bounds = polygon.getBounds();
478 if (bounds.width == 0 && bounds.height == 0) return false;
479 if (bounds.x > nc.getWidth()) return false;
480 if (bounds.y > nc.getHeight()) return false;
481 if (bounds.x + bounds.width < 0) return false;
482 if (bounds.y + bounds.height < 0) return false;
483 return true;
484 }
485
486 protected void displaySegments() {
487 displaySegments(null);
488 }
489 protected void displaySegments(Color newColor) {
490 if (currentPath != null) {
491 g.setColor(currentColor);
492 g.draw(currentPath);
493 currentPath = new GeneralPath();
494 currentColor = newColor;
495 }
496 }
497}
Note: See TracBrowser for help on using the repository browser.