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

Last change on this file since 3083 was 3083, checked in by bastiK, 14 years ago

added svn:eol-style=native to source files

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