source: josm/trunk/src/org/openstreetmap/josm/data/osm/visitor/paint/MapPainter.java@ 3291

Last change on this file since 3291 was 3291, checked in by stoecker, 14 years ago

fix #4414 - draw selected relation and selected elements a bit different

  • Property svn:eol-style set to native
  • Property svn:mime-type set to text/plain
File size: 16.0 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.Font;
7import java.awt.FontMetrics;
8import java.awt.Graphics2D;
9import java.awt.Image;
10import java.awt.Point;
11import java.awt.Polygon;
12import java.awt.Rectangle;
13import java.awt.geom.GeneralPath;
14import java.awt.geom.Rectangle2D;
15import java.util.Arrays;
16import java.util.Collection;
17import java.util.Iterator;
18
19import javax.swing.ImageIcon;
20
21import org.openstreetmap.josm.Main;
22import org.openstreetmap.josm.data.osm.Node;
23import org.openstreetmap.josm.data.osm.OsmPrimitive;
24import org.openstreetmap.josm.data.osm.Way;
25import org.openstreetmap.josm.gui.NavigatableComponent;
26import org.openstreetmap.josm.tools.ImageProvider;
27import org.openstreetmap.josm.tools.LanguageInfo;
28
29public class MapPainter {
30 private final Graphics2D g;
31 private final NavigatableComponent nc;
32 private final boolean inactive;
33
34 private final boolean useStrokes;
35 private final boolean showNames;
36 private final boolean showIcons;
37
38 private final Color inactiveColor;
39 private final Color textColor;
40 private final Color selectedColor;
41 private final Color relationSelectedColor;
42 private final Color areaTextColor;
43 private final Color nodeColor;
44 private final Color backgroundColor;
45
46 private final Font orderFont;
47 private final int fillAlpha;
48 private final int virtualNodeSize;
49 private final int virtualNodeSpace;
50 private final int segmentNumberSpace;
51
52 private final double circum;
53
54 private final Collection<String> regionalNameOrder;
55
56 public MapPainter(MapPaintSettings settings, Graphics2D g, boolean inactive, NavigatableComponent nc, boolean virtual, double dist, double circum) {
57 this.g = g;
58 this.inactive = inactive;
59 this.nc = nc;
60 this.useStrokes = settings.getUseStrokesDistance() > dist;
61 this.showNames = settings.getShowNamesDistance() > dist;
62 this.showIcons = settings.getShowIconsDistance() > dist;
63
64 this.inactiveColor = PaintColors.INACTIVE.get();
65 this.textColor = PaintColors.TEXT.get();
66 this.selectedColor = PaintColors.SELECTED.get();
67 this.relationSelectedColor = PaintColors.RELATIONSELECTED.get();
68 this.areaTextColor = PaintColors.AREA_TEXT.get();
69 this.nodeColor = PaintColors.NODE.get();
70 this.backgroundColor = PaintColors.BACKGROUND.get();
71
72 this.orderFont = new Font(Main.pref.get("mappaint.font", "Helvetica"), Font.PLAIN, Main.pref.getInteger("mappaint.fontsize", 8));
73 this.fillAlpha = Math.min(255, Math.max(0, Integer.valueOf(Main.pref.getInteger("mappaint.fillalpha", 50))));
74 this.virtualNodeSize = virtual ? Main.pref.getInteger("mappaint.node.virtual-size", 8) / 2 : 0;
75 this.virtualNodeSpace = Main.pref.getInteger("mappaint.node.virtual-space", 70);
76 this.segmentNumberSpace = Main.pref.getInteger("mappaint.segmentnumber.space", 40);
77
78 String[] names = {"name:" + LanguageInfo.getJOSMLocaleCode(), "name", "int_name", "ref", "operator", "brand", "addr:housenumber"};
79 this.regionalNameOrder = Main.pref.getCollection("mappaint.nameOrder", Arrays.asList(names));
80 this.circum = circum;
81 }
82
83 public void drawWay(Way way, Color color, int width, float dashed[], Color dashedColor, boolean showDirection,
84 boolean reversedDirection, boolean showHeadArrowOnly) {
85
86 GeneralPath path = new GeneralPath();
87
88 Point lastPoint = null;
89 Iterator<Node> it = way.getNodes().iterator();
90 while (it.hasNext()) {
91 Node n = it.next();
92 Point p = nc.getPoint(n);
93 if(lastPoint != null) {
94 drawSegment(path, lastPoint, p, showHeadArrowOnly ? !it.hasNext() : showDirection, reversedDirection);
95 }
96 lastPoint = p;
97 }
98 displaySegments(path, color, width, dashed, dashedColor);
99 }
100
101 private void displaySegments(GeneralPath path, Color color, int width, float dashed[], Color dashedColor) {
102 g.setColor(inactive ? inactiveColor : color);
103 if (useStrokes) {
104 if (dashed.length > 0) {
105 g.setStroke(new BasicStroke(width,BasicStroke.CAP_BUTT,BasicStroke.JOIN_ROUND,0, dashed,0));
106 } else {
107 g.setStroke(new BasicStroke(width,BasicStroke.CAP_ROUND,BasicStroke.JOIN_ROUND));
108 }
109 }
110 g.draw(path);
111
112 if(!inactive && useStrokes && dashedColor != null) {
113 g.setColor(dashedColor);
114 if (dashed.length > 0) {
115 float[] dashedOffset = new float[dashed.length];
116 System.arraycopy(dashed, 1, dashedOffset, 0, dashed.length - 1);
117 dashedOffset[dashed.length-1] = dashed[0];
118 float offset = dashedOffset[0];
119 g.setStroke(new BasicStroke(width,BasicStroke.CAP_BUTT,BasicStroke.JOIN_ROUND,0,dashedOffset,offset));
120 } else {
121 g.setStroke(new BasicStroke(width,BasicStroke.CAP_ROUND,BasicStroke.JOIN_ROUND));
122 }
123 g.draw(path);
124 }
125
126 if(useStrokes) {
127 g.setStroke(new BasicStroke());
128 }
129 }
130
131 private static final double PHI = Math.toRadians(20);
132 private static final double cosPHI = Math.cos(PHI);
133 private static final double sinPHI = Math.sin(PHI);
134
135 private void drawSegment(GeneralPath path, Point p1, Point p2, boolean showDirection, boolean reversedDirection) {
136 boolean drawIt = false;
137 if (Main.isOpenjdk) {
138 /**
139 * Work around openjdk bug. It leads to drawing artefacts when zooming in a lot. (#4289, #4424)
140 * (It looks like int overflow when clipping.) We do custom clipping.
141 */
142 Rectangle bounds = g.getClipBounds();
143 bounds.grow(100, 100); // avoid arrow heads at the border
144 LineClip clip = new LineClip();
145 drawIt = clip.cohenSutherland(p1.x, p1.y, p2.x, p2.y, bounds.x, bounds.y, bounds.x+bounds.width, bounds.y+bounds.height);
146 p1 = clip.getP1();
147 p2 = clip.getP2();
148 } else {
149 drawIt = isSegmentVisible(p1, p2);
150 }
151 if (drawIt) {
152 /* draw segment line */
153 path.moveTo(p1.x, p1.y);
154 path.lineTo(p2.x, p2.y);
155
156 /* draw arrow */
157 if (showDirection) {
158 Point q1 = p1;
159 Point q2 = p2;
160 if (reversedDirection) {
161 q1 = p2;
162 q2 = p1;
163 path.moveTo(q2.x, q2.y);
164 }
165 final double l = 10. / q1.distance(q2);
166
167 final double sx = l * (q1.x - q2.x);
168 final double sy = l * (q1.y - q2.y);
169
170 path.lineTo (q2.x + (int) Math.round(cosPHI * sx - sinPHI * sy), q2.y + (int) Math.round(sinPHI * sx + cosPHI * sy));
171 path.moveTo (q2.x + (int) Math.round(cosPHI * sx + sinPHI * sy), q2.y + (int) Math.round(- sinPHI * sx + cosPHI * sy));
172 path.lineTo(q2.x, q2.y);
173 }
174 }
175 }
176
177 private boolean isSegmentVisible(Point p1, Point p2) {
178 if ((p1.x < 0) && (p2.x < 0)) return false;
179 if ((p1.y < 0) && (p2.y < 0)) return false;
180 if ((p1.x > nc.getWidth()) && (p2.x > nc.getWidth())) return false;
181 if ((p1.y > nc.getHeight()) && (p2.y > nc.getHeight())) return false;
182 return true;
183 }
184
185 public void drawNodeIcon(Node n, ImageIcon icon, boolean selected, boolean member, String name) {
186 Point p = nc.getPoint(n);
187 if ((p.x < 0) || (p.y < 0) || (p.x > nc.getWidth()) || (p.y > nc.getHeight())) return;
188
189 int w = icon.getIconWidth(), h=icon.getIconHeight();
190 icon.paintIcon ( nc, g, p.x-w/2, p.y-h/2 );
191 if(name != null) {
192 if (inactive || n.isDisabled()) {
193 g.setColor(inactiveColor);
194 } else {
195 g.setColor(textColor);
196 }
197 Font defaultFont = g.getFont();
198 g.setFont (orderFont);
199 g.drawString (name, p.x+w/2+2, p.y+h/2+2);
200 g.setFont(defaultFont);
201 }
202 if (selected)
203 {
204 g.setColor ( member ? relationSelectedColor : selectedColor );
205 g.drawRect (p.x-w/2-2, p.y-h/2-2, w+4, h+4);
206 }
207 }
208
209 /**
210 * Draw the node as small rectangle with the given color.
211 *
212 * @param n The node to draw.
213 * @param color The color of the node.
214 */
215 public void drawNode(Node n, Color color, int size, boolean fill, String name) {
216 if (size > 1) {
217 int radius = size / 2;
218 Point p = nc.getPoint(n);
219 if ((p.x < 0) || (p.y < 0) || (p.x > nc.getWidth())
220 || (p.y > nc.getHeight()))
221 return;
222
223 if (inactive || n.isDisabled()) {
224 g.setColor(inactiveColor);
225 } else {
226 g.setColor(color);
227 }
228 if (fill) {
229 g.fillRect(p.x - radius, p.y - radius, size, size);
230 g.drawRect(p.x - radius, p.y - radius, size, size);
231 } else {
232 g.drawRect(p.x - radius, p.y - radius, size, size);
233 }
234
235 if(name != null) {
236 if (inactive || n.isDisabled()) {
237 g.setColor(inactiveColor);
238 } else {
239 g.setColor(textColor);
240 }
241 Font defaultFont = g.getFont();
242 g.setFont (orderFont);
243 g.drawString (name, p.x+radius+2, p.y+radius+2);
244 g.setFont(defaultFont);
245 }
246 }
247 }
248
249 protected void drawArea(Polygon polygon, Color color, String name) {
250
251 /* set the opacity (alpha) level of the filled polygon */
252 g.setColor(new Color(color.getRed(), color.getGreen(), color.getBlue(), fillAlpha));
253 g.fillPolygon(polygon);
254
255 if (name != null) {
256 Rectangle pb = polygon.getBounds();
257 FontMetrics fontMetrics = g.getFontMetrics(orderFont); // if slow, use cache
258 Rectangle2D nb = fontMetrics.getStringBounds(name, g); // if slow, approximate by strlen()*maxcharbounds(font)
259
260 // Point2D c = getCentroid(polygon);
261 // Using the Centroid is Nicer for buildings like: +--------+
262 // but this needs to be fast. As most houses are | 42 |
263 // boxes anyway, the center of the bounding box +---++---+
264 // will have to do. ++
265 // Centroids are not optimal either, just imagine a U-shaped house.
266 // Point2D c = new Point2D.Double(pb.x + pb.width / 2.0, pb.y + pb.height / 2.0);
267 // Rectangle2D.Double centeredNBounds =
268 // new Rectangle2D.Double(c.getX() - nb.getWidth()/2,
269 // c.getY() - nb.getHeight()/2,
270 // nb.getWidth(),
271 // nb.getHeight());
272
273 Rectangle centeredNBounds = new Rectangle(pb.x + (int)((pb.width - nb.getWidth())/2.0),
274 pb.y + (int)((pb.height - nb.getHeight())/2.0),
275 (int)nb.getWidth(),
276 (int)nb.getHeight());
277
278 if ((pb.width >= nb.getWidth() && pb.height >= nb.getHeight()) && // quick check
279 polygon.contains(centeredNBounds) // slow but nice
280 ) {
281 g.setColor(areaTextColor);
282 Font defaultFont = g.getFont();
283 g.setFont (orderFont);
284 g.drawString (name,
285 (int)(centeredNBounds.getMinX() - nb.getMinX()),
286 (int)(centeredNBounds.getMinY() - nb.getMinY()));
287 g.setFont(defaultFont);
288 }
289 }
290 }
291
292 public void drawRestriction(ImageIcon icon, Point pVia, double vx, double vx2, double vy, double vy2, double iconAngle, boolean selected) {
293 /* rotate icon with direction last node in from to */
294 ImageIcon rotatedIcon = ImageProvider.createRotatedImage(null /*icon2*/, icon, iconAngle);
295
296 /* scale down icon to 16*16 pixels */
297 ImageIcon smallIcon = new ImageIcon(rotatedIcon.getImage().getScaledInstance(16 , 16, Image.SCALE_SMOOTH));
298 int w = smallIcon.getIconWidth(), h=smallIcon.getIconHeight();
299 smallIcon.paintIcon (nc, g, (int)(pVia.x+vx+vx2)-w/2, (int)(pVia.y+vy+vy2)-h/2 );
300
301 if (selected) {
302 g.setColor(relationSelectedColor);
303 g.drawRect((int)(pVia.x+vx+vx2)-w/2-2,(int)(pVia.y+vy+vy2)-h/2-2, w+4, h+4);
304 }
305 }
306
307 public void drawVirtualNodes(Collection<Way> ways) {
308
309 if (virtualNodeSize != 0) {
310 GeneralPath path = new GeneralPath();
311 for (Way osm: ways){
312 if (osm.isUsable() && !osm.isFiltered() && !osm.isDisabled()) {
313 visitVirtual(path, osm);
314 }
315 }
316 g.setColor(nodeColor);
317 g.draw(path);
318 }
319 }
320
321 public void visitVirtual(GeneralPath path, Way w) {
322 Iterator<Node> it = w.getNodes().iterator();
323 if (it.hasNext()) {
324 Point lastP = nc.getPoint(it.next());
325 while(it.hasNext())
326 {
327 Point p = nc.getPoint(it.next());
328 if(isSegmentVisible(lastP, p) && isLargeSegment(lastP, p, virtualNodeSpace))
329 {
330 int x = (p.x+lastP.x)/2;
331 int y = (p.y+lastP.y)/2;
332 path.moveTo(x-virtualNodeSize, y);
333 path.lineTo(x+virtualNodeSize, y);
334 path.moveTo(x, y-virtualNodeSize);
335 path.lineTo(x, y+virtualNodeSize);
336 }
337 lastP = p;
338 }
339 }
340 }
341
342 private static boolean isLargeSegment(Point p1, Point p2, int space) {
343 int xd = p1.x-p2.x; if(xd < 0) {
344 xd = -xd;
345 }
346 int yd = p1.y-p2.y; if(yd < 0) {
347 yd = -yd;
348 }
349 return (xd+yd > space);
350 }
351
352 /**
353 * Draw a number of the order of the two consecutive nodes within the
354 * parents way
355 */
356 public void drawOrderNumber(Node n1, Node n2, int orderNumber) {
357 Point p1 = nc.getPoint(n1);
358 Point p2 = nc.getPoint(n2);
359 drawOrderNumber(p1, p2, orderNumber);
360 }
361
362 /**
363 * Draw an number of the order of the two consecutive nodes within the
364 * parents way
365 */
366 protected void drawOrderNumber(Point p1, Point p2, int orderNumber) {
367 if (isSegmentVisible(p1, p2) && isLargeSegment(p1, p2, segmentNumberSpace)) {
368 String on = Integer.toString(orderNumber);
369 int strlen = on.length();
370 int x = (p1.x+p2.x)/2 - 4*strlen;
371 int y = (p1.y+p2.y)/2 + 4;
372
373 if(virtualNodeSize != 0 && isLargeSegment(p1, p2, virtualNodeSpace))
374 {
375 y = (p1.y+p2.y)/2 - virtualNodeSize - 3;
376 }
377
378 Color c = g.getColor();
379 g.setColor(backgroundColor);
380 g.fillRect(x-1, y-12, 8*strlen+1, 14);
381 g.setColor(c);
382 g.drawString(on, x, y);
383 }
384 }
385
386 //TODO Not a good place for this method
387 public String getNodeName(Node n) {
388 String name = null;
389 if (n.hasKeys()) {
390 for (String rn : regionalNameOrder) {
391 name = n.get(rn);
392 if (name != null) {
393 break;
394 }
395 }
396 }
397 return name;
398 }
399
400 //TODO Not a good place for this method
401 public String getAreaName(OsmPrimitive w) {
402 String name = null;
403 if (w.hasKeys()) {
404 for (String rn : regionalNameOrder) {
405 name = w.get(rn);
406 if (name != null) {
407 break;
408 }
409 }
410 }
411 return name;
412 }
413
414 public boolean isInactive() {
415 return inactive;
416 }
417
418 public boolean isShowNames() {
419 return showNames;
420 }
421
422 public double getCircum() {
423 return circum;
424 }
425
426 public boolean isShowIcons() {
427 return showIcons;
428 }
429
430}
Note: See TracBrowser for help on using the repository browser.