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