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