001package org.openstreetmap.gui.jmapviewer; 002 003//License: GPL. Copyright 2008 by Jan Peter Stotz 004 005import java.awt.Dimension; 006import java.awt.Font; 007import java.awt.Graphics; 008import java.awt.Insets; 009import java.awt.Point; 010import java.awt.event.ActionEvent; 011import java.awt.event.ActionListener; 012import java.awt.event.MouseEvent; 013import java.util.LinkedList; 014import java.util.List; 015 016import javax.swing.ImageIcon; 017import javax.swing.JButton; 018import javax.swing.JPanel; 019import javax.swing.JSlider; 020import javax.swing.event.ChangeEvent; 021import javax.swing.event.ChangeListener; 022import javax.swing.event.EventListenerList; 023 024import org.openstreetmap.gui.jmapviewer.events.JMVCommandEvent; 025import org.openstreetmap.gui.jmapviewer.events.JMVCommandEvent.COMMAND; 026import org.openstreetmap.gui.jmapviewer.interfaces.ICoordinate; 027import org.openstreetmap.gui.jmapviewer.interfaces.JMapViewerEventListener; 028import org.openstreetmap.gui.jmapviewer.interfaces.MapMarker; 029import org.openstreetmap.gui.jmapviewer.interfaces.MapPolygon; 030import org.openstreetmap.gui.jmapviewer.interfaces.MapRectangle; 031import org.openstreetmap.gui.jmapviewer.interfaces.TileCache; 032import org.openstreetmap.gui.jmapviewer.interfaces.TileLoader; 033import org.openstreetmap.gui.jmapviewer.interfaces.TileLoaderListener; 034import org.openstreetmap.gui.jmapviewer.interfaces.TileSource; 035import org.openstreetmap.gui.jmapviewer.tilesources.OsmTileSource; 036 037/** 038 * 039 * Provides a simple panel that displays pre-rendered map tiles loaded from the 040 * OpenStreetMap project. 041 * 042 * @author Jan Peter Stotz 043 * 044 */ 045public class JMapViewer extends JPanel implements TileLoaderListener { 046 047 private static final long serialVersionUID = 1L; 048 049 /** 050 * Vectors for clock-wise tile painting 051 */ 052 protected static final Point[] move = { new Point(1, 0), new Point(0, 1), new Point(-1, 0), new Point(0, -1) }; 053 054 public static final int MAX_ZOOM = 22; 055 public static final int MIN_ZOOM = 0; 056 057 protected List<MapMarker> mapMarkerList; 058 protected List<MapRectangle> mapRectangleList; 059 protected List<MapPolygon> mapPolygonList; 060 061 protected boolean mapMarkersVisible; 062 protected boolean mapRectanglesVisible; 063 protected boolean mapPolygonsVisible; 064 065 protected boolean tileGridVisible; 066 protected boolean scrollWrapEnabled; 067 068 protected TileController tileController; 069 070 /** 071 * x- and y-position of the center of this map-panel on the world map 072 * denoted in screen pixel regarding the current zoom level. 073 */ 074 protected Point center; 075 076 /** 077 * Current zoom level 078 */ 079 protected int zoom; 080 081 protected JSlider zoomSlider; 082 protected JButton zoomInButton; 083 protected JButton zoomOutButton; 084 085 public static enum ZOOM_BUTTON_STYLE { 086 HORIZONTAL, 087 VERTICAL 088 } 089 protected ZOOM_BUTTON_STYLE zoomButtonStyle; 090 091 private TileSource tileSource; 092 093 protected AttributionSupport attribution = new AttributionSupport(); 094 095 /** 096 * Creates a standard {@link JMapViewer} instance that can be controlled via 097 * mouse: hold right mouse button for moving, double click left mouse button 098 * or use mouse wheel for zooming. Loaded tiles are stored the 099 * {@link MemoryTileCache} and the tile loader uses 4 parallel threads for 100 * retrieving the tiles. 101 */ 102 public JMapViewer() { 103 this(new MemoryTileCache(), 8); 104 new DefaultMapController(this); 105 } 106 107 public JMapViewer(TileCache tileCache, int downloadThreadCount) { 108 super(); 109 JobDispatcher.setMaxWorkers(downloadThreadCount); 110 tileSource = new OsmTileSource.Mapnik(); 111 tileController = new TileController(tileSource, tileCache, this); 112 mapMarkerList = new LinkedList<MapMarker>(); 113 mapPolygonList = new LinkedList<MapPolygon>(); 114 mapRectangleList = new LinkedList<MapRectangle>(); 115 mapMarkersVisible = true; 116 mapRectanglesVisible = true; 117 mapPolygonsVisible = true; 118 tileGridVisible = false; 119 setLayout(null); 120 initializeZoomSlider(); 121 setMinimumSize(new Dimension(tileSource.getTileSize(), tileSource.getTileSize())); 122 setPreferredSize(new Dimension(400, 400)); 123 setDisplayPositionByLatLon(50, 9, 3); 124 //setToolTipText(""); 125 } 126 127 @Override 128 public String getToolTipText(MouseEvent event) { 129 // Point screenPoint = event.getLocationOnScreen(); 130 // Coordinate c = getPosition(screenPoint); 131 return super.getToolTipText(event); 132 } 133 134 protected void initializeZoomSlider() { 135 zoomSlider = new JSlider(MIN_ZOOM, tileController.getTileSource().getMaxZoom()); 136 zoomSlider.setOrientation(JSlider.VERTICAL); 137 zoomSlider.setBounds(10, 10, 30, 150); 138 zoomSlider.setOpaque(false); 139 zoomSlider.addChangeListener(new ChangeListener() { 140 public void stateChanged(ChangeEvent e) { 141 setZoom(zoomSlider.getValue()); 142 } 143 }); 144 zoomSlider.setFocusable(false); 145 add(zoomSlider); 146 int size = 18; 147 try { 148 ImageIcon icon = new ImageIcon(JMapViewer.class.getResource("images/plus.png")); 149 zoomInButton = new JButton(icon); 150 } catch (Exception e) { 151 zoomInButton = new JButton("+"); 152 zoomInButton.setFont(new Font("sansserif", Font.BOLD, 9)); 153 zoomInButton.setMargin(new Insets(0, 0, 0, 0)); 154 } 155 zoomInButton.setBounds(4, 155, size, size); 156 zoomInButton.addActionListener(new ActionListener() { 157 158 public void actionPerformed(ActionEvent e) { 159 zoomIn(); 160 } 161 }); 162 zoomInButton.setFocusable(false); 163 add(zoomInButton); 164 try { 165 ImageIcon icon = new ImageIcon(JMapViewer.class.getResource("images/minus.png")); 166 zoomOutButton = new JButton(icon); 167 } catch (Exception e) { 168 zoomOutButton = new JButton("-"); 169 zoomOutButton.setFont(new Font("sansserif", Font.BOLD, 9)); 170 zoomOutButton.setMargin(new Insets(0, 0, 0, 0)); 171 } 172 zoomOutButton.setBounds(8 + size, 155, size, size); 173 zoomOutButton.addActionListener(new ActionListener() { 174 175 public void actionPerformed(ActionEvent e) { 176 zoomOut(); 177 } 178 }); 179 zoomOutButton.setFocusable(false); 180 add(zoomOutButton); 181 } 182 183 /** 184 * Changes the map pane so that it is centered on the specified coordinate 185 * at the given zoom level. 186 * 187 * @param lat 188 * latitude of the specified coordinate 189 * @param lon 190 * longitude of the specified coordinate 191 * @param zoom 192 * {@link #MIN_ZOOM} <= zoom level <= {@link #MAX_ZOOM} 193 */ 194 public void setDisplayPositionByLatLon(double lat, double lon, int zoom) { 195 setDisplayPositionByLatLon(new Point(getWidth() / 2, getHeight() / 2), lat, lon, zoom); 196 } 197 198 /** 199 * Changes the map pane so that the specified coordinate at the given zoom 200 * level is displayed on the map at the screen coordinate 201 * <code>mapPoint</code>. 202 * 203 * @param mapPoint 204 * point on the map denoted in pixels where the coordinate should 205 * be set 206 * @param lat 207 * latitude of the specified coordinate 208 * @param lon 209 * longitude of the specified coordinate 210 * @param zoom 211 * {@link #MIN_ZOOM} <= zoom level <= 212 * {@link TileSource#getMaxZoom()} 213 */ 214 public void setDisplayPositionByLatLon(Point mapPoint, double lat, double lon, int zoom) { 215 int x = OsmMercator.LonToX(lon, zoom); 216 int y = OsmMercator.LatToY(lat, zoom); 217 setDisplayPosition(mapPoint, x, y, zoom); 218 } 219 220 public void setDisplayPosition(int x, int y, int zoom) { 221 setDisplayPosition(new Point(getWidth() / 2, getHeight() / 2), x, y, zoom); 222 } 223 224 public void setDisplayPosition(Point mapPoint, int x, int y, int zoom) { 225 if (zoom > tileController.getTileSource().getMaxZoom() || zoom < MIN_ZOOM) 226 return; 227 228 // Get the plain tile number 229 Point p = new Point(); 230 p.x = x - mapPoint.x + getWidth() / 2; 231 p.y = y - mapPoint.y + getHeight() / 2; 232 center = p; 233 setIgnoreRepaint(true); 234 try { 235 int oldZoom = this.zoom; 236 this.zoom = zoom; 237 if (oldZoom != zoom) { 238 zoomChanged(oldZoom); 239 } 240 if (zoomSlider.getValue() != zoom) { 241 zoomSlider.setValue(zoom); 242 } 243 } finally { 244 setIgnoreRepaint(false); 245 repaint(); 246 } 247 } 248 249 /** 250 * Sets the displayed map pane and zoom level so that all chosen map elements are 251 * visible. 252 */ 253 public void setDisplayToFitMapElements(boolean markers, boolean rectangles, boolean polygons) { 254 int nbElemToCheck = 0; 255 if (markers && mapMarkerList != null) 256 nbElemToCheck += mapMarkerList.size(); 257 if (rectangles && mapRectangleList != null) 258 nbElemToCheck += mapRectangleList.size(); 259 if (polygons && mapPolygonList != null) 260 nbElemToCheck += mapPolygonList.size(); 261 if (nbElemToCheck == 0) 262 return; 263 264 int x_min = Integer.MAX_VALUE; 265 int y_min = Integer.MAX_VALUE; 266 int x_max = Integer.MIN_VALUE; 267 int y_max = Integer.MIN_VALUE; 268 int mapZoomMax = tileController.getTileSource().getMaxZoom(); 269 270 if (markers) { 271 for (MapMarker marker : mapMarkerList) { 272 if(marker.isVisible()){ 273 int x = OsmMercator.LonToX(marker.getLon(), mapZoomMax); 274 int y = OsmMercator.LatToY(marker.getLat(), mapZoomMax); 275 x_max = Math.max(x_max, x); 276 y_max = Math.max(y_max, y); 277 x_min = Math.min(x_min, x); 278 y_min = Math.min(y_min, y); 279 } 280 } 281 } 282 283 if (rectangles) { 284 for (MapRectangle rectangle : mapRectangleList) { 285 if(rectangle.isVisible()){ 286 x_max = Math.max(x_max, OsmMercator.LonToX(rectangle.getBottomRight().getLon(), mapZoomMax)); 287 y_max = Math.max(y_max, OsmMercator.LatToY(rectangle.getTopLeft().getLat(), mapZoomMax)); 288 x_min = Math.min(x_min, OsmMercator.LonToX(rectangle.getTopLeft().getLon(), mapZoomMax)); 289 y_min = Math.min(y_min, OsmMercator.LatToY(rectangle.getBottomRight().getLat(), mapZoomMax)); 290 } 291 } 292 } 293 294 if (polygons) { 295 for (MapPolygon polygon : mapPolygonList) { 296 if(polygon.isVisible()){ 297 for (ICoordinate c : polygon.getPoints()) { 298 int x = OsmMercator.LonToX(c.getLon(), mapZoomMax); 299 int y = OsmMercator.LatToY(c.getLat(), mapZoomMax); 300 x_max = Math.max(x_max, x); 301 y_max = Math.max(y_max, y); 302 x_min = Math.min(x_min, x); 303 y_min = Math.min(y_min, y); 304 } 305 } 306 } 307 } 308 309 int height = Math.max(0, getHeight()); 310 int width = Math.max(0, getWidth()); 311 int newZoom = mapZoomMax; 312 int x = x_max - x_min; 313 int y = y_max - y_min; 314 while (x > width || y > height) { 315 newZoom--; 316 x >>= 1; 317 y >>= 1; 318 } 319 x = x_min + (x_max - x_min) / 2; 320 y = y_min + (y_max - y_min) / 2; 321 int z = 1 << (mapZoomMax - newZoom); 322 x /= z; 323 y /= z; 324 setDisplayPosition(x, y, newZoom); 325 } 326 327 328 /** 329 * Sets the displayed map pane and zoom level so that all map markers are 330 * visible. 331 */ 332 public void setDisplayToFitMapMarkers() { 333 setDisplayToFitMapElements(true, false, false); 334 } 335 336 /** 337 * Sets the displayed map pane and zoom level so that all map rectangles are 338 * visible. 339 */ 340 public void setDisplayToFitMapRectangles() { 341 setDisplayToFitMapElements(false, true, false); 342 } 343 344 /** 345 * Sets the displayed map pane and zoom level so that all map polygons are 346 * visible. 347 */ 348 public void setDisplayToFitMapPolygons() { 349 setDisplayToFitMapElements(false, false, true); 350 } 351 352 /** 353 * @return the center 354 */ 355 public Point getCenter() { 356 return center; 357 } 358 359 /** 360 * @param center the center to set 361 */ 362 public void setCenter(Point center) { 363 this.center = center; 364 } 365 366 /** 367 * Calculates the latitude/longitude coordinate of the center of the 368 * currently displayed map area. 369 * 370 * @return latitude / longitude 371 */ 372 public Coordinate getPosition() { 373 double lon = OsmMercator.XToLon(center.x, zoom); 374 double lat = OsmMercator.YToLat(center.y, zoom); 375 return new Coordinate(lat, lon); 376 } 377 378 /** 379 * Converts the relative pixel coordinate (regarding the top left corner of 380 * the displayed map) into a latitude / longitude coordinate 381 * 382 * @param mapPoint 383 * relative pixel coordinate regarding the top left corner of the 384 * displayed map 385 * @return latitude / longitude 386 */ 387 public Coordinate getPosition(Point mapPoint) { 388 return getPosition(mapPoint.x, mapPoint.y); 389 } 390 391 /** 392 * Converts the relative pixel coordinate (regarding the top left corner of 393 * the displayed map) into a latitude / longitude coordinate 394 * 395 * @param mapPointX 396 * @param mapPointY 397 * @return latitude / longitude 398 */ 399 public Coordinate getPosition(int mapPointX, int mapPointY) { 400 int x = center.x + mapPointX - getWidth() / 2; 401 int y = center.y + mapPointY - getHeight() / 2; 402 double lon = OsmMercator.XToLon(x, zoom); 403 double lat = OsmMercator.YToLat(y, zoom); 404 return new Coordinate(lat, lon); 405 } 406 407 /** 408 * Calculates the position on the map of a given coordinate 409 * 410 * @param lat 411 * @param lon 412 * @param checkOutside 413 * @return point on the map or <code>null</code> if the point is not visible 414 * and checkOutside set to <code>true</code> 415 */ 416 public Point getMapPosition(double lat, double lon, boolean checkOutside) { 417 int x = OsmMercator.LonToX(lon, zoom); 418 int y = OsmMercator.LatToY(lat, zoom); 419 x -= center.x - getWidth() / 2; 420 y -= center.y - getHeight() / 2; 421 if (checkOutside) { 422 if (x < 0 || y < 0 || x > getWidth() || y > getHeight()) 423 return null; 424 } 425 return new Point(x, y); 426 } 427 428 /** 429 * Calculates the position on the map of a given coordinate 430 * 431 * @param lat Latitude 432 * @param offset Offset respect Latitude 433 * @param checkOutside 434 * @return Integer the radius in pixels 435 */ 436 public Integer getLatOffset(double lat, double offset, boolean checkOutside) { 437 int y = OsmMercator.LatToY(lat+offset, zoom); 438 y -= center.y - getHeight() / 2; 439 if (checkOutside) { 440 if (y < 0 || y > getHeight()) 441 return null; 442 } 443 return y; 444 } 445 446 /** 447 * Calculates the position on the map of a given coordinate 448 * 449 * @param lat 450 * @param lon 451 * @return point on the map or <code>null</code> if the point is not visible 452 */ 453 public Point getMapPosition(double lat, double lon) { 454 return getMapPosition(lat, lon, true); 455 } 456 457 /** 458 * Calculates the position on the map of a given coordinate 459 * 460 * @param marker MapMarker object that define the x,y coordinate 461 * @return Integer the radius in pixels 462 */ 463 public Integer getRadius(MapMarker marker, Point p) { 464 if(marker.getMarkerStyle() == MapMarker.STYLE.FIXED) 465 return (int)marker.getRadius(); 466 else if(p!=null){ 467 Integer radius = getLatOffset(marker.getLat(), marker.getRadius(), false); 468 radius = radius==null?null:p.y-radius.intValue(); 469 return radius; 470 }else return null; 471 } 472 473 /** 474 * Calculates the position on the map of a given coordinate 475 * 476 * @param coord 477 * @return point on the map or <code>null</code> if the point is not visible 478 */ 479 public Point getMapPosition(Coordinate coord) { 480 if (coord != null) 481 return getMapPosition(coord.getLat(), coord.getLon()); 482 else 483 return null; 484 } 485 486 /** 487 * Calculates the position on the map of a given coordinate 488 * 489 * @param coord 490 * @return point on the map or <code>null</code> if the point is not visible 491 * and checkOutside set to <code>true</code> 492 */ 493 public Point getMapPosition(ICoordinate coord, boolean checkOutside) { 494 if (coord != null) 495 return getMapPosition(coord.getLat(), coord.getLon(), checkOutside); 496 else 497 return null; 498 } 499 500 /** 501 * Gets the meter per pixel. 502 * 503 * @return the meter per pixel 504 * @author Jason Huntley 505 */ 506 public double getMeterPerPixel() { 507 Point origin=new Point(5,5); 508 Point center=new Point(getWidth()/2, getHeight()/2); 509 510 double pDistance=center.distance(origin); 511 512 Coordinate originCoord=getPosition(origin); 513 Coordinate centerCoord=getPosition(center); 514 515 double mDistance=OsmMercator.getDistance(originCoord.getLat(), originCoord.getLon(), 516 centerCoord.getLat(), centerCoord.getLon()); 517 518 return mDistance/pDistance; 519 } 520 521 @Override 522 protected void paintComponent(Graphics g) { 523 super.paintComponent(g); 524 525 int iMove = 0; 526 527 int tilesize = tileSource.getTileSize(); 528 int tilex = center.x / tilesize; 529 int tiley = center.y / tilesize; 530 int off_x = (center.x % tilesize); 531 int off_y = (center.y % tilesize); 532 533 int w2 = getWidth() / 2; 534 int h2 = getHeight() / 2; 535 int posx = w2 - off_x; 536 int posy = h2 - off_y; 537 538 int diff_left = off_x; 539 int diff_right = tilesize - off_x; 540 int diff_top = off_y; 541 int diff_bottom = tilesize - off_y; 542 543 boolean start_left = diff_left < diff_right; 544 boolean start_top = diff_top < diff_bottom; 545 546 if (start_top) { 547 if (start_left) { 548 iMove = 2; 549 } else { 550 iMove = 3; 551 } 552 } else { 553 if (start_left) { 554 iMove = 1; 555 } else { 556 iMove = 0; 557 } 558 } // calculate the visibility borders 559 int x_min = -tilesize; 560 int y_min = -tilesize; 561 int x_max = getWidth(); 562 int y_max = getHeight(); 563 564 // calculate the length of the grid (number of squares per edge) 565 int gridLength = 1 << zoom; 566 567 // paint the tiles in a spiral, starting from center of the map 568 boolean painted = true; 569 int x = 0; 570 while (painted) { 571 painted = false; 572 for (int i = 0; i < 4; i++) { 573 if (i % 2 == 0) { 574 x++; 575 } 576 for (int j = 0; j < x; j++) { 577 if (x_min <= posx && posx <= x_max && y_min <= posy && posy <= y_max) { 578 // tile is visible 579 Tile tile; 580 if (scrollWrapEnabled) { 581 // in case tilex is out of bounds, grab the tile to use for wrapping 582 int tilexWrap = (((tilex % gridLength) + gridLength) % gridLength); 583 tile = tileController.getTile(tilexWrap, tiley, zoom); 584 } else { 585 tile = tileController.getTile(tilex, tiley, zoom); 586 } 587 if (tile != null) { 588 tile.paint(g, posx, posy); 589 if (tileGridVisible) { 590 g.drawRect(posx, posy, tilesize, tilesize); 591 } 592 } 593 painted = true; 594 } 595 Point p = move[iMove]; 596 posx += p.x * tilesize; 597 posy += p.y * tilesize; 598 tilex += p.x; 599 tiley += p.y; 600 } 601 iMove = (iMove + 1) % move.length; 602 } 603 } 604 // outer border of the map 605 int mapSize = tilesize << zoom; 606 if (scrollWrapEnabled) { 607 g.drawLine(0, h2 - center.y, getWidth(), h2 - center.y); 608 g.drawLine(0, h2 - center.y + mapSize, getWidth(), h2 - center.y + mapSize); 609 } else { 610 g.drawRect(w2 - center.x, h2 - center.y, mapSize, mapSize); 611 } 612 613 // g.drawString("Tiles in cache: " + tileCache.getTileCount(), 50, 20); 614 615 // keep x-coordinates from growing without bound if scroll-wrap is enabled 616 if (scrollWrapEnabled) { 617 center.x = center.x % mapSize; 618 } 619 620 if (mapPolygonsVisible && mapPolygonList != null) { 621 for (MapPolygon polygon : mapPolygonList) { 622 if(polygon.isVisible()) paintPolygon(g, polygon); 623 } 624 } 625 626 if (mapRectanglesVisible && mapRectangleList != null) { 627 for (MapRectangle rectangle : mapRectangleList) { 628 if(rectangle.isVisible()) paintRectangle(g, rectangle); 629 } 630 } 631 632 if (mapMarkersVisible && mapMarkerList != null) { 633 for (MapMarker marker : mapMarkerList) { 634 if(marker.isVisible())paintMarker(g, marker); 635 } 636 } 637 638 attribution.paintAttribution(g, getWidth(), getHeight(), getPosition(0, 0), getPosition(getWidth(), getHeight()), zoom, this); 639 } 640 641 /** 642 * Paint a single marker. 643 */ 644 protected void paintMarker(Graphics g, MapMarker marker) { 645 Point p = getMapPosition(marker.getLat(), marker.getLon(), marker.getMarkerStyle()==MapMarker.STYLE.FIXED); 646 Integer radius = getRadius(marker, p); 647 if (scrollWrapEnabled) { 648 int tilesize = tileSource.getTileSize(); 649 int mapSize = tilesize << zoom; 650 if (p == null) { 651 p = getMapPosition(marker.getLat(), marker.getLon(), false); 652 radius = getRadius(marker, p); 653 } 654 marker.paint(g, p, radius); 655 int xSave = p.x; 656 int xWrap = xSave; 657 // overscan of 15 allows up to 30-pixel markers to gracefully scroll off the edge of the panel 658 while ((xWrap -= mapSize) >= -15) { 659 p.x = xWrap; 660 marker.paint(g, p, radius); 661 } 662 xWrap = xSave; 663 while ((xWrap += mapSize) <= getWidth() + 15) { 664 p.x = xWrap; 665 marker.paint(g, p, radius); 666 } 667 } else { 668 if (p != null) { 669 marker.paint(g, p, radius); 670 } 671 } 672 } 673 674 /** 675 * Paint a single rectangle. 676 */ 677 protected void paintRectangle(Graphics g, MapRectangle rectangle) { 678 Coordinate topLeft = rectangle.getTopLeft(); 679 Coordinate bottomRight = rectangle.getBottomRight(); 680 if (topLeft != null && bottomRight != null) { 681 Point pTopLeft = getMapPosition(topLeft, false); 682 Point pBottomRight = getMapPosition(bottomRight, false); 683 if (pTopLeft != null && pBottomRight != null) { 684 rectangle.paint(g, pTopLeft, pBottomRight); 685 if (scrollWrapEnabled) { 686 int tilesize = tileSource.getTileSize(); 687 int mapSize = tilesize << zoom; 688 int xTopLeftSave = pTopLeft.x; 689 int xTopLeftWrap = xTopLeftSave; 690 int xBottomRightSave = pBottomRight.x; 691 int xBottomRightWrap = xBottomRightSave; 692 while ((xBottomRightWrap -= mapSize) >= 0) { 693 xTopLeftWrap -= mapSize; 694 pTopLeft.x = xTopLeftWrap; 695 pBottomRight.x = xBottomRightWrap; 696 rectangle.paint(g, pTopLeft, pBottomRight); 697 } 698 xTopLeftWrap = xTopLeftSave; 699 xBottomRightWrap = xBottomRightSave; 700 while ((xTopLeftWrap += mapSize) <= getWidth()) { 701 xBottomRightWrap += mapSize; 702 pTopLeft.x = xTopLeftWrap; 703 pBottomRight.x = xBottomRightWrap; 704 rectangle.paint(g, pTopLeft, pBottomRight); 705 } 706 707 } 708 } 709 } 710 } 711 712 /** 713 * Paint a single polygon. 714 */ 715 protected void paintPolygon(Graphics g, MapPolygon polygon) { 716 List<? extends ICoordinate> coords = polygon.getPoints(); 717 if (coords != null && coords.size() >= 3) { 718 List<Point> points = new LinkedList<Point>(); 719 for (ICoordinate c : coords) { 720 Point p = getMapPosition(c, false); 721 if (p == null) { 722 return; 723 } 724 points.add(p); 725 } 726 polygon.paint(g, points); 727 if (scrollWrapEnabled) { 728 int tilesize = tileSource.getTileSize(); 729 int mapSize = tilesize << zoom; 730 List<Point> pointsWrapped = new LinkedList<Point>(points); 731 boolean keepWrapping = true; 732 while (keepWrapping) { 733 for (Point p : pointsWrapped) { 734 p.x -= mapSize; 735 if (p.x < 0) { 736 keepWrapping = false; 737 } 738 } 739 polygon.paint(g, pointsWrapped); 740 } 741 pointsWrapped = new LinkedList<Point>(points); 742 keepWrapping = true; 743 while (keepWrapping) { 744 for (Point p : pointsWrapped) { 745 p.x += mapSize; 746 if (p.x > getWidth()) { 747 keepWrapping = false; 748 } 749 } 750 polygon.paint(g, pointsWrapped); 751 } 752 } 753 } 754 } 755 756 /** 757 * Moves the visible map pane. 758 * 759 * @param x 760 * horizontal movement in pixel. 761 * @param y 762 * vertical movement in pixel 763 */ 764 public void moveMap(int x, int y) { 765 tileController.cancelOutstandingJobs(); // Clear outstanding load 766 center.x += x; 767 center.y += y; 768 repaint(); 769 this.fireJMVEvent(new JMVCommandEvent(COMMAND.MOVE, this)); 770 } 771 772 /** 773 * @return the current zoom level 774 */ 775 public int getZoom() { 776 return zoom; 777 } 778 779 /** 780 * Increases the current zoom level by one 781 */ 782 public void zoomIn() { 783 setZoom(zoom + 1); 784 } 785 786 /** 787 * Increases the current zoom level by one 788 */ 789 public void zoomIn(Point mapPoint) { 790 setZoom(zoom + 1, mapPoint); 791 } 792 793 /** 794 * Decreases the current zoom level by one 795 */ 796 public void zoomOut() { 797 setZoom(zoom - 1); 798 } 799 800 /** 801 * Decreases the current zoom level by one 802 * 803 * @param mapPoint point to choose as center for new zoom level 804 */ 805 public void zoomOut(Point mapPoint) { 806 setZoom(zoom - 1, mapPoint); 807 } 808 809 /** 810 * Set the zoom level and center point for display 811 * 812 * @param zoom new zoom level 813 * @param mapPoint point to choose as center for new zoom level 814 */ 815 public void setZoom(int zoom, Point mapPoint) { 816 if (zoom > tileController.getTileSource().getMaxZoom() || zoom < tileController.getTileSource().getMinZoom() 817 || zoom == this.zoom) 818 return; 819 Coordinate zoomPos = getPosition(mapPoint); 820 tileController.cancelOutstandingJobs(); // Clearing outstanding load 821 // requests 822 setDisplayPositionByLatLon(mapPoint, zoomPos.getLat(), zoomPos.getLon(), zoom); 823 824 this.fireJMVEvent(new JMVCommandEvent(COMMAND.ZOOM, this)); 825 } 826 827 /** 828 * Set the zoom level 829 * 830 * @param zoom new zoom level 831 */ 832 public void setZoom(int zoom) { 833 setZoom(zoom, new Point(getWidth() / 2, getHeight() / 2)); 834 } 835 836 /** 837 * Every time the zoom level changes this method is called. Override it in 838 * derived implementations for adapting zoom dependent values. The new zoom 839 * level can be obtained via {@link #getZoom()}. 840 * 841 * @param oldZoom 842 * the previous zoom level 843 */ 844 protected void zoomChanged(int oldZoom) { 845 zoomSlider.setToolTipText("Zoom level " + zoom); 846 zoomInButton.setToolTipText("Zoom to level " + (zoom + 1)); 847 zoomOutButton.setToolTipText("Zoom to level " + (zoom - 1)); 848 zoomOutButton.setEnabled(zoom > tileController.getTileSource().getMinZoom()); 849 zoomInButton.setEnabled(zoom < tileController.getTileSource().getMaxZoom()); 850 } 851 852 public boolean isTileGridVisible() { 853 return tileGridVisible; 854 } 855 856 public void setTileGridVisible(boolean tileGridVisible) { 857 this.tileGridVisible = tileGridVisible; 858 repaint(); 859 } 860 861 public boolean getMapMarkersVisible() { 862 return mapMarkersVisible; 863 } 864 865 /** 866 * Enables or disables painting of the {@link MapMarker} 867 * 868 * @param mapMarkersVisible 869 * @see #addMapMarker(MapMarker) 870 * @see #getMapMarkerList() 871 */ 872 public void setMapMarkerVisible(boolean mapMarkersVisible) { 873 this.mapMarkersVisible = mapMarkersVisible; 874 repaint(); 875 } 876 877 public void setMapMarkerList(List<MapMarker> mapMarkerList) { 878 this.mapMarkerList = mapMarkerList; 879 repaint(); 880 } 881 882 public List<MapMarker> getMapMarkerList() { 883 return mapMarkerList; 884 } 885 886 public void setMapRectangleList(List<MapRectangle> mapRectangleList) { 887 this.mapRectangleList = mapRectangleList; 888 repaint(); 889 } 890 891 public List<MapRectangle> getMapRectangleList() { 892 return mapRectangleList; 893 } 894 895 public void setMapPolygonList(List<MapPolygon> mapPolygonList) { 896 this.mapPolygonList = mapPolygonList; 897 repaint(); 898 } 899 900 public List<MapPolygon> getMapPolygonList() { 901 return mapPolygonList; 902 } 903 904 public void addMapMarker(MapMarker marker) { 905 mapMarkerList.add(marker); 906 repaint(); 907 } 908 909 public void removeMapMarker(MapMarker marker) { 910 mapMarkerList.remove(marker); 911 repaint(); 912 } 913 914 public void removeAllMapMarkers() { 915 mapMarkerList.clear(); 916 repaint(); 917 } 918 919 public void addMapRectangle(MapRectangle rectangle) { 920 mapRectangleList.add(rectangle); 921 repaint(); 922 } 923 924 public void removeMapRectangle(MapRectangle rectangle) { 925 mapRectangleList.remove(rectangle); 926 repaint(); 927 } 928 929 public void removeAllMapRectangles() { 930 mapRectangleList.clear(); 931 repaint(); 932 } 933 934 public void addMapPolygon(MapPolygon polygon) { 935 mapPolygonList.add(polygon); 936 repaint(); 937 } 938 939 public void removeMapPolygon(MapPolygon polygon) { 940 mapPolygonList.remove(polygon); 941 repaint(); 942 } 943 944 public void removeAllMapPolygons() { 945 mapPolygonList.clear(); 946 repaint(); 947 } 948 949 public void setZoomContolsVisible(boolean visible) { 950 zoomSlider.setVisible(visible); 951 zoomInButton.setVisible(visible); 952 zoomOutButton.setVisible(visible); 953 } 954 955 public boolean getZoomContolsVisible() { 956 return zoomSlider.isVisible(); 957 } 958 959 public void setTileSource(TileSource tileSource) { 960 if (tileSource.getMaxZoom() > MAX_ZOOM) 961 throw new RuntimeException("Maximum zoom level too high"); 962 if (tileSource.getMinZoom() < MIN_ZOOM) 963 throw new RuntimeException("Minumim zoom level too low"); 964 this.tileSource = tileSource; 965 tileController.setTileSource(tileSource); 966 zoomSlider.setMinimum(tileSource.getMinZoom()); 967 zoomSlider.setMaximum(tileSource.getMaxZoom()); 968 tileController.cancelOutstandingJobs(); 969 if (zoom > tileSource.getMaxZoom()) { 970 setZoom(tileSource.getMaxZoom()); 971 } 972 973 attribution.initialize(tileSource); 974 repaint(); 975 } 976 977 public void tileLoadingFinished(Tile tile, boolean success) { 978 repaint(); 979 } 980 981 public boolean isMapRectanglesVisible() { 982 return mapRectanglesVisible; 983 } 984 985 /** 986 * Enables or disables painting of the {@link MapRectangle} 987 * 988 * @param mapRectanglesVisible 989 * @see #addMapRectangle(MapRectangle) 990 * @see #getMapRectangleList() 991 */ 992 public void setMapRectanglesVisible(boolean mapRectanglesVisible) { 993 this.mapRectanglesVisible = mapRectanglesVisible; 994 repaint(); 995 } 996 997 public boolean isMapPolygonsVisible() { 998 return mapPolygonsVisible; 999 } 1000 1001 /** 1002 * Enables or disables painting of the {@link MapPolygon} 1003 * 1004 * @param mapPolygonsVisible 1005 * @see #addMapPolygon(MapPolygon) 1006 * @see #getMapPolygonList() 1007 */ 1008 public void setMapPolygonsVisible(boolean mapPolygonsVisible) { 1009 this.mapPolygonsVisible = mapPolygonsVisible; 1010 repaint(); 1011 } 1012 1013 public boolean isScrollWrapEnabled() { 1014 return scrollWrapEnabled; 1015 } 1016 1017 public void setScrollWrapEnabled(boolean scrollWrapEnabled) { 1018 this.scrollWrapEnabled = scrollWrapEnabled; 1019 repaint(); 1020 } 1021 1022 public ZOOM_BUTTON_STYLE getZoomButtonStyle() { 1023 return zoomButtonStyle; 1024 } 1025 1026 public void setZoomButtonStyle(ZOOM_BUTTON_STYLE style) { 1027 zoomButtonStyle = style; 1028 if (zoomSlider == null || zoomInButton == null || zoomOutButton == null) { 1029 return; 1030 } 1031 switch (style) { 1032 case HORIZONTAL: 1033 zoomSlider.setBounds(10, 10, 30, 150); 1034 zoomInButton.setBounds(4, 155, 18, 18); 1035 zoomOutButton.setBounds(26, 155, 18, 18); 1036 break; 1037 case VERTICAL: 1038 zoomSlider.setBounds(10, 27, 30, 150); 1039 zoomInButton.setBounds(14, 8, 20, 20); 1040 zoomOutButton.setBounds(14, 176, 20, 20); 1041 break; 1042 default: 1043 zoomSlider.setBounds(10, 10, 30, 150); 1044 zoomInButton.setBounds(4, 155, 18, 18); 1045 zoomOutButton.setBounds(26, 155, 18, 18); 1046 break; 1047 } 1048 repaint(); 1049 } 1050 1051 public TileController getTileController() { 1052 return tileController; 1053 } 1054 1055 /** 1056 * Return tile information caching class 1057 * @see TileLoaderListener#getTileCache() 1058 */ 1059 public TileCache getTileCache() { 1060 return tileController.getTileCache(); 1061 } 1062 1063 public void setTileLoader(TileLoader loader) { 1064 tileController.setTileLoader(loader); 1065 } 1066 1067 public AttributionSupport getAttribution() { 1068 return attribution; 1069 } 1070 1071 protected EventListenerList listenerList = new EventListenerList(); 1072 1073 /** 1074 * @param listener listener to set 1075 */ 1076 public void addJMVListener(JMapViewerEventListener listener) { 1077 listenerList.add(JMapViewerEventListener.class, listener); 1078 } 1079 1080 /** 1081 * @param listener listener to remove 1082 */ 1083 public void removeJMVListener(JMapViewerEventListener listener) { 1084 listenerList.remove(JMapViewerEventListener.class, listener); 1085 } 1086 1087 /** 1088 * Send an update to all objects registered with viewer 1089 * 1090 * @param evt event to dispatch 1091 */ 1092 void fireJMVEvent(JMVCommandEvent evt) { 1093 Object[] listeners = listenerList.getListenerList(); 1094 for (int i=0; i<listeners.length; i+=2) { 1095 if (listeners[i]==JMapViewerEventListener.class) { 1096 ((JMapViewerEventListener)listeners[i+1]).processCommand(evt); 1097 } 1098 } 1099 } 1100}