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