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