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

Last change on this file since 6920 was 6380, checked in by Don-vip, 11 years ago

update license/copyright information

  • Property svn:eol-style set to native
File size: 17.6 KB
Line 
1/* License: GPL. For details, see LICENSE file. */
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.util.ArrayList;
14import java.util.Arrays;
15import java.util.Iterator;
16import java.util.List;
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.Changeset;
22import org.openstreetmap.josm.data.osm.DataSet;
23import org.openstreetmap.josm.data.osm.Node;
24import org.openstreetmap.josm.data.osm.OsmPrimitive;
25import org.openstreetmap.josm.data.osm.Relation;
26import org.openstreetmap.josm.data.osm.RelationMember;
27import org.openstreetmap.josm.data.osm.Way;
28import org.openstreetmap.josm.data.osm.WaySegment;
29import org.openstreetmap.josm.data.osm.visitor.Visitor;
30import org.openstreetmap.josm.gui.NavigatableComponent;
31
32/**
33 * A map renderer that paints a simple scheme of every primitive it visits to a
34 * previous set graphic environment.
35 */
36public class WireframeMapRenderer extends AbstractMapRenderer implements Visitor {
37
38 /** Color Preference for ways not matching any other group */
39 protected Color dfltWayColor;
40 /** Color Preference for relations */
41 protected Color relationColor;
42 /** Color Preference for untagged ways */
43 protected Color untaggedWayColor;
44 /** Color Preference for tagged nodes */
45 protected Color taggedColor;
46 /** Color Preference for multiply connected nodes */
47 protected Color connectionColor;
48 /** Color Preference for tagged and multiply connected nodes */
49 protected Color taggedConnectionColor;
50 /** Preference: should directional arrows be displayed */
51 protected boolean showDirectionArrow;
52 /** Preference: should arrows for oneways be displayed */
53 protected boolean showOnewayArrow;
54 /** Preference: should only the last arrow of a way be displayed */
55 protected boolean showHeadArrowOnly;
56 /** Preference: should the segement numbers of ways be displayed */
57 protected boolean showOrderNumber;
58 /** Preference: should selected nodes be filled */
59 protected boolean fillSelectedNode;
60 /** Preference: should unselected nodes be filled */
61 protected boolean fillUnselectedNode;
62 /** Preference: should tagged nodes be filled */
63 protected boolean fillTaggedNode;
64 /** Preference: should multiply connected nodes be filled */
65 protected boolean fillConnectionNode;
66 /** Preference: size of selected nodes */
67 protected int selectedNodeSize;
68 /** Preference: size of unselected nodes */
69 protected int unselectedNodeSize;
70 /** Preference: size of multiply connected nodes */
71 protected int connectionNodeSize;
72 /** Preference: size of tagged nodes */
73 protected int taggedNodeSize;
74
75 /** Color cache to draw subsequent segments of same color as one <code>Path</code>. */
76 protected Color currentColor = null;
77 /** Path store to draw subsequent segments of same color as one <code>Path</code>. */
78 protected GeneralPath currentPath = new GeneralPath();
79 /**
80 * <code>DataSet</code> passed to the @{link render} function to overcome the argument
81 * limitations of @{link Visitor} interface. Only valid until end of rendering call.
82 */
83 private DataSet ds;
84
85 /** Helper variable for {@link #drawSegment} */
86 private static final double PHI = Math.toRadians(20);
87 /** Helper variable for {@link #drawSegment} */
88 private static final double cosPHI = Math.cos(PHI);
89 /** Helper variable for {@link #drawSegment} */
90 private static final double sinPHI = Math.sin(PHI);
91
92 /** Helper variable for {@link #visit(Relation)} */
93 private Stroke relatedWayStroke = new BasicStroke(
94 4, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_BEVEL);
95
96 /**
97 * Creates an wireframe render
98 *
99 * @param g the graphics context. Must not be null.
100 * @param nc the map viewport. Must not be null.
101 * @param isInactiveMode if true, the paint visitor shall render OSM objects such that they
102 * look inactive. Example: rendering of data in an inactive layer using light gray as color only.
103 * @throws IllegalArgumentException thrown if {@code g} is null
104 * @throws IllegalArgumentException thrown if {@code nc} is null
105 */
106 public WireframeMapRenderer(Graphics2D g, NavigatableComponent nc, boolean isInactiveMode) {
107 super(g, nc, isInactiveMode);
108 }
109
110 @Override
111 public void getColors() {
112 super.getColors();
113 dfltWayColor = PaintColors.DEFAULT_WAY.get();
114 relationColor = PaintColors.RELATION.get();
115 untaggedWayColor = PaintColors.UNTAGGED_WAY.get();
116 highlightColor = PaintColors.HIGHLIGHT_WIREFRAME.get();
117 taggedColor = PaintColors.TAGGED.get();
118 connectionColor = PaintColors.CONNECTION.get();
119
120 if (taggedColor != nodeColor) {
121 taggedConnectionColor = taggedColor;
122 } else {
123 taggedConnectionColor = connectionColor;
124 }
125 }
126
127 @Override
128 protected void getSettings(boolean virtual) {
129 super.getSettings(virtual);
130 MapPaintSettings settings = MapPaintSettings.INSTANCE;
131 showDirectionArrow = settings.isShowDirectionArrow();
132 showOnewayArrow = settings.isShowOnewayArrow();
133 showHeadArrowOnly = settings.isShowHeadArrowOnly();
134 showOrderNumber = settings.isShowOrderNumber();
135 selectedNodeSize = settings.getSelectedNodeSize();
136 unselectedNodeSize = settings.getUnselectedNodeSize();
137 connectionNodeSize = settings.getConnectionNodeSize();
138 taggedNodeSize = settings.getTaggedNodeSize();
139 fillSelectedNode = settings.isFillSelectedNode();
140 fillUnselectedNode = settings.isFillUnselectedNode();
141 fillConnectionNode = settings.isFillConnectionNode();
142 fillTaggedNode = settings.isFillTaggedNode();
143
144 g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
145 Main.pref.getBoolean("mappaint.wireframe.use-antialiasing", false) ?
146 RenderingHints.VALUE_ANTIALIAS_ON : RenderingHints.VALUE_ANTIALIAS_OFF);
147 }
148
149 /**
150 * Renders the dataset for display.
151 *
152 * @param data <code>DataSet</code> to display
153 * @param virtual <code>true</code> if virtual nodes are used
154 * @param bounds display boundaries
155 */
156 @SuppressWarnings("unchecked")
157 @Override
158 public void render(DataSet data, boolean virtual, Bounds bounds) {
159 BBox bbox = bounds.toBBox();
160 this.ds = data;
161 getSettings(virtual);
162
163 for (final Relation rel : data.searchRelations(bbox)) {
164 if (rel.isDrawable() && !ds.isSelected(rel) && !rel.isDisabledAndHidden()) {
165 rel.accept(this);
166 }
167 }
168
169 // draw tagged ways first, then untagged ways, then highlighted ways
170 List<Way> highlightedWays = new ArrayList<Way>();
171 List<Way> untaggedWays = new ArrayList<Way>();
172
173 for (final Way way : data.searchWays(bbox)){
174 if (way.isDrawable() && !ds.isSelected(way) && !way.isDisabledAndHidden()) {
175 if (way.isHighlighted()) {
176 highlightedWays.add(way);
177 } else if (!way.isTagged()) {
178 untaggedWays.add(way);
179 } else {
180 way.accept(this);
181 }
182 }
183 }
184 displaySegments();
185
186 // Display highlighted ways after the other ones (fix #8276)
187 for (List<Way> specialWays : Arrays.asList(new List[]{untaggedWays, highlightedWays})) {
188 for (final Way way : specialWays){
189 way.accept(this);
190 }
191 specialWays.clear();
192 displaySegments();
193 }
194
195 for (final OsmPrimitive osm : data.getSelected()) {
196 if (osm.isDrawable()) {
197 osm.accept(this);
198 }
199 }
200 displaySegments();
201
202 for (final OsmPrimitive osm: data.searchNodes(bbox)) {
203 if (osm.isDrawable() && !ds.isSelected(osm) && !osm.isDisabledAndHidden())
204 {
205 osm.accept(this);
206 }
207 }
208 drawVirtualNodes(data, bbox);
209
210 // draw highlighted way segments over the already drawn ways. Otherwise each
211 // way would have to be checked if it contains a way segment to highlight when
212 // in most of the cases there won't be more than one segment. Since the wireframe
213 // renderer does not feature any transparency there should be no visual difference.
214 for (final WaySegment wseg : data.getHighlightedWaySegments()) {
215 drawSegment(nc.getPoint(wseg.getFirstNode()), nc.getPoint(wseg.getSecondNode()), highlightColor, false);
216 }
217 displaySegments();
218 }
219
220 /**
221 * Helper function to calculate maximum of 4 values.
222 *
223 * @param a First value
224 * @param b Second value
225 * @param c Third value
226 * @param d Fourth value
227 */
228 private static final int max(int a, int b, int c, int d) {
229 return Math.max(Math.max(a, b), Math.max(c, d));
230 }
231
232 /**
233 * Draw a small rectangle.
234 * White if selected (as always) or red otherwise.
235 *
236 * @param n The node to draw.
237 */
238 @Override
239 public void visit(Node n) {
240 if (n.isIncomplete()) return;
241
242 if (n.isHighlighted()) {
243 drawNode(n, highlightColor, selectedNodeSize, fillSelectedNode);
244 } else {
245 Color color;
246
247 if (isInactiveMode || n.isDisabled()) {
248 color = inactiveColor;
249 } else if (ds.isSelected(n)) {
250 color = selectedColor;
251 } else if (n.isConnectionNode()) {
252 if (isNodeTagged(n)) {
253 color = taggedConnectionColor;
254 } else {
255 color = connectionColor;
256 }
257 } else {
258 if (isNodeTagged(n)) {
259 color = taggedColor;
260 } else {
261 color = nodeColor;
262 }
263 }
264
265 final int size = max((ds.isSelected(n) ? selectedNodeSize : 0),
266 (isNodeTagged(n) ? taggedNodeSize : 0),
267 (n.isConnectionNode() ? connectionNodeSize : 0),
268 unselectedNodeSize);
269
270 final boolean fill = (ds.isSelected(n) && fillSelectedNode) ||
271 (isNodeTagged(n) && fillTaggedNode) ||
272 (n.isConnectionNode() && fillConnectionNode) ||
273 fillUnselectedNode;
274
275 drawNode(n, color, size, fill);
276 }
277 }
278
279 private boolean isNodeTagged(Node n) {
280 return n.isTagged() || n.isAnnotated();
281 }
282
283 /**
284 * Draw a line for all way segments.
285 * @param w The way to draw.
286 */
287 @Override
288 public void visit(Way w) {
289 if (w.isIncomplete() || w.getNodesCount() < 2)
290 return;
291
292 /* show direction arrows, if draw.segment.relevant_directions_only is not set, the way is tagged with a direction key
293 (even if the tag is negated as in oneway=false) or the way is selected */
294
295 boolean showThisDirectionArrow = ds.isSelected(w) || showDirectionArrow;
296 /* head only takes over control if the option is true,
297 the direction should be shown at all and not only because it's selected */
298 boolean showOnlyHeadArrowOnly = showThisDirectionArrow && !ds.isSelected(w) && showHeadArrowOnly;
299 Color wayColor;
300
301 if (isInactiveMode || w.isDisabled()) {
302 wayColor = inactiveColor;
303 } else if(w.isHighlighted()) {
304 wayColor = highlightColor;
305 } else if(ds.isSelected(w)) {
306 wayColor = selectedColor;
307 } else if (!w.isTagged()) {
308 wayColor = untaggedWayColor;
309 } else {
310 wayColor = dfltWayColor;
311 }
312
313 Iterator<Node> it = w.getNodes().iterator();
314 if (it.hasNext()) {
315 Point lastP = nc.getPoint(it.next());
316 for (int orderNumber = 1; it.hasNext(); orderNumber++) {
317 Point p = nc.getPoint(it.next());
318 drawSegment(lastP, p, wayColor,
319 showOnlyHeadArrowOnly ? !it.hasNext() : showThisDirectionArrow);
320 if (showOrderNumber && !isInactiveMode) {
321 drawOrderNumber(lastP, p, orderNumber, g.getColor());
322 }
323 lastP = p;
324 }
325 }
326 }
327
328 /**
329 * Draw objects used in relations.
330 * @param r The relation to draw.
331 */
332 @Override
333 public void visit(Relation r) {
334 if (r.isIncomplete()) return;
335
336 Color col;
337 if (isInactiveMode || r.isDisabled()) {
338 col = inactiveColor;
339 } else if (ds.isSelected(r)) {
340 col = selectedColor;
341 } else {
342 col = relationColor;
343 }
344 g.setColor(col);
345
346 for (RelationMember m : r.getMembers()) {
347 if (m.getMember().isIncomplete() || !m.getMember().isDrawable()) {
348 continue;
349 }
350
351 if (m.isNode()) {
352 Point p = nc.getPoint(m.getNode());
353 if (p.x < 0 || p.y < 0
354 || p.x > nc.getWidth() || p.y > nc.getHeight()) {
355 continue;
356 }
357
358 g.drawOval(p.x-3, p.y-3, 6, 6);
359 } else if (m.isWay()) {
360 GeneralPath path = new GeneralPath();
361
362 boolean first = true;
363 for (Node n : m.getWay().getNodes()) {
364 if (!n.isDrawable()) {
365 continue;
366 }
367 Point p = nc.getPoint(n);
368 if (first) {
369 path.moveTo(p.x, p.y);
370 first = false;
371 } else {
372 path.lineTo(p.x, p.y);
373 }
374 }
375
376 g.draw(relatedWayStroke.createStrokedShape(path));
377 }
378 }
379 }
380
381 /**
382 * Visitor for changesets not used in this class
383 * @param cs The changeset for inspection.
384 */
385 @Override
386 public void visit(Changeset cs) {/* ignore */}
387
388 @Override
389 public void drawNode(Node n, Color color, int size, boolean fill) {
390 if (size > 1) {
391 int radius = size / 2;
392 Point p = nc.getPoint(n);
393 if ((p.x < 0) || (p.y < 0) || (p.x > nc.getWidth())
394 || (p.y > nc.getHeight()))
395 return;
396 g.setColor(color);
397 if (fill) {
398 g.fillRect(p.x - radius, p.y - radius, size, size);
399 g.drawRect(p.x - radius, p.y - radius, size, size);
400 } else {
401 g.drawRect(p.x - radius, p.y - radius, size, size);
402 }
403 }
404 }
405
406 /**
407 * Draw a line with the given color.
408 *
409 * @param path The path to append this segment.
410 * @param p1 First point of the way segment.
411 * @param p2 Second point of the way segment.
412 * @param showDirection <code>true</code> if segment direction should be indicated
413 */
414 protected void drawSegment(GeneralPath path, Point p1, Point p2, boolean showDirection) {
415 Rectangle bounds = g.getClipBounds();
416 bounds.grow(100, 100); // avoid arrow heads at the border
417 LineClip clip = new LineClip(p1, p2, bounds);
418 if (clip.execute()) {
419 p1 = clip.getP1();
420 p2 = clip.getP2();
421 path.moveTo(p1.x, p1.y);
422 path.lineTo(p2.x, p2.y);
423
424 if (showDirection) {
425 final double l = 10. / p1.distance(p2);
426
427 final double sx = l * (p1.x - p2.x);
428 final double sy = l * (p1.y - p2.y);
429
430 path.lineTo (p2.x + (int) Math.round(cosPHI * sx - sinPHI * sy), p2.y + (int) Math.round(sinPHI * sx + cosPHI * sy));
431 path.moveTo (p2.x + (int) Math.round(cosPHI * sx + sinPHI * sy), p2.y + (int) Math.round(- sinPHI * sx + cosPHI * sy));
432 path.lineTo(p2.x, p2.y);
433 }
434 }
435 }
436
437 /**
438 * Draw a line with the given color.
439 *
440 * @param p1 First point of the way segment.
441 * @param p2 Second point of the way segment.
442 * @param col The color to use for drawing line.
443 * @param showDirection <code>true</code> if segment direction should be indicated.
444 */
445 protected void drawSegment(Point p1, Point p2, Color col, boolean showDirection) {
446 if (col != currentColor) {
447 displaySegments(col);
448 }
449 drawSegment(currentPath, p1, p2, showDirection);
450 }
451
452 /**
453 * Checks if a polygon is visible in display.
454 *
455 * @param polygon The polygon to check.
456 * @return <code>true</code> if polygon is visible.
457 */
458 protected boolean isPolygonVisible(Polygon polygon) {
459 Rectangle bounds = polygon.getBounds();
460 if (bounds.width == 0 && bounds.height == 0) return false;
461 if (bounds.x > nc.getWidth()) return false;
462 if (bounds.y > nc.getHeight()) return false;
463 if (bounds.x + bounds.width < 0) return false;
464 if (bounds.y + bounds.height < 0) return false;
465 return true;
466 }
467
468 /**
469 * Finally display all segments in currect path.
470 */
471 protected void displaySegments() {
472 displaySegments(null);
473 }
474
475 /**
476 * Finally display all segments in currect path.
477 *
478 * @param newColor This color is set after the path is drawn.
479 */
480 protected void displaySegments(Color newColor) {
481 if (currentPath != null) {
482 g.setColor(currentColor);
483 g.draw(currentPath);
484 currentPath = new GeneralPath();
485 currentColor = newColor;
486 }
487 }
488}
Note: See TracBrowser for help on using the repository browser.