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