source: josm/trunk/src/org/openstreetmap/josm/data/osm/visitor/MapPaintVisitor.java@ 1190

Last change on this file since 1190 was 1190, checked in by stoecker, 15 years ago

added first part of multipolygon drawing

  • Property svn:eol-style set to native
File size: 23.1 KB
Line 
1// License: GPL. Copyright 2007 by Immanuel Scholz and others
2package org.openstreetmap.josm.data.osm.visitor;
3
4import static org.openstreetmap.josm.tools.I18n.marktr;
5
6import java.awt.BasicStroke;
7import java.awt.Color;
8import java.awt.Font;
9import java.awt.Graphics2D;
10import java.awt.Point;
11import java.awt.Polygon;
12import java.awt.Stroke;
13import java.awt.geom.GeneralPath;
14import java.util.ArrayList;
15import java.util.Collection;
16import java.util.LinkedList;
17import java.util.Locale;
18
19import javax.swing.ImageIcon;
20
21import org.openstreetmap.josm.Main;
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.visitor.SimplePaintVisitor;
29import org.openstreetmap.josm.gui.mappaint.AreaElemStyle;
30import org.openstreetmap.josm.gui.mappaint.ElemStyle;
31import org.openstreetmap.josm.gui.mappaint.ElemStyles;
32import org.openstreetmap.josm.gui.mappaint.IconElemStyle;
33import org.openstreetmap.josm.gui.mappaint.LineElemStyle;
34import org.openstreetmap.josm.gui.mappaint.MapPaintStyles;
35
36public class MapPaintVisitor extends SimplePaintVisitor {
37 protected boolean useRealWidth;
38 protected boolean zoomLevelDisplay;
39 protected boolean fillAreas;
40 protected int fillAlpha;
41 protected Color untaggedColor;
42 protected Color textColor;
43 protected boolean currentDashed = false;
44 protected int currentWidth = 0;
45 protected Stroke currentStroke = null;
46 protected Font orderFont;
47 protected ElemStyles styles;
48 protected double circum;
49 protected String regionalNameOrder[];
50 protected Collection<Way> alreadyDrawnWays = new LinkedList<Way>();
51 protected Collection<Way> alreadyDrawnAreas = new LinkedList<Way>();
52
53 protected boolean isZoomOk(ElemStyle e) {
54 if (!zoomLevelDisplay) /* show everything if the user wishes so */
55 return true;
56
57 if(e == null) /* the default for things that don't have a rule (show, if scale is smaller than 1500m) */
58 return (circum < 1500);
59
60 // formula to calculate a map scale: natural size / map size = scale
61 // example: 876000mm (876m as displayed) / 22mm (roughly estimated screen size of legend bar) = 39818
62 //
63 // so the exact "correcting value" below depends only on the screen size and resolution
64 // XXX - do we need a Preference setting for this (if things vary widely)?
65 return !(circum >= e.maxScale / 22 || circum < e.minScale / 22);
66 }
67
68 /**
69 * Draw a small rectangle.
70 * White if selected (as always) or red otherwise.
71 *
72 * @param n The node to draw.
73 */
74 public void visit(Node n) {
75 IconElemStyle nodeStyle = styles.get(n);
76 if (nodeStyle != null && isZoomOk(nodeStyle))
77 drawNode(n, nodeStyle.icon, nodeStyle.annotate);
78 else if (n.selected)
79 drawNode(n, selectedColor, selectedNodeSize, selectedNodeRadius, fillSelectedNode);
80 else if (n.tagged)
81 drawNode(n, nodeColor, taggedNodeSize, taggedNodeRadius, fillUnselectedNode);
82 else
83 drawNode(n, nodeColor, unselectedNodeSize, unselectedNodeRadius, fillUnselectedNode);
84 }
85
86 /**
87 * Draw a line for all segments, according to tags.
88 * @param w The way to draw.
89 */
90 public void visit(Way w) {
91 if(w.nodes.size() < 2)
92 return;
93
94 ElemStyle wayStyle = styles.get(w);
95
96 if(!isZoomOk(wayStyle))
97 return;
98
99 LineElemStyle l = null;
100 Color areacolor = untaggedColor;
101 if(wayStyle!=null)
102 {
103 boolean area = false;
104 if(wayStyle instanceof LineElemStyle)
105 l = (LineElemStyle)wayStyle;
106 else if (wayStyle instanceof AreaElemStyle)
107 {
108 areacolor = ((AreaElemStyle)wayStyle).color;
109 l = ((AreaElemStyle)wayStyle).line;
110 area = true;
111 }
112 if (area && fillAreas)
113 drawWayAsArea(w, areacolor);
114 }
115
116 drawWay(w, l, areacolor);
117 }
118
119 public void drawWay(Way w, LineElemStyle l, Color color) {
120 // show direction arrows, if draw.segment.relevant_directions_only is not set,
121 // the way is tagged with a direction key
122 // (even if the tag is negated as in oneway=false) or the way is selected
123 boolean showDirection = w.selected || ((!useRealWidth) && (showDirectionArrow
124 && (!showRelevantDirectionsOnly || w.hasDirectionKeys)));
125 int width = defaultSegmentWidth;
126 int realWidth = 0; //the real width of the element in meters
127 boolean dashed = false;
128
129 if(l != null)
130 {
131 color = l.color;
132 width = l.width;
133 realWidth = l.realWidth;
134 dashed = l.dashed;
135 }
136 if (realWidth > 0 && useRealWidth && !showDirection)
137 {
138 int tmpWidth = (int) (100 / (float) (circum / realWidth));
139 if (tmpWidth > width) width = tmpWidth;
140 }
141 if(w.selected)
142 color = selectedColor;
143
144 Node lastN;
145 if(l != null && l.overlays != null)
146 {
147 for(LineElemStyle s : l.overlays)
148 {
149 if(!s.over)
150 {
151 lastN = null;
152 for(Node n : w.nodes)
153 {
154 if(lastN != null)
155 {
156 drawSeg(lastN, n, s.color != null && !w.selected ? s.color : color,
157 false, s.getWidth(width), s.dashed);
158 }
159 lastN = n;
160 }
161 }
162 }
163 }
164
165 lastN = null;
166 for(Node n : w.nodes)
167 {
168 if(lastN != null)
169 drawSeg(lastN, n, color, showDirection, width, dashed);
170 lastN = n;
171 }
172
173 if(l != null && l.overlays != null)
174 {
175 for(LineElemStyle s : l.overlays)
176 {
177 if(s.over)
178 {
179 lastN = null;
180 for(Node n : w.nodes)
181 {
182 if(lastN != null)
183 {
184 drawSeg(lastN, n, s.color != null && !w.selected ? s.color : color,
185 false, s.getWidth(width), s.dashed);
186 }
187 lastN = n;
188 }
189 }
190 }
191 }
192
193 if(showOrderNumber)
194 {
195 int orderNumber = 0;
196 lastN = null;
197 for(Node n : w.nodes)
198 {
199 if(lastN != null)
200 {
201 orderNumber++;
202 drawOrderNumber(lastN, n, orderNumber);
203 }
204 lastN = n;
205 }
206 }
207 displaySegments();
208 }
209
210 public Collection<Way> joinWays(Collection<Way> join)
211 {
212 Collection<Way> res = new LinkedList<Way>();
213 Object[] joinArray = join.toArray();
214 int left = join.size();
215 while(left != 0)
216 {
217 Way w = null;
218 Boolean selected = false;
219 ArrayList<Node> n = null;
220 Boolean joined = true;
221 while(joined && left != 0)
222 {
223 joined = false;
224 for(int i = 0; i < joinArray.length && left != 0; ++i)
225 {
226 if(joinArray[i] != null)
227 {
228 Way c = (Way)joinArray[i];
229 if(w == null)
230 { w = c; selected = w.selected; joinArray[i] = null; --left; }
231 else
232 {
233 int mode = 0;
234 int cl = c.nodes.size()-1;
235 int nl;
236 if(n == null)
237 {
238 nl = w.nodes.size()-1;
239 if(w.nodes.get(nl) == c.nodes.get(0)) mode = 21;
240 else if(w.nodes.get(nl) == c.nodes.get(cl)) mode = 22;
241 else if(w.nodes.get(0) == c.nodes.get(0)) mode = 11;
242 else if(w.nodes.get(0) == c.nodes.get(cl)) mode = 12;
243 }
244 else
245 {
246 nl = n.size()-1;
247 if(n.get(nl) == c.nodes.get(0)) mode = 21;
248 else if(n.get(0) == c.nodes.get(cl)) mode = 12;
249 else if(n.get(0) == c.nodes.get(0)) mode = 11;
250 else if(n.get(nl) == c.nodes.get(cl)) mode = 22;
251 }
252 if(mode != 0)
253 {
254 joinArray[i] = null;
255 joined = true;
256 if(c.selected) selected = true;
257 --left;
258 if(n == null) n = new ArrayList(w.nodes);
259System.out.println("old: " + n);
260System.out.println("new: " + c.nodes);
261 n.remove((mode == 21 || mode == 22) ? nl : 0);
262 if(mode == 21)
263 n.addAll(c.nodes);
264 else if(mode == 12)
265 n.addAll(0, c.nodes);
266 else if(mode == 22)
267 {
268 for(Node node : c.nodes)
269 n.add(nl, node);
270System.out.println("ERROR: Joining way reversed: " + c);
271 }
272 else /* mode == 11 */
273 {
274 for(Node node : c.nodes)
275 n.add(0, node);
276System.out.println("ERROR: Joining way reversed: " + c);
277 }
278System.out.println("joined: " + n);
279 }
280 }
281 }
282 } /* for(i = ... */
283 } /* while(joined) */
284 if(n != null)
285 {
286 w = new Way(w);
287 w.nodes.clear();
288 w.nodes.addAll(n);
289 w.selected = selected;
290 }
291 if(!w.isClosed())
292 {
293System.out.println("ERROR: multipolygon way is not closed." + w);
294 }
295 res.add(w);
296 } /* while(left != 0) */
297
298 return res;
299 }
300
301 public void visit(Relation r) {
302 // draw multipolygon relations including their ways
303 // other relations are not (yet?) drawn.
304 if (r.incomplete) return;
305
306 if(!Main.pref.getBoolean("mappaint.multipolygon",false)) return;
307
308 if(!"multipolygon".equals(r.keys.get("type"))) return;
309
310 Collection<Way> inner = new LinkedList<Way>();
311 Collection<Way> outer = new LinkedList<Way>();
312 Collection<Way> innerclosed = new LinkedList<Way>();
313 Collection<Way> outerclosed = new LinkedList<Way>();
314
315 for (RelationMember m : r.members)
316 {
317 if (!m.member.incomplete && !m.member.deleted)
318 {
319 if(m.member instanceof Way)
320 {
321 Way w = (Way) m.member;
322 if(w.nodes.size() < 2)
323 {
324System.out.println("ERROR: Way with less than two points " + w);
325 }
326 else if("inner".equals(m.role))
327 inner.add(w);
328 else if("outer".equals(m.role))
329 outer.add(w);
330 else
331 {
332System.out.println("ERROR: No useful role for Way " + w);
333 if(m.role == null || m.role.length() == 0)
334 outer.add(w);
335 }
336 }
337 else
338 {
339System.out.println("ERROR: Non-Way in multipolygon " + m.member);
340 }
341 }
342 }
343
344 ElemStyle wayStyle = styles.get(r);
345 /* find one wayStyle, prefer the style from Relation or take the first
346 one of outer rings */
347 if(wayStyle == null || !(wayStyle instanceof AreaElemStyle))
348 {
349 for (Way w : outer)
350 {
351 if(wayStyle == null || !(wayStyle instanceof AreaElemStyle))
352 wayStyle = styles.get(w);
353 }
354 }
355
356 if(wayStyle != null && wayStyle instanceof AreaElemStyle)
357 {
358 Boolean zoomok = isZoomOk(wayStyle);
359 Collection<Way> join = new LinkedList<Way>();
360
361 /* parse all outer rings and join them */
362 for (Way w : outer)
363 {
364 if(w.isClosed()) outerclosed.add(w);
365 else join.add(w);
366 }
367 if(join.size() != 0)
368 {
369 for(Way w : joinWays(join))
370 outerclosed.add(w);
371 }
372
373 /* parse all inner rings and join them */
374 join.clear();
375 for (Way w : inner)
376 {
377 if(w.isClosed()) innerclosed.add(w);
378 else join.add(w);
379 }
380 if(join.size() != 0)
381 {
382 for(Way w : joinWays(join))
383 innerclosed.add(w);
384 }
385
386 /* handle inside out stuff */
387
388 if(zoomok) /* draw */
389 {
390 for (Way w : outerclosed)
391 {
392 Color color = w.selected ? selectedColor
393 : ((AreaElemStyle)wayStyle).color;
394 Polygon polygon = new Polygon();
395 Point pOuter = null;
396
397 for (Node n : w.nodes)
398 {
399 pOuter = nc.getPoint(n.eastNorth);
400 polygon.addPoint(pOuter.x,pOuter.y);
401 }
402 for (Way wInner : innerclosed)
403 {
404 for (Node n : wInner.nodes)
405 {
406 Point pInner = nc.getPoint(n.eastNorth);
407 polygon.addPoint(pInner.x,pInner.y);
408 }
409 polygon.addPoint(pOuter.x,pOuter.y);
410 }
411
412 g.setColor(new Color( color.getRed(), color.getGreen(),
413 color.getBlue(), fillAlpha));
414
415 g.fillPolygon(polygon);
416 alreadyDrawnAreas.add(w);
417 }
418 }
419 for (Way wInner : inner)
420 {
421 ElemStyle innerStyle = styles.get(wInner);
422 if(innerStyle == null)
423 {
424 if(zoomok)
425 drawWay(wInner, ((AreaElemStyle)wayStyle).line,
426 ((AreaElemStyle)wayStyle).color);
427 alreadyDrawnWays.add(wInner);
428 }
429 else if(wayStyle.equals(innerStyle))
430 {
431System.out.println("WARNING: Inner waystyle equals multipolygon for way " + wInner);
432 alreadyDrawnAreas.add(wInner);
433 }
434 }
435 for (Way wOuter : outer)
436 {
437 ElemStyle outerStyle = styles.get(wOuter);
438 if(outerStyle == null)
439 {
440 if(zoomok)
441 drawWay(wOuter, ((AreaElemStyle)wayStyle).line,
442 ((AreaElemStyle)wayStyle).color);
443 alreadyDrawnWays.add(wOuter);
444 }
445 else
446 {
447 if(!wayStyle.equals(outerStyle))
448System.out.println("ERROR: Outer waystyle does not match multipolygon for way " + wOuter);
449 alreadyDrawnAreas.add(wOuter);
450 }
451 }
452 }
453 }
454
455 protected void drawWayAsArea(Way w, Color color)
456 {
457 Polygon polygon = new Polygon();
458
459 for (Node n : w.nodes)
460 {
461 Point p = nc.getPoint(n.eastNorth);
462 polygon.addPoint(p.x,p.y);
463 }
464
465 Color mycolor = w.selected ? selectedColor : color;
466 // set the opacity (alpha) level of the filled polygon
467 g.setColor(new Color( mycolor.getRed(), mycolor.getGreen(), mycolor.getBlue(), fillAlpha));
468
469 g.fillPolygon(polygon);
470 }
471
472 // NEW
473 protected void drawNode(Node n, ImageIcon icon, boolean annotate) {
474 Point p = nc.getPoint(n.eastNorth);
475 if ((p.x < 0) || (p.y < 0) || (p.x > nc.getWidth()) || (p.y > nc.getHeight())) return;
476 int w = icon.getIconWidth(), h=icon.getIconHeight();
477 icon.paintIcon ( Main.map.mapView, g, p.x-w/2, p.y-h/2 );
478 String name = getNodeName(n);
479 if (name!=null && annotate)
480 {
481 g.setColor(textColor);
482 Font defaultFont = g.getFont();
483 g.setFont (orderFont);
484 g.drawString (name, p.x+w/2+2, p.y+h/2+2);
485 g.setFont(defaultFont);
486 }
487 if (n.selected)
488 {
489 g.setColor ( selectedColor );
490 g.drawRect (p.x-w/2-2,p.y-w/2-2, w+4, h+4);
491 }
492 }
493
494 protected String getNodeName(Node n) {
495 String name = null;
496 if (n.keys != null) {
497 for (int i = 0; i < regionalNameOrder.length; i++) {
498 name = n.keys.get(regionalNameOrder[i]);
499 if (name != null) break;
500 }
501 }
502 return name;
503 }
504
505 private void drawSeg(Node n1, Node n2, Color col, boolean showDirection, int width, boolean dashed) {
506 if (col != currentColor || width != currentWidth || dashed != currentDashed) {
507 displaySegments(col, width, dashed);
508 }
509 Point p1 = nc.getPoint(n1.eastNorth);
510 Point p2 = nc.getPoint(n2.eastNorth);
511
512 if (!isSegmentVisible(p1, p2)) {
513 return;
514 }
515 currentPath.moveTo(p1.x, p1.y);
516 currentPath.lineTo(p2.x, p2.y);
517
518 if (showDirection) {
519 double t = Math.atan2(p2.y-p1.y, p2.x-p1.x) + Math.PI;
520 currentPath.lineTo((int)(p2.x + 10*Math.cos(t-PHI)), (int)(p2.y + 10*Math.sin(t-PHI)));
521 currentPath.moveTo((int)(p2.x + 10*Math.cos(t+PHI)), (int)(p2.y + 10*Math.sin(t+PHI)));
522 currentPath.lineTo(p2.x, p2.y);
523 }
524 }
525
526 protected void displaySegments() {
527 displaySegments(null, 0, false);
528 }
529
530 protected void displaySegments(Color newColor, int newWidth, boolean newDash) {
531 if (currentPath != null) {
532 Graphics2D g2d = (Graphics2D)g;
533 g2d.setColor(inactive ? inactiveColor : currentColor);
534 if (currentStroke == null) {
535 if (currentDashed)
536 g2d.setStroke(new BasicStroke(currentWidth,BasicStroke.CAP_BUTT,BasicStroke.JOIN_ROUND,0,new float[] {9},0));
537 else
538 g2d.setStroke(new BasicStroke(currentWidth,BasicStroke.CAP_ROUND,BasicStroke.JOIN_ROUND));
539 }
540 g2d.draw(currentPath);
541 g2d.setStroke(new BasicStroke(1));
542
543 currentPath = new GeneralPath();
544 currentColor = newColor;
545 currentWidth = newWidth;
546 currentDashed = newDash;
547 currentStroke = null;
548 }
549 }
550
551 /**
552 * Draw the node as small rectangle with the given color.
553 *
554 * @param n The node to draw.
555 * @param color The color of the node.
556 */
557 public void drawNode(Node n, Color color, int size, int radius, boolean fill) {
558 if (isZoomOk(null) && size > 1) {
559 Point p = nc.getPoint(n.eastNorth);
560 if ((p.x < 0) || (p.y < 0) || (p.x > nc.getWidth())
561 || (p.y > nc.getHeight()))
562 return;
563 g.setColor(color);
564 if (fill) {
565 g.fillRect(p.x - radius, p.y - radius, size, size);
566 g.drawRect(p.x - radius, p.y - radius, size, size);
567 } else
568 g.drawRect(p.x - radius, p.y - radius, size, size);
569 }
570 }
571
572 // NW 111106 Overridden from SimplePaintVisitor in josm-1.4-nw1
573 // Shows areas before non-areas
574 public void visitAll(DataSet data, Boolean virtual) {
575 getSettings(virtual);
576 untaggedColor = Main.pref.getColor(marktr("untagged"),Color.GRAY);
577 textColor = Main.pref.getColor (marktr("text"), Color.WHITE);
578 useRealWidth = Main.pref.getBoolean("mappaint.useRealWidth",false);
579 zoomLevelDisplay = Main.pref.getBoolean("mappaint.zoomLevelDisplay",false);
580 fillAreas = Main.pref.getBoolean("mappaint.fillareas", true);
581 fillAlpha = Math.min(255, Math.max(0, Integer.valueOf(Main.pref.getInteger("mappaint.fillalpha", 50))));
582 circum = Main.map.mapView.getScale()*100*Main.proj.scaleFactor()*40041455; // circumference of the earth in meter
583 styles = MapPaintStyles.getStyles();
584 orderFont = new Font(Main.pref.get("mappaint.font","Helvetica"), Font.PLAIN, Main.pref.getInteger("mappaint.fontsize", 8));
585 String currentLocale = Locale.getDefault().getLanguage();
586 regionalNameOrder = Main.pref.get("mappaint.nameOrder", "name:"+currentLocale+";name;int_name").split(";");
587
588 if (fillAreas && styles.hasAreas()) {
589 Collection<Way> noAreaWays = new LinkedList<Way>();
590
591 for (final Relation osm : data.relations)
592 {
593 if (!osm.deleted && !osm.selected)
594 {
595 osm.visit(this);
596 }
597 }
598
599 for (final Way osm : data.ways)
600 {
601 if (!osm.incomplete && !osm.deleted && !alreadyDrawnWays.contains(osm))
602 {
603 if(styles.isArea((Way)osm) && !alreadyDrawnAreas.contains(osm))
604 osm.visit(this);
605 else
606 noAreaWays.add((Way)osm);
607 }
608 }
609 // free that stuff
610 alreadyDrawnWays = null;
611 alreadyDrawnAreas = null;
612
613 fillAreas = false;
614 for (final OsmPrimitive osm : noAreaWays)
615 osm.visit(this);
616 }
617 else
618 {
619 for (final OsmPrimitive osm : data.ways)
620 if (!osm.incomplete && !osm.deleted)
621 osm.visit(this);
622 }
623
624 for (final OsmPrimitive osm : data.getSelected())
625 if (!osm.incomplete && !osm.deleted){
626 osm.visit(this);
627 }
628
629 displaySegments();
630
631 for (final OsmPrimitive osm : data.nodes)
632 if (!osm.incomplete && !osm.deleted)
633 osm.visit(this);
634
635 if (virtualNodeSize != 0)
636 {
637 currentColor = nodeColor;
638 for (final OsmPrimitive osm : data.ways)
639 if (!osm.deleted)
640 visitVirtual((Way)osm);
641 displaySegments(null);
642 }
643 }
644
645 /**
646 * Draw a number of the order of the two consecutive nodes within the
647 * parents way
648 */
649 protected void drawOrderNumber(Node n1, Node n2, int orderNumber) {
650 Point p1 = nc.getPoint(n1.eastNorth);
651 Point p2 = nc.getPoint(n2.eastNorth);
652 drawOrderNumber(p1, p2, orderNumber);
653 }
654}
Note: See TracBrowser for help on using the repository browser.