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