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

Last change on this file since 10884 was 10884, checked in by Don-vip, 8 years ago

fix #13427 - in wireframe mode, some line segments are invisible (patch by michael2402)

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