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