source: osm/applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/JMapViewer.java@ 19697

Last change on this file since 19697 was 19697, checked in by stotz, 14 years ago

JavaDoc added for getPosition() methods

File size: 22.6 KB
Line 
1package org.openstreetmap.gui.jmapviewer;
2
3//License: GPL. Copyright 2008 by Jan Peter Stotz
4
5import java.awt.Dimension;
6import java.awt.Font;
7import java.awt.Graphics;
8import java.awt.Insets;
9import java.awt.Point;
10import java.awt.event.ActionEvent;
11import java.awt.event.ActionListener;
12import java.awt.event.MouseEvent;
13import java.util.LinkedList;
14import java.util.List;
15
16import javax.swing.ImageIcon;
17import javax.swing.JButton;
18import javax.swing.JPanel;
19import javax.swing.JSlider;
20import javax.swing.event.ChangeEvent;
21import javax.swing.event.ChangeListener;
22
23import org.openstreetmap.gui.jmapviewer.interfaces.MapMarker;
24import org.openstreetmap.gui.jmapviewer.interfaces.MapRectangle;
25import org.openstreetmap.gui.jmapviewer.interfaces.TileCache;
26import org.openstreetmap.gui.jmapviewer.interfaces.TileLoader;
27import org.openstreetmap.gui.jmapviewer.interfaces.TileLoaderListener;
28import org.openstreetmap.gui.jmapviewer.interfaces.TileSource;
29
30/**
31 *
32 * Provides a simple panel that displays pre-rendered map tiles loaded from the
33 * OpenStreetMap project.
34 *
35 * @author Jan Peter Stotz
36 *
37 */
38public class JMapViewer extends JPanel implements TileLoaderListener {
39
40 private static final long serialVersionUID = 1L;
41
42 /**
43 * Vectors for clock-wise tile painting
44 */
45 protected static final Point[] move = { new Point(1, 0), new Point(0, 1), new Point(-1, 0), new Point(0, -1) };
46
47 public static final int MAX_ZOOM = 22;
48 public static final int MIN_ZOOM = 0;
49
50 protected List<MapMarker> mapMarkerList;
51 protected List<MapRectangle> mapRectangleList;
52
53 protected boolean mapMarkersVisible;
54 protected boolean mapRectanglesVisible;
55
56 protected boolean tileGridVisible;
57
58 protected TileController tileController;
59
60 /**
61 * x- and y-position of the center of this map-panel on the world map
62 * denoted in screen pixel regarding the current zoom level.
63 */
64 protected Point center;
65
66 /**
67 * Current zoom level
68 */
69 protected int zoom;
70
71 protected JSlider zoomSlider;
72 protected JButton zoomInButton;
73 protected JButton zoomOutButton;
74
75 /**
76 * Creates a standard {@link JMapViewer} instance that can be controlled via
77 * mouse: hold right mouse button for moving, double click left mouse button
78 * or use mouse wheel for zooming. Loaded tiles are stored the
79 * {@link MemoryTileCache} and the tile loader uses 4 parallel threads for
80 * retrieving the tiles.
81 */
82 public JMapViewer() {
83 this(new MemoryTileCache(), 4);
84 new DefaultMapController(this);
85 }
86
87 public JMapViewer(TileCache tileCache, int downloadThreadCount) {
88 super();
89 tileController = new TileController(new OsmTileSource.Mapnik(), tileCache, this);
90 mapMarkerList = new LinkedList<MapMarker>();
91 mapRectangleList = new LinkedList<MapRectangle>();
92 mapMarkersVisible = true;
93 mapRectanglesVisible = true;
94 tileGridVisible = false;
95 setLayout(null);
96 initializeZoomSlider();
97 setMinimumSize(new Dimension(Tile.SIZE, Tile.SIZE));
98 setPreferredSize(new Dimension(400, 400));
99 setDisplayPositionByLatLon(50, 9, 3);
100 //setToolTipText("");
101 }
102
103 @Override
104 public String getToolTipText(MouseEvent event) {
105 // Point screenPoint = event.getLocationOnScreen();
106 // Coordinate c = getPosition(screenPoint);
107 return super.getToolTipText(event);
108 }
109
110 protected void initializeZoomSlider() {
111 zoomSlider = new JSlider(MIN_ZOOM, tileController.getTileSource().getMaxZoom());
112 zoomSlider.setOrientation(JSlider.VERTICAL);
113 zoomSlider.setBounds(10, 10, 30, 150);
114 zoomSlider.setOpaque(false);
115 zoomSlider.addChangeListener(new ChangeListener() {
116 public void stateChanged(ChangeEvent e) {
117 setZoom(zoomSlider.getValue());
118 }
119 });
120 add(zoomSlider);
121 int size = 18;
122 try {
123 ImageIcon icon = new ImageIcon(getClass().getResource("images/plus.png"));
124 zoomInButton = new JButton(icon);
125 } catch (Exception e) {
126 zoomInButton = new JButton("+");
127 zoomInButton.setFont(new Font("sansserif", Font.BOLD, 9));
128 zoomInButton.setMargin(new Insets(0, 0, 0, 0));
129 }
130 zoomInButton.setBounds(4, 155, size, size);
131 zoomInButton.addActionListener(new ActionListener() {
132
133 public void actionPerformed(ActionEvent e) {
134 zoomIn();
135 }
136 });
137 add(zoomInButton);
138 try {
139 ImageIcon icon = new ImageIcon(getClass().getResource("images/minus.png"));
140 zoomOutButton = new JButton(icon);
141 } catch (Exception e) {
142 zoomOutButton = new JButton("-");
143 zoomOutButton.setFont(new Font("sansserif", Font.BOLD, 9));
144 zoomOutButton.setMargin(new Insets(0, 0, 0, 0));
145 }
146 zoomOutButton.setBounds(8 + size, 155, size, size);
147 zoomOutButton.addActionListener(new ActionListener() {
148
149 public void actionPerformed(ActionEvent e) {
150 zoomOut();
151 }
152 });
153 add(zoomOutButton);
154 }
155
156 /**
157 * Changes the map pane so that it is centered on the specified coordinate
158 * at the given zoom level.
159 *
160 * @param lat
161 * latitude of the specified coordinate
162 * @param lon
163 * longitude of the specified coordinate
164 * @param zoom
165 * {@link #MIN_ZOOM} <= zoom level <= {@link #MAX_ZOOM}
166 */
167 public void setDisplayPositionByLatLon(double lat, double lon, int zoom) {
168 setDisplayPositionByLatLon(new Point(getWidth() / 2, getHeight() / 2), lat, lon, zoom);
169 }
170
171 /**
172 * Changes the map pane so that the specified coordinate at the given zoom
173 * level is displayed on the map at the screen coordinate
174 * <code>mapPoint</code>.
175 *
176 * @param mapPoint
177 * point on the map denoted in pixels where the coordinate should
178 * be set
179 * @param lat
180 * latitude of the specified coordinate
181 * @param lon
182 * longitude of the specified coordinate
183 * @param zoom
184 * {@link #MIN_ZOOM} <= zoom level <=
185 * {@link TileSource#getMaxZoom()}
186 */
187 public void setDisplayPositionByLatLon(Point mapPoint, double lat, double lon, int zoom) {
188 int x = OsmMercator.LonToX(lon, zoom);
189 int y = OsmMercator.LatToY(lat, zoom);
190 setDisplayPosition(mapPoint, x, y, zoom);
191 }
192
193 public void setDisplayPosition(int x, int y, int zoom) {
194 setDisplayPosition(new Point(getWidth() / 2, getHeight() / 2), x, y, zoom);
195 }
196
197 public void setDisplayPosition(Point mapPoint, int x, int y, int zoom) {
198 if (zoom > tileController.getTileSource().getMaxZoom() || zoom < MIN_ZOOM)
199 return;
200
201 // Get the plain tile number
202 Point p = new Point();
203 p.x = x - mapPoint.x + getWidth() / 2;
204 p.y = y - mapPoint.y + getHeight() / 2;
205 center = p;
206 setIgnoreRepaint(true);
207 try {
208 int oldZoom = this.zoom;
209 this.zoom = zoom;
210 if (oldZoom != zoom)
211 zoomChanged(oldZoom);
212 if (zoomSlider.getValue() != zoom)
213 zoomSlider.setValue(zoom);
214 } finally {
215 setIgnoreRepaint(false);
216 repaint();
217 }
218 }
219
220 /**
221 * Sets the displayed map pane and zoom level so that all map markers are
222 * visible.
223 */
224 public void setDisplayToFitMapMarkers() {
225 if (mapMarkerList == null || mapMarkerList.size() == 0)
226 return;
227 int x_min = Integer.MAX_VALUE;
228 int y_min = Integer.MAX_VALUE;
229 int x_max = Integer.MIN_VALUE;
230 int y_max = Integer.MIN_VALUE;
231 int mapZoomMax = tileController.getTileSource().getMaxZoom();
232 for (MapMarker marker : mapMarkerList) {
233 int x = OsmMercator.LonToX(marker.getLon(), mapZoomMax);
234 int y = OsmMercator.LatToY(marker.getLat(), mapZoomMax);
235 x_max = Math.max(x_max, x);
236 y_max = Math.max(y_max, y);
237 x_min = Math.min(x_min, x);
238 y_min = Math.min(y_min, y);
239 }
240 int height = Math.max(0, getHeight());
241 int width = Math.max(0, getWidth());
242 // System.out.println(x_min + " < x < " + x_max);
243 // System.out.println(y_min + " < y < " + y_max);
244 // System.out.println("tiles: " + width + " " + height);
245 int newZoom = mapZoomMax;
246 int x = x_max - x_min;
247 int y = y_max - y_min;
248 while (x > width || y > height) {
249 // System.out.println("zoom: " + zoom + " -> " + x + " " + y);
250 newZoom--;
251 x >>= 1;
252 y >>= 1;
253 }
254 x = x_min + (x_max - x_min) / 2;
255 y = y_min + (y_max - y_min) / 2;
256 int z = 1 << (mapZoomMax - newZoom);
257 x /= z;
258 y /= z;
259 setDisplayPosition(x, y, newZoom);
260 }
261
262 /**
263 * Sets the displayed map pane and zoom level so that all map markers are
264 * visible.
265 */
266 public void setDisplayToFitMapRectangle() {
267 if (mapRectangleList == null || mapRectangleList.size() == 0) {
268 return;
269 }
270 int x_min = Integer.MAX_VALUE;
271 int y_min = Integer.MAX_VALUE;
272 int x_max = Integer.MIN_VALUE;
273 int y_max = Integer.MIN_VALUE;
274 int mapZoomMax = tileController.getTileSource().getMaxZoom();
275 for (MapRectangle rectangle : mapRectangleList) {
276 x_max = Math.max(x_max, OsmMercator.LonToX(rectangle.getBottomRight().getLon(), mapZoomMax));
277 y_max = Math.max(y_max, OsmMercator.LatToY(rectangle.getTopLeft().getLat(), mapZoomMax));
278 x_min = Math.min(x_min, OsmMercator.LonToX(rectangle.getTopLeft().getLon(), mapZoomMax));
279 y_min = Math.min(y_min, OsmMercator.LatToY(rectangle.getBottomRight().getLat(), mapZoomMax));
280 }
281 int height = Math.max(0, getHeight());
282 int width = Math.max(0, getWidth());
283 // System.out.println(x_min + " < x < " + x_max);
284 // System.out.println(y_min + " < y < " + y_max);
285 // System.out.println("tiles: " + width + " " + height);
286 int newZoom = mapZoomMax;
287 int x = x_max - x_min;
288 int y = y_max - y_min;
289 while (x > width || y > height) {
290 // System.out.println("zoom: " + zoom + " -> " + x + " " + y);
291 newZoom--;
292 x >>= 1;
293 y >>= 1;
294 }
295 x = x_min + (x_max - x_min) / 2;
296 y = y_min + (y_max - y_min) / 2;
297 int z = 1 << (mapZoomMax - newZoom);
298 x /= z;
299 y /= z;
300 setDisplayPosition(x, y, newZoom);
301 }
302
303 /**
304 * Calculates the latitude/longitude coordinate of the center of the
305 * currently displayed map area.
306 *
307 * @return latitude / longitude
308 */
309 public Coordinate getPosition() {
310 double lon = OsmMercator.XToLon(center.x, zoom);
311 double lat = OsmMercator.YToLat(center.y, zoom);
312 return new Coordinate(lat, lon);
313 }
314
315 /**
316 * Converts the relative pixel coordinate (regarding the top left corner of
317 * the displayed map) into a latitude / longitude coordinate
318 *
319 * @param mapPoint
320 * relative pixel coordinate regarding the top left corner of the
321 * displayed map
322 * @return latitude / longitude
323 */
324 public Coordinate getPosition(Point mapPoint) {
325 return getPosition(mapPoint.x, mapPoint.y);
326 }
327
328 /**
329 * Converts the relative pixel coordinate (regarding the top left corner of
330 * the displayed map) into a latitude / longitude coordinate
331 *
332 * @param mapPointX
333 * @param mapPointY
334 * @return
335 */
336 public Coordinate getPosition(int mapPointX, int mapPointY) {
337 int x = center.x + mapPointX - getWidth() / 2;
338 int y = center.y + mapPointY - getHeight() / 2;
339 double lon = OsmMercator.XToLon(x, zoom);
340 double lat = OsmMercator.YToLat(y, zoom);
341 return new Coordinate(lat, lon);
342 }
343
344 /**
345 * Calculates the position on the map of a given coordinate
346 *
347 * @param lat
348 * @param lon
349 * @param checkOutside
350 * @return point on the map or <code>null</code> if the point is not visible
351 * and checkOutside set to <code>true</code>
352 */
353 public Point getMapPosition(double lat, double lon, boolean checkOutside) {
354 int x = OsmMercator.LonToX(lon, zoom);
355 int y = OsmMercator.LatToY(lat, zoom);
356 x -= center.x - getWidth() / 2;
357 y -= center.y - getHeight() / 2;
358 if (checkOutside) {
359 if (x < 0 || y < 0 || x > getWidth() || y > getHeight()) {
360 return null;
361 }
362 }
363 return new Point(x, y);
364 }
365
366 /**
367 * Calculates the position on the map of a given coordinate
368 *
369 * @param lat
370 * @param lon
371 * @return point on the map or <code>null</code> if the point is not visible
372 */
373 public Point getMapPosition(double lat, double lon) {
374 return getMapPosition(lat, lon, true);
375 }
376
377 /**
378 * Calculates the position on the map of a given coordinate
379 *
380 * @param coord
381 * @return point on the map or <code>null</code> if the point is not visible
382 */
383 public Point getMapPosition(Coordinate coord) {
384 if (coord != null) {
385 return getMapPosition(coord.getLat(), coord.getLon());
386 } else {
387 return null;
388 }
389 }
390
391 /**
392 * Calculates the position on the map of a given coordinate
393 *
394 * @param coord
395 * @return point on the map or <code>null</code> if the point is not visible
396 * and checkOutside set to <code>true</code>
397 */
398 public Point getMapPosition(Coordinate coord, boolean checkOutside) {
399 if (coord != null) {
400 return getMapPosition(coord.getLat(), coord.getLon(), checkOutside);
401 } else {
402 return null;
403 }
404 }
405
406 @Override
407 protected void paintComponent(Graphics g) {
408 super.paintComponent(g);
409
410 int iMove = 0;
411
412 int tilex = center.x / Tile.SIZE;
413 int tiley = center.y / Tile.SIZE;
414 int off_x = (center.x % Tile.SIZE);
415 int off_y = (center.y % Tile.SIZE);
416
417 int w2 = getWidth() / 2;
418 int h2 = getHeight() / 2;
419 int posx = w2 - off_x;
420 int posy = h2 - off_y;
421
422 int diff_left = off_x;
423 int diff_right = Tile.SIZE - off_x;
424 int diff_top = off_y;
425 int diff_bottom = Tile.SIZE - off_y;
426
427 boolean start_left = diff_left < diff_right;
428 boolean start_top = diff_top < diff_bottom;
429
430 if (start_top) {
431 if (start_left)
432 iMove = 2;
433 else
434 iMove = 3;
435 } else {
436 if (start_left)
437 iMove = 1;
438 else
439 iMove = 0;
440 } // calculate the visibility borders
441 int x_min = -Tile.SIZE;
442 int y_min = -Tile.SIZE;
443 int x_max = getWidth();
444 int y_max = getHeight();
445
446 // paint the tiles in a spiral, starting from center of the map
447 boolean painted = true;
448 int x = 0;
449 while (painted) {
450 painted = false;
451 for (int i = 0; i < 4; i++) {
452 if (i % 2 == 0)
453 x++;
454 for (int j = 0; j < x; j++) {
455 if (x_min <= posx && posx <= x_max && y_min <= posy && posy <= y_max) {
456 // tile is visible
457 Tile tile = tileController.getTile(tilex, tiley, zoom);
458 if (tile != null) {
459 painted = true;
460 tile.paint(g, posx, posy);
461 if (tileGridVisible)
462 g.drawRect(posx, posy, Tile.SIZE, Tile.SIZE);
463 }
464 }
465 Point p = move[iMove];
466 posx += p.x * Tile.SIZE;
467 posy += p.y * Tile.SIZE;
468 tilex += p.x;
469 tiley += p.y;
470 }
471 iMove = (iMove + 1) % move.length;
472 }
473 }
474 // outer border of the map
475 int mapSize = Tile.SIZE << zoom;
476 g.drawRect(w2 - center.x, h2 - center.y, mapSize, mapSize);
477
478 // g.drawString("Tiles in cache: " + tileCache.getTileCount(), 50, 20);
479
480 if (mapRectanglesVisible && mapRectangleList != null) {
481 for (MapRectangle rectangle : mapRectangleList) {
482 Coordinate topLeft = rectangle.getTopLeft();
483 Coordinate bottomRight = rectangle.getBottomRight();
484 if (topLeft != null && bottomRight != null) {
485 Point pTopLeft = getMapPosition(topLeft.getLat(), topLeft.getLon(), false);
486 Point pBottomRight = getMapPosition(bottomRight.getLat(), bottomRight.getLon(), false);
487 if (pTopLeft != null && pBottomRight != null) {
488 rectangle.paint(g, pTopLeft, pBottomRight);
489 }
490 }
491 }
492 }
493
494 if (mapMarkersVisible && mapMarkerList != null) {
495 for (MapMarker marker : mapMarkerList) {
496 Point p = getMapPosition(marker.getLat(), marker.getLon());
497 if (p != null) {
498 marker.paint(g, p);
499 }
500 }
501 }
502 }
503
504 /**
505 * Moves the visible map pane.
506 *
507 * @param x
508 * horizontal movement in pixel.
509 * @param y
510 * vertical movement in pixel
511 */
512 public void moveMap(int x, int y) {
513 center.x += x;
514 center.y += y;
515 repaint();
516 }
517
518 /**
519 * @return the current zoom level
520 */
521 public int getZoom() {
522 return zoom;
523 }
524
525 /**
526 * Increases the current zoom level by one
527 */
528 public void zoomIn() {
529 setZoom(zoom + 1);
530 }
531
532 /**
533 * Increases the current zoom level by one
534 */
535 public void zoomIn(Point mapPoint) {
536 setZoom(zoom + 1, mapPoint);
537 }
538
539 /**
540 * Decreases the current zoom level by one
541 */
542 public void zoomOut() {
543 setZoom(zoom - 1);
544 }
545
546 /**
547 * Decreases the current zoom level by one
548 */
549 public void zoomOut(Point mapPoint) {
550 setZoom(zoom - 1, mapPoint);
551 }
552
553 public void setZoom(int zoom, Point mapPoint) {
554 if (zoom > tileController.getTileSource().getMaxZoom() || zoom < tileController.getTileSource().getMinZoom()
555 || zoom == this.zoom)
556 return;
557 Coordinate zoomPos = getPosition(mapPoint);
558 tileController.cancelOutstandingJobs(); // Clearing outstanding load
559 // requests
560 setDisplayPositionByLatLon(mapPoint, zoomPos.getLat(), zoomPos.getLon(), zoom);
561 }
562
563 public void setZoom(int zoom) {
564 setZoom(zoom, new Point(getWidth() / 2, getHeight() / 2));
565 }
566
567 /**
568 * Every time the zoom level changes this method is called. Override it in
569 * derived implementations for adapting zoom dependent values. The new zoom
570 * level can be obtained via {@link #getZoom()}.
571 *
572 * @param oldZoom
573 * the previous zoom level
574 */
575 protected void zoomChanged(int oldZoom) {
576 zoomSlider.setToolTipText("Zoom level " + zoom);
577 zoomInButton.setToolTipText("Zoom to level " + (zoom + 1));
578 zoomOutButton.setToolTipText("Zoom to level " + (zoom - 1));
579 zoomOutButton.setEnabled(zoom > tileController.getTileSource().getMinZoom());
580 zoomInButton.setEnabled(zoom < tileController.getTileSource().getMaxZoom());
581 }
582
583 public boolean isTileGridVisible() {
584 return tileGridVisible;
585 }
586
587 public void setTileGridVisible(boolean tileGridVisible) {
588 this.tileGridVisible = tileGridVisible;
589 repaint();
590 }
591
592 public boolean getMapMarkersVisible() {
593 return mapMarkersVisible;
594 }
595
596 /**
597 * Enables or disables painting of the {@link MapMarker}
598 *
599 * @param mapMarkersVisible
600 * @see #addMapMarker(MapMarker)
601 * @see #getMapMarkerList()
602 */
603 public void setMapMarkerVisible(boolean mapMarkersVisible) {
604 this.mapMarkersVisible = mapMarkersVisible;
605 repaint();
606 }
607
608 public void setMapMarkerList(List<MapMarker> mapMarkerList) {
609 this.mapMarkerList = mapMarkerList;
610 repaint();
611 }
612
613 public List<MapMarker> getMapMarkerList() {
614 return mapMarkerList;
615 }
616
617 public void setMapRectangleList(List<MapRectangle> mapRectangleList) {
618 this.mapRectangleList = mapRectangleList;
619 repaint();
620 }
621
622 public List<MapRectangle> getMapRectangleList() {
623 return mapRectangleList;
624 }
625
626 public void addMapMarker(MapMarker marker) {
627 mapMarkerList.add(marker);
628 repaint();
629 }
630
631 public void addMapRectangle(MapRectangle rectangle) {
632 mapRectangleList.add(rectangle);
633 repaint();
634 }
635
636 public void removeMapRectangle(MapRectangle rectangle) {
637 mapRectangleList.remove(rectangle);
638 repaint();
639 }
640
641 public void setZoomContolsVisible(boolean visible) {
642 zoomSlider.setVisible(visible);
643 zoomInButton.setVisible(visible);
644 zoomOutButton.setVisible(visible);
645 }
646
647 public boolean getZoomContolsVisible() {
648 return zoomSlider.isVisible();
649 }
650
651 public void setTileSource(TileSource tileSource) {
652 if (tileSource.getMaxZoom() > MAX_ZOOM)
653 throw new RuntimeException("Maximum zoom level too high");
654 if (tileSource.getMinZoom() < MIN_ZOOM)
655 throw new RuntimeException("Minumim zoom level too low");
656 tileController.setTileSource(tileSource);
657 zoomSlider.setMinimum(tileSource.getMinZoom());
658 zoomSlider.setMaximum(tileSource.getMaxZoom());
659 tileController.cancelOutstandingJobs();
660 if (zoom > tileSource.getMaxZoom())
661 setZoom(tileSource.getMaxZoom());
662 repaint();
663 }
664
665 public void tileLoadingFinished(Tile tile, boolean success) {
666 repaint();
667 }
668
669 public boolean isMapRectanglesVisible() {
670 return mapRectanglesVisible;
671 }
672
673 /**
674 * Enables or disables painting of the {@link MapRectangle}
675 *
676 * @param mapMarkersVisible
677 * @see #addMapRectangle(MapRectangle)
678 * @see #getMapRectangleList()
679 */
680 public void setMapRectanglesVisible(boolean mapRectanglesVisible) {
681 this.mapRectanglesVisible = mapRectanglesVisible;
682 repaint();
683 }
684
685 /*
686 * (non-Javadoc)
687 *
688 * @see
689 * org.openstreetmap.gui.jmapviewer.interfaces.TileLoaderListener#getTileCache
690 * ()
691 */
692 public TileCache getTileCache() {
693 return tileController.getTileCache();
694 }
695
696 public void setTileLoader(TileLoader loader) {
697 tileController.setTileLoader(loader);
698 }
699}
Note: See TracBrowser for help on using the repository browser.