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

Last change on this file since 4327 was 4327, checked in by xeen, 13 years ago

updates visual appearance of highlights and adds them to select and delete action

in more detail:

  • add target highlighting to select action
  • add target cursor to select action
  • add target highlighting to delete action
  • unify ctrl/alt/shift modifier detection in MapMode actions
  • highlights are now a halo around the way/node instead of a color change
  • default highlight color is now the same as the select color (red)
  • ability to highlight WaySegments and VirtualNodes
  • various style/whitespace nits
  • fixes #2411

This patch touches a lot of areas, so please report any regressions in the map mode
tools. Also please add a comment to #2411 if you find to highlighting in select
mode distracting, so it can be fine tuned (or turned off by default).

  • Property svn:eol-style set to native
File size: 17.8 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.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) {
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.