Ignore:
Timestamp:
2008-11-07T15:23:05+01:00 (16 years ago)
Author:
stotz
Message:

Patch by r_x and further small enhancements

Location:
applications/viewer/jmapviewer
Files:
1 added
8 edited

Legend:

Unmodified
Added
Removed
  • applications/viewer/jmapviewer

    • Property svn:ignore
      •  

        old new  
         1tiles
        12JMapViewer_Demo.jar
        23JMapViewer_src.jar
  • applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/JMapViewer.java

    r11104 r11783  
    1111import java.awt.event.ActionListener;
    1212import java.awt.geom.Point2D;
    13 import java.awt.image.BufferedImage;
    1413import java.util.LinkedList;
    1514import java.util.List;
    1615
    17 import javax.imageio.ImageIO;
    1816import javax.swing.ImageIcon;
    1917import javax.swing.JButton;
     
    4038public class JMapViewer extends JPanel implements TileLoaderListener {
    4139
    42         private static final long serialVersionUID = 1L;
    43 
    44         /**
    45          * Vectors for clock-wise tile painting
    46          */
    47         protected static final Point[] move = { new Point(1, 0), new Point(0, 1),
    48                         new Point(-1, 0), new Point(0, -1) };
    49 
    50         public static final int MAX_ZOOM = 22;
    51         public static final int MIN_ZOOM = 0;
    52 
    53         protected TileLoader tileLoader;
    54         protected TileCache tileCache;
    55         protected TileSource tileSource;
    56 
    57         protected List<MapMarker> mapMarkerList;
    58         protected boolean mapMarkersVisible;
    59         protected boolean tileGridVisible;
    60 
    61         /**
    62          * x- and y-position of the center of this map-panel on the world map
    63          * denoted in screen pixel regarding the current zoom level.
    64          */
    65         protected Point center;
    66 
    67         /**
    68          * Current zoom level
    69          */
    70         protected int zoom;
    71 
    72         protected JSlider zoomSlider;
    73         protected JButton zoomInButton;
    74         protected JButton zoomOutButton;
    75 
    76         /**
    77          * Hourglass image that is displayed until a map tile has been loaded
    78          */
    79         protected BufferedImage loadingImage;
    80 
    81         JobDispatcher jobDispatcher;
    82 
    83         /**
    84          * Creates a standard {@link JMapViewer} instance that can be controlled via
    85          * mouse: hold right mouse button for moving, double click left mouse button
    86          * or use mouse wheel for zooming. Loaded tiles are stored the
    87          * {@link MemoryTileCache} and the tile loader uses 4 parallel threads for
    88          * retrieving the tiles.
    89          */
    90         public JMapViewer() {
    91                 this(new MemoryTileCache(), 4);
    92                 new DefaultMapController(this);
    93         }
    94 
    95         public JMapViewer(TileCache tileCache, int downloadThreadCount) {
    96                 super();
    97                 tileSource = new OsmTileSource.Mapnik();
    98                 tileLoader = new OsmTileLoader(this);
    99                 this.tileCache = tileCache;
    100                 jobDispatcher = JobDispatcher.getInstance();
    101                 mapMarkerList = new LinkedList<MapMarker>();
    102                 mapMarkersVisible = true;
    103                 tileGridVisible = false;
    104                 setLayout(null);
    105                 initializeZoomSlider();
    106                 setMinimumSize(new Dimension(Tile.SIZE, Tile.SIZE));
    107                 setPreferredSize(new Dimension(400, 400));
    108                 try {
    109                         loadingImage = ImageIO.read(JMapViewer.class
    110                                         .getResourceAsStream("images/hourglass.png"));
    111                 } catch (Exception e1) {
    112                         loadingImage = null;
    113                 }
    114                 setDisplayPositionByLatLon(50, 9, 3);
    115         }
    116 
    117         protected void initializeZoomSlider() {
    118                 zoomSlider = new JSlider(MIN_ZOOM, tileSource.getMaxZoom());
    119                 zoomSlider.setOrientation(JSlider.VERTICAL);
    120                 zoomSlider.setBounds(10, 10, 30, 150);
    121                 zoomSlider.setOpaque(false);
    122                 zoomSlider.addChangeListener(new ChangeListener() {
    123                         public void stateChanged(ChangeEvent e) {
    124                                 setZoom(zoomSlider.getValue());
    125                         }
    126                 });
    127                 add(zoomSlider);
    128                 int size = 18;
    129                 try {
    130                         ImageIcon icon = new ImageIcon(getClass().getResource(
    131                                         "images/plus.png"));
    132                         zoomInButton = new JButton(icon);
    133                 } catch (Exception e) {
    134                         zoomInButton = new JButton("+");
    135                         zoomInButton.setFont(new Font("sansserif", Font.BOLD, 9));
    136                         zoomInButton.setMargin(new Insets(0, 0, 0, 0));
    137                 }
    138                 zoomInButton.setBounds(4, 155, size, size);
    139                 zoomInButton.addActionListener(new ActionListener() {
    140 
    141                         public void actionPerformed(ActionEvent e) {
    142                                 zoomIn();
    143                         }
    144                 });
    145                 add(zoomInButton);
    146                 try {
    147                         ImageIcon icon = new ImageIcon(getClass().getResource(
    148                                         "images/minus.png"));
    149                         zoomOutButton = new JButton(icon);
    150                 } catch (Exception e) {
    151                         zoomOutButton = new JButton("-");
    152                         zoomOutButton.setFont(new Font("sansserif", Font.BOLD, 9));
    153                         zoomOutButton.setMargin(new Insets(0, 0, 0, 0));
    154                 }
    155                 zoomOutButton.setBounds(8 + size, 155, size, size);
    156                 zoomOutButton.addActionListener(new ActionListener() {
    157 
    158                         public void actionPerformed(ActionEvent e) {
    159                                 zoomOut();
    160                         }
    161                 });
    162                 add(zoomOutButton);
    163         }
    164 
    165         /**
    166          * Changes the map pane so that it is centered on the specified coordinate
    167          * at the given zoom level.
    168          *
    169          * @param lat
    170          *            latitude of the specified coordinate
    171          * @param lon
    172          *            longitude of the specified coordinate
    173          * @param zoom
    174          *            {@link #MIN_ZOOM} <= zoom level <= {@link #MAX_ZOOM}
    175          */
    176         public void setDisplayPositionByLatLon(double lat, double lon, int zoom) {
    177                 setDisplayPositionByLatLon(new Point(getWidth() / 2, getHeight() / 2),
    178                                 lat, lon, zoom);
    179         }
    180 
    181         /**
    182          * Changes the map pane so that the specified coordinate at the given zoom
    183          * level is displayed on the map at the screen coordinate
    184          * <code>mapPoint</code>.
    185          *
    186          * @param mapPoint
    187          *            point on the map denoted in pixels where the coordinate should
    188          *            be set
    189          * @param lat
    190          *            latitude of the specified coordinate
    191          * @param lon
    192          *            longitude of the specified coordinate
    193          * @param zoom
    194          *            {@link #MIN_ZOOM} <= zoom level <=
    195          *            {@link TileSource#getMaxZoom()}
    196          */
    197         public void setDisplayPositionByLatLon(Point mapPoint, double lat,
    198                         double lon, int zoom) {
    199                 int x = OsmMercator.LonToX(lon, zoom);
    200                 int y = OsmMercator.LatToY(lat, zoom);
    201                 setDisplayPosition(mapPoint, x, y, zoom);
    202         }
    203 
    204         public void setDisplayPosition(int x, int y, int zoom) {
    205                 setDisplayPosition(new Point(getWidth() / 2, getHeight() / 2), x, y,
    206                                 zoom);
    207         }
    208 
    209         public void setDisplayPosition(Point mapPoint, int x, int y, int zoom) {
    210                 if (zoom > tileSource.getMaxZoom() || zoom < MIN_ZOOM)
    211                         return;
    212 
    213                 // Get the plain tile number
    214                 Point p = new Point();
    215                 p.x = x - mapPoint.x + getWidth() / 2;
    216                 p.y = y - mapPoint.y + getHeight() / 2;
    217                 center = p;
    218                 setIgnoreRepaint(true);
    219                 try {
    220                         int oldZoom = this.zoom;
    221                         this.zoom = zoom;
    222                         if (oldZoom != zoom)
    223                                 zoomChanged(oldZoom);
    224                         if (zoomSlider.getValue() != zoom)
    225                                 zoomSlider.setValue(zoom);
    226                 } finally {
    227                         setIgnoreRepaint(false);
    228                         repaint();
    229                 }
    230         }
    231 
    232         /**
    233          * Sets the displayed map pane and zoom level so that all map markers are
    234          * visible.
    235          */
    236         public void setDisplayToFitMapMarkers() {
    237                 if (mapMarkerList == null || mapMarkerList.size() == 0)
    238                         return;
    239                 int x_min = Integer.MAX_VALUE;
    240                 int y_min = Integer.MAX_VALUE;
    241                 int x_max = Integer.MIN_VALUE;
    242                 int y_max = Integer.MIN_VALUE;
    243                 int mapZoomMax = tileSource.getMaxZoom();
    244                 for (MapMarker marker : mapMarkerList) {
    245                         int x = OsmMercator.LonToX(marker.getLon(), mapZoomMax);
    246                         int y = OsmMercator.LatToY(marker.getLat(), mapZoomMax);
    247                         x_max = Math.max(x_max, x);
    248                         y_max = Math.max(y_max, y);
    249                         x_min = Math.min(x_min, x);
    250                         y_min = Math.min(y_min, y);
    251                 }
    252                 int height = Math.max(0, getHeight());
    253                 int width = Math.max(0, getWidth());
    254                 // System.out.println(x_min + " < x < " + x_max);
    255                 // System.out.println(y_min + " < y < " + y_max);
    256                 // System.out.println("tiles: " + width + " " + height);
    257                 int newZoom = mapZoomMax;
    258                 int x = x_max - x_min;
    259                 int y = y_max - y_min;
    260                 while (x > width || y > height) {
    261                         // System.out.println("zoom: " + zoom + " -> " + x + " " + y);
    262                         newZoom--;
    263                         x >>= 1;
    264                         y >>= 1;
    265                 }
    266                 x = x_min + (x_max - x_min) / 2;
    267                 y = y_min + (y_max - y_min) / 2;
    268                 int z = 1 << (mapZoomMax - newZoom);
    269                 x /= z;
    270                 y /= z;
    271                 setDisplayPosition(x, y, newZoom);
    272         }
    273 
    274         public Point2D.Double getPosition() {
    275                 double lon = OsmMercator.XToLon(center.x, zoom);
    276                 double lat = OsmMercator.YToLat(center.y, zoom);
    277                 return new Point2D.Double(lat, lon);
    278         }
    279 
    280         public Point2D.Double getPosition(Point mapPoint) {
    281                 int x = center.x + mapPoint.x - getWidth() / 2;
    282                 int y = center.y + mapPoint.y - getHeight() / 2;
    283                 double lon = OsmMercator.XToLon(x, zoom);
    284                 double lat = OsmMercator.YToLat(y, zoom);
    285                 return new Point2D.Double(lat, lon);
    286         }
    287 
    288         /**
    289          * Calculates the position on the map of a given coordinate
    290          *
    291          * @param lat
    292          * @param lon
    293          * @return point on the map or <code>null</code> if the point is not visible
    294          */
    295         public Point getMapPosition(double lat, double lon) {
    296                 int x = OsmMercator.LonToX(lon, zoom);
    297                 int y = OsmMercator.LatToY(lat, zoom);
    298                 x -= center.x - getWidth() / 2;
    299                 y -= center.y - getHeight() / 2;
    300                 if (x < 0 || y < 0 || x > getWidth() || y > getHeight())
    301                         return null;
    302                 return new Point(x, y);
    303         }
    304 
    305         @Override
    306         protected void paintComponent(Graphics g) {
    307                 super.paintComponent(g);
    308 
    309                 int iMove = 0;
    310 
    311                 int tilex = center.x / Tile.SIZE;
    312                 int tiley = center.y / Tile.SIZE;
    313                 int off_x = (center.x % Tile.SIZE);
    314                 int off_y = (center.y % Tile.SIZE);
    315 
    316                 int w2 = getWidth() / 2;
    317                 int h2 = getHeight() / 2;
    318                 int posx = w2 - off_x;
    319                 int posy = h2 - off_y;
    320 
    321                 int diff_left = off_x;
    322                 int diff_right = Tile.SIZE - off_x;
    323                 int diff_top = off_y;
    324                 int diff_bottom = Tile.SIZE - off_y;
    325 
    326                 boolean start_left = diff_left < diff_right;
    327                 boolean start_top = diff_top < diff_bottom;
    328 
    329                 if (start_top) {
    330                         if (start_left)
    331                                 iMove = 2;
    332                         else
    333                                 iMove = 3;
    334                 } else {
    335                         if (start_left)
    336                                 iMove = 1;
    337                         else
    338                                 iMove = 0;
    339                 } // calculate the visibility borders
    340                 int x_min = -Tile.SIZE;
    341                 int y_min = -Tile.SIZE;
    342                 int x_max = getWidth();
    343                 int y_max = getHeight();
    344 
    345                 // paint the tiles in a spiral, starting from center of the map
    346                 boolean painted = true;
    347                 int x = 0;
    348                 while (painted) {
    349                         painted = false;
    350                         for (int i = 0; i < 4; i++) {
    351                                 if (i % 2 == 0)
    352                                         x++;
    353                                 for (int j = 0; j < x; j++) {
    354                                         if (x_min <= posx && posx <= x_max && y_min <= posy
    355                                                         && posy <= y_max) {
    356                                                 // tile is visible
    357                                                 Tile tile = getTile(tilex, tiley, zoom);
    358                                                 if (tile != null) {
    359                                                         painted = true;
    360                                                         tile.paint(g, posx, posy);
    361                                                         if (tileGridVisible)
    362                                                                 g.drawRect(posx, posy, Tile.SIZE, Tile.SIZE);
    363                                                 }
    364                                         }
    365                                         Point p = move[iMove];
    366                                         posx += p.x * Tile.SIZE;
    367                                         posy += p.y * Tile.SIZE;
    368                                         tilex += p.x;
    369                                         tiley += p.y;
    370                                 }
    371                                 iMove = (iMove + 1) % move.length;
    372                         }
    373                 }
    374                 // outer border of the map
    375                 int mapSize = Tile.SIZE << zoom;
    376                 g.drawRect(w2 - center.x, h2 - center.y, mapSize, mapSize);
    377 
    378                 // g.drawString("Tiles in cache: " + tileCache.getTileCount(), 50, 20);
    379                 if (!mapMarkersVisible || mapMarkerList == null)
    380                         return;
    381                 for (MapMarker marker : mapMarkerList) {
    382                         Point p = getMapPosition(marker.getLat(), marker.getLon());
    383                         // System.out.println(marker + " -> " + p);
    384                         if (p != null)
    385                                 marker.paint(g, p);
    386                 }
    387         }
    388 
    389         /**
    390          * Moves the visible map pane.
    391          *
    392          * @param x
    393          *            horizontal movement in pixel.
    394          * @param y
    395          *            vertical movement in pixel
    396          */
    397         public void moveMap(int x, int y) {
    398                 center.x += x;
    399                 center.y += y;
    400                 repaint();
    401         }
    402 
    403         /**
    404          * @return the current zoom level
    405          */
    406         public int getZoom() {
    407                 return zoom;
    408         }
    409 
    410         /**
    411          * Increases the current zoom level by one
    412          */
    413         public void zoomIn() {
    414                 setZoom(zoom + 1);
    415         }
    416 
    417         /**
    418          * Increases the current zoom level by one
    419          */
    420         public void zoomIn(Point mapPoint) {
    421                 setZoom(zoom + 1, mapPoint);
    422         }
    423 
    424         /**
    425          * Decreases the current zoom level by one
    426          */
    427         public void zoomOut() {
    428                 setZoom(zoom - 1);
    429         }
    430 
    431         /**
    432          * Decreases the current zoom level by one
    433          */
    434         public void zoomOut(Point mapPoint) {
    435                 setZoom(zoom - 1, mapPoint);
    436         }
    437 
    438         public void setZoom(int zoom, Point mapPoint) {
    439                 if (zoom > tileSource.getMaxZoom() || zoom == this.zoom)
    440                         return;
    441                 Point2D.Double zoomPos = getPosition(mapPoint);
    442                 jobDispatcher.cancelOutstandingJobs(); // Clearing outstanding load
    443                 // requests
    444                 setDisplayPositionByLatLon(mapPoint, zoomPos.x, zoomPos.y, zoom);
    445         }
    446 
    447         public void setZoom(int zoom) {
    448                 setZoom(zoom, new Point(getWidth() / 2, getHeight() / 2));
    449         }
    450 
    451         /**
    452          * retrieves a tile from the cache. If the tile is not present in the cache
    453          * a load job is added to the working queue of {@link JobThread}.
    454          *
    455          * @param tilex
    456          * @param tiley
    457          * @param zoom
    458          * @return specified tile from the cache or <code>null</code> if the tile
    459          *         was not found in the cache.
    460          */
    461         protected Tile getTile(int tilex, int tiley, int zoom) {
    462                 int max = (1 << zoom);
    463                 if (tilex < 0 || tilex >= max || tiley < 0 || tiley >= max)
    464                         return null;
    465                 Tile tile = tileCache.getTile(tileSource, tilex, tiley, zoom);
    466                 if (tile == null) {
    467                         tile = new Tile(tileSource, tilex, tiley, zoom, loadingImage);
    468                         tileCache.addTile(tile);
    469                         tile.loadPlaceholderFromCache(tileCache);
    470                 }
    471                 if (!tile.isLoaded()) {
    472                         jobDispatcher.addJob(tileLoader.createTileLoaderJob(tileSource,
    473                                         tilex, tiley, zoom));
    474                 }
    475                 return tile;
    476         }
    477 
    478         /**
    479          * Every time the zoom level changes this method is called. Override it in
    480          * derived implementations for adapting zoom dependent values. The new zoom
    481          * level can be obtained via {@link #getZoom()}.
    482          *
    483          * @param oldZoom
    484          *            the previous zoom level
    485          */
    486         protected void zoomChanged(int oldZoom) {
    487                 zoomSlider.setToolTipText("Zoom level " + zoom);
    488                 zoomInButton.setToolTipText("Zoom to level " + (zoom + 1));
    489                 zoomOutButton.setToolTipText("Zoom to level " + (zoom - 1));
    490                 zoomOutButton.setEnabled(zoom > 0);
    491                 zoomInButton.setEnabled(zoom < tileSource.getMaxZoom());
    492         }
    493 
    494         public boolean isTileGridVisible() {
    495                 return tileGridVisible;
    496         }
    497 
    498         public void setTileGridVisible(boolean tileGridVisible) {
    499                 this.tileGridVisible = tileGridVisible;
    500                 repaint();
    501         }
    502 
    503         public boolean getMapMarkersVisible() {
    504                 return mapMarkersVisible;
    505         }
    506 
    507         /**
    508          * Enables or disables painting of the {@link MapMarker}
    509          *
    510          * @param mapMarkersVisible
    511          * @see #addMapMarker(MapMarker)
    512          * @see #getMapMarkerList()
    513          */
    514         public void setMapMarkerVisible(boolean mapMarkersVisible) {
    515                 this.mapMarkersVisible = mapMarkersVisible;
    516                 repaint();
    517         }
    518 
    519         public void setMapMarkerList(List<MapMarker> mapMarkerList) {
    520                 this.mapMarkerList = mapMarkerList;
    521                 repaint();
    522         }
    523 
    524         public List<MapMarker> getMapMarkerList() {
    525                 return mapMarkerList;
    526         }
    527 
    528         public void addMapMarker(MapMarker marker) {
    529                 mapMarkerList.add(marker);
    530         }
    531 
    532         public void setZoomContolsVisible(boolean visible) {
    533                 zoomSlider.setVisible(visible);
    534                 zoomInButton.setVisible(visible);
    535                 zoomOutButton.setVisible(visible);
    536         }
    537 
    538         public boolean getZoomContolsVisible() {
    539                 return zoomSlider.isVisible();
    540         }
    541 
    542         public TileCache getTileCache() {
    543                 return tileCache;
    544         }
    545 
    546         public TileLoader getTileLoader() {
    547                 return tileLoader;
    548         }
    549 
    550         public void setTileLoader(TileLoader tileLoader) {
    551                 this.tileLoader = tileLoader;
    552         }
    553 
    554         public TileSource getTileLayerSource() {
    555                 return tileSource;
    556         }
    557 
    558         public void setTileSource(TileSource tileSource) {
    559                 if (tileSource.getMaxZoom() > MAX_ZOOM)
    560                         throw new RuntimeException("Zoom level too high");
    561                 this.tileSource = tileSource;
    562                 zoomSlider.setMaximum(tileSource.getMaxZoom());
    563                 jobDispatcher.cancelOutstandingJobs();
    564                 if (zoom > tileSource.getMaxZoom())
    565                         setZoom(tileSource.getMaxZoom());
    566                 repaint();
    567         }
    568 
    569         public void tileLoadingFinished(Tile tile) {
    570                 repaint();
    571         }
     40    private static final long serialVersionUID = 1L;
     41
     42    /**
     43     * Vectors for clock-wise tile painting
     44     */
     45    protected static final Point[] move = { new Point(1, 0), new Point(0, 1), new Point(-1, 0), new Point(0, -1) };
     46
     47    public static final int MAX_ZOOM = 22;
     48    public static final int MIN_ZOOM = 0;
     49
     50    protected TileLoader tileLoader;
     51    protected TileCache tileCache;
     52    protected TileSource tileSource;
     53
     54    protected List<MapMarker> mapMarkerList;
     55    protected boolean mapMarkersVisible;
     56    protected boolean tileGridVisible;
     57
     58    /**
     59     * x- and y-position of the center of this map-panel on the world map
     60     * denoted in screen pixel regarding the current zoom level.
     61     */
     62    protected Point center;
     63
     64    /**
     65     * Current zoom level
     66     */
     67    protected int zoom;
     68
     69    protected JSlider zoomSlider;
     70    protected JButton zoomInButton;
     71    protected JButton zoomOutButton;
     72
     73    JobDispatcher jobDispatcher;
     74
     75    /**
     76     * Creates a standard {@link JMapViewer} instance that can be controlled via
     77     * mouse: hold right mouse button for moving, double click left mouse button
     78     * or use mouse wheel for zooming. Loaded tiles are stored the
     79     * {@link MemoryTileCache} and the tile loader uses 4 parallel threads for
     80     * retrieving the tiles.
     81     */
     82    public JMapViewer() {
     83        this(new MemoryTileCache(), 4);
     84        new DefaultMapController(this);
     85    }
     86
     87    public JMapViewer(TileCache tileCache, int downloadThreadCount) {
     88        super();
     89        tileSource = new OsmTileSource.Mapnik();
     90        tileLoader = new OsmTileLoader(this);
     91        this.tileCache = tileCache;
     92        jobDispatcher = JobDispatcher.getInstance();
     93        mapMarkerList = new LinkedList<MapMarker>();
     94        mapMarkersVisible = true;
     95        tileGridVisible = false;
     96        setLayout(null);
     97        initializeZoomSlider();
     98        setMinimumSize(new Dimension(Tile.SIZE, Tile.SIZE));
     99        setPreferredSize(new Dimension(400, 400));
     100        setDisplayPositionByLatLon(50, 9, 3);
     101    }
     102
     103    protected void initializeZoomSlider() {
     104        zoomSlider = new JSlider(MIN_ZOOM, tileSource.getMaxZoom());
     105        zoomSlider.setOrientation(JSlider.VERTICAL);
     106        zoomSlider.setBounds(10, 10, 30, 150);
     107        zoomSlider.setOpaque(false);
     108        zoomSlider.addChangeListener(new ChangeListener() {
     109            public void stateChanged(ChangeEvent e) {
     110                setZoom(zoomSlider.getValue());
     111            }
     112        });
     113        add(zoomSlider);
     114        int size = 18;
     115        try {
     116            ImageIcon icon = new ImageIcon(getClass().getResource("images/plus.png"));
     117            zoomInButton = new JButton(icon);
     118        } catch (Exception e) {
     119            zoomInButton = new JButton("+");
     120            zoomInButton.setFont(new Font("sansserif", Font.BOLD, 9));
     121            zoomInButton.setMargin(new Insets(0, 0, 0, 0));
     122        }
     123        zoomInButton.setBounds(4, 155, size, size);
     124        zoomInButton.addActionListener(new ActionListener() {
     125
     126            public void actionPerformed(ActionEvent e) {
     127                zoomIn();
     128            }
     129        });
     130        add(zoomInButton);
     131        try {
     132            ImageIcon icon = new ImageIcon(getClass().getResource("images/minus.png"));
     133            zoomOutButton = new JButton(icon);
     134        } catch (Exception e) {
     135            zoomOutButton = new JButton("-");
     136            zoomOutButton.setFont(new Font("sansserif", Font.BOLD, 9));
     137            zoomOutButton.setMargin(new Insets(0, 0, 0, 0));
     138        }
     139        zoomOutButton.setBounds(8 + size, 155, size, size);
     140        zoomOutButton.addActionListener(new ActionListener() {
     141
     142            public void actionPerformed(ActionEvent e) {
     143                zoomOut();
     144            }
     145        });
     146        add(zoomOutButton);
     147    }
     148
     149    /**
     150     * Changes the map pane so that it is centered on the specified coordinate
     151     * at the given zoom level.
     152     *
     153     * @param lat
     154     *            latitude of the specified coordinate
     155     * @param lon
     156     *            longitude of the specified coordinate
     157     * @param zoom
     158     *            {@link #MIN_ZOOM} <= zoom level <= {@link #MAX_ZOOM}
     159     */
     160    public void setDisplayPositionByLatLon(double lat, double lon, int zoom) {
     161        setDisplayPositionByLatLon(new Point(getWidth() / 2, getHeight() / 2), lat, lon, zoom);
     162    }
     163
     164    /**
     165     * Changes the map pane so that the specified coordinate at the given zoom
     166     * level is displayed on the map at the screen coordinate
     167     * <code>mapPoint</code>.
     168     *
     169     * @param mapPoint
     170     *            point on the map denoted in pixels where the coordinate should
     171     *            be set
     172     * @param lat
     173     *            latitude of the specified coordinate
     174     * @param lon
     175     *            longitude of the specified coordinate
     176     * @param zoom
     177     *            {@link #MIN_ZOOM} <= zoom level <=
     178     *            {@link TileSource#getMaxZoom()}
     179     */
     180    public void setDisplayPositionByLatLon(Point mapPoint, double lat, double lon, int zoom) {
     181        int x = OsmMercator.LonToX(lon, zoom);
     182        int y = OsmMercator.LatToY(lat, zoom);
     183        setDisplayPosition(mapPoint, x, y, zoom);
     184    }
     185
     186    public void setDisplayPosition(int x, int y, int zoom) {
     187        setDisplayPosition(new Point(getWidth() / 2, getHeight() / 2), x, y, zoom);
     188    }
     189
     190    public void setDisplayPosition(Point mapPoint, int x, int y, int zoom) {
     191        if (zoom > tileSource.getMaxZoom() || zoom < MIN_ZOOM)
     192            return;
     193
     194        // Get the plain tile number
     195        Point p = new Point();
     196        p.x = x - mapPoint.x + getWidth() / 2;
     197        p.y = y - mapPoint.y + getHeight() / 2;
     198        center = p;
     199        setIgnoreRepaint(true);
     200        try {
     201            int oldZoom = this.zoom;
     202            this.zoom = zoom;
     203            if (oldZoom != zoom)
     204                zoomChanged(oldZoom);
     205            if (zoomSlider.getValue() != zoom)
     206                zoomSlider.setValue(zoom);
     207        } finally {
     208            setIgnoreRepaint(false);
     209            repaint();
     210        }
     211    }
     212
     213    /**
     214     * Sets the displayed map pane and zoom level so that all map markers are
     215     * visible.
     216     */
     217    public void setDisplayToFitMapMarkers() {
     218        if (mapMarkerList == null || mapMarkerList.size() == 0)
     219            return;
     220        int x_min = Integer.MAX_VALUE;
     221        int y_min = Integer.MAX_VALUE;
     222        int x_max = Integer.MIN_VALUE;
     223        int y_max = Integer.MIN_VALUE;
     224        int mapZoomMax = tileSource.getMaxZoom();
     225        for (MapMarker marker : mapMarkerList) {
     226            int x = OsmMercator.LonToX(marker.getLon(), mapZoomMax);
     227            int y = OsmMercator.LatToY(marker.getLat(), mapZoomMax);
     228            x_max = Math.max(x_max, x);
     229            y_max = Math.max(y_max, y);
     230            x_min = Math.min(x_min, x);
     231            y_min = Math.min(y_min, y);
     232        }
     233        int height = Math.max(0, getHeight());
     234        int width = Math.max(0, getWidth());
     235        // System.out.println(x_min + " < x < " + x_max);
     236        // System.out.println(y_min + " < y < " + y_max);
     237        // System.out.println("tiles: " + width + " " + height);
     238        int newZoom = mapZoomMax;
     239        int x = x_max - x_min;
     240        int y = y_max - y_min;
     241        while (x > width || y > height) {
     242            // System.out.println("zoom: " + zoom + " -> " + x + " " + y);
     243            newZoom--;
     244            x >>= 1;
     245            y >>= 1;
     246        }
     247        x = x_min + (x_max - x_min) / 2;
     248        y = y_min + (y_max - y_min) / 2;
     249        int z = 1 << (mapZoomMax - newZoom);
     250        x /= z;
     251        y /= z;
     252        setDisplayPosition(x, y, newZoom);
     253    }
     254
     255    public Point2D.Double getPosition() {
     256        double lon = OsmMercator.XToLon(center.x, zoom);
     257        double lat = OsmMercator.YToLat(center.y, zoom);
     258        return new Point2D.Double(lat, lon);
     259    }
     260
     261    public Point2D.Double getPosition(Point mapPoint) {
     262        int x = center.x + mapPoint.x - getWidth() / 2;
     263        int y = center.y + mapPoint.y - getHeight() / 2;
     264        double lon = OsmMercator.XToLon(x, zoom);
     265        double lat = OsmMercator.YToLat(y, zoom);
     266        return new Point2D.Double(lat, lon);
     267    }
     268
     269    /**
     270     * Calculates the position on the map of a given coordinate
     271     *
     272     * @param lat
     273     * @param lon
     274     * @return point on the map or <code>null</code> if the point is not visible
     275     */
     276    public Point getMapPosition(double lat, double lon) {
     277        int x = OsmMercator.LonToX(lon, zoom);
     278        int y = OsmMercator.LatToY(lat, zoom);
     279        x -= center.x - getWidth() / 2;
     280        y -= center.y - getHeight() / 2;
     281        if (x < 0 || y < 0 || x > getWidth() || y > getHeight())
     282            return null;
     283        return new Point(x, y);
     284    }
     285
     286    @Override
     287    protected void paintComponent(Graphics g) {
     288        super.paintComponent(g);
     289
     290        int iMove = 0;
     291
     292        int tilex = center.x / Tile.SIZE;
     293        int tiley = center.y / Tile.SIZE;
     294        int off_x = (center.x % Tile.SIZE);
     295        int off_y = (center.y % Tile.SIZE);
     296
     297        int w2 = getWidth() / 2;
     298        int h2 = getHeight() / 2;
     299        int posx = w2 - off_x;
     300        int posy = h2 - off_y;
     301
     302        int diff_left = off_x;
     303        int diff_right = Tile.SIZE - off_x;
     304        int diff_top = off_y;
     305        int diff_bottom = Tile.SIZE - off_y;
     306
     307        boolean start_left = diff_left < diff_right;
     308        boolean start_top = diff_top < diff_bottom;
     309
     310        if (start_top) {
     311            if (start_left)
     312                iMove = 2;
     313            else
     314                iMove = 3;
     315        } else {
     316            if (start_left)
     317                iMove = 1;
     318            else
     319                iMove = 0;
     320        } // calculate the visibility borders
     321        int x_min = -Tile.SIZE;
     322        int y_min = -Tile.SIZE;
     323        int x_max = getWidth();
     324        int y_max = getHeight();
     325
     326        // paint the tiles in a spiral, starting from center of the map
     327        boolean painted = true;
     328        int x = 0;
     329        while (painted) {
     330            painted = false;
     331            for (int i = 0; i < 4; i++) {
     332                if (i % 2 == 0)
     333                    x++;
     334                for (int j = 0; j < x; j++) {
     335                    if (x_min <= posx && posx <= x_max && y_min <= posy && posy <= y_max) {
     336                        // tile is visible
     337                        Tile tile = getTile(tilex, tiley, zoom);
     338                        if (tile != null) {
     339                            painted = true;
     340                            tile.paint(g, posx, posy);
     341                            if (tileGridVisible)
     342                                g.drawRect(posx, posy, Tile.SIZE, Tile.SIZE);
     343                        }
     344                    }
     345                    Point p = move[iMove];
     346                    posx += p.x * Tile.SIZE;
     347                    posy += p.y * Tile.SIZE;
     348                    tilex += p.x;
     349                    tiley += p.y;
     350                }
     351                iMove = (iMove + 1) % move.length;
     352            }
     353        }
     354        // outer border of the map
     355        int mapSize = Tile.SIZE << zoom;
     356        g.drawRect(w2 - center.x, h2 - center.y, mapSize, mapSize);
     357
     358        // g.drawString("Tiles in cache: " + tileCache.getTileCount(), 50, 20);
     359        if (!mapMarkersVisible || mapMarkerList == null)
     360            return;
     361        for (MapMarker marker : mapMarkerList) {
     362            Point p = getMapPosition(marker.getLat(), marker.getLon());
     363            // System.out.println(marker + " -> " + p);
     364            if (p != null)
     365                marker.paint(g, p);
     366        }
     367    }
     368
     369    /**
     370     * Moves the visible map pane.
     371     *
     372     * @param x
     373     *            horizontal movement in pixel.
     374     * @param y
     375     *            vertical movement in pixel
     376     */
     377    public void moveMap(int x, int y) {
     378        center.x += x;
     379        center.y += y;
     380        repaint();
     381    }
     382
     383    /**
     384     * @return the current zoom level
     385     */
     386    public int getZoom() {
     387        return zoom;
     388    }
     389
     390    /**
     391     * Increases the current zoom level by one
     392     */
     393    public void zoomIn() {
     394        setZoom(zoom + 1);
     395    }
     396
     397    /**
     398     * Increases the current zoom level by one
     399     */
     400    public void zoomIn(Point mapPoint) {
     401        setZoom(zoom + 1, mapPoint);
     402    }
     403
     404    /**
     405     * Decreases the current zoom level by one
     406     */
     407    public void zoomOut() {
     408        setZoom(zoom - 1);
     409    }
     410
     411    /**
     412     * Decreases the current zoom level by one
     413     */
     414    public void zoomOut(Point mapPoint) {
     415        setZoom(zoom - 1, mapPoint);
     416    }
     417
     418    public void setZoom(int zoom, Point mapPoint) {
     419        if (zoom > tileSource.getMaxZoom() || zoom < tileSource.getMinZoom() || zoom == this.zoom)
     420            return;
     421        Point2D.Double zoomPos = getPosition(mapPoint);
     422        jobDispatcher.cancelOutstandingJobs(); // Clearing outstanding load
     423        // requests
     424        setDisplayPositionByLatLon(mapPoint, zoomPos.x, zoomPos.y, zoom);
     425    }
     426
     427    public void setZoom(int zoom) {
     428        setZoom(zoom, new Point(getWidth() / 2, getHeight() / 2));
     429    }
     430
     431    /**
     432     * retrieves a tile from the cache. If the tile is not present in the cache
     433     * a load job is added to the working queue of {@link JobThread}.
     434     *
     435     * @param tilex
     436     * @param tiley
     437     * @param zoom
     438     * @return specified tile from the cache or <code>null</code> if the tile
     439     *         was not found in the cache.
     440     */
     441    protected Tile getTile(int tilex, int tiley, int zoom) {
     442        int max = (1 << zoom);
     443        if (tilex < 0 || tilex >= max || tiley < 0 || tiley >= max)
     444            return null;
     445        Tile tile = tileCache.getTile(tileSource, tilex, tiley, zoom);
     446        if (tile == null) {
     447            tile = new Tile(tileSource, tilex, tiley, zoom);
     448            tileCache.addTile(tile);
     449            tile.loadPlaceholderFromCache(tileCache);
     450        }
     451        if (!tile.isLoaded()) {
     452            jobDispatcher.addJob(tileLoader.createTileLoaderJob(tileSource, tilex, tiley, zoom));
     453        }
     454        return tile;
     455    }
     456
     457    /**
     458     * Every time the zoom level changes this method is called. Override it in
     459     * derived implementations for adapting zoom dependent values. The new zoom
     460     * level can be obtained via {@link #getZoom()}.
     461     *
     462     * @param oldZoom
     463     *            the previous zoom level
     464     */
     465    protected void zoomChanged(int oldZoom) {
     466        zoomSlider.setToolTipText("Zoom level " + zoom);
     467        zoomInButton.setToolTipText("Zoom to level " + (zoom + 1));
     468        zoomOutButton.setToolTipText("Zoom to level " + (zoom - 1));
     469        zoomOutButton.setEnabled(zoom > tileSource.getMinZoom());
     470        zoomInButton.setEnabled(zoom < tileSource.getMaxZoom());
     471    }
     472
     473    public boolean isTileGridVisible() {
     474        return tileGridVisible;
     475    }
     476
     477    public void setTileGridVisible(boolean tileGridVisible) {
     478        this.tileGridVisible = tileGridVisible;
     479        repaint();
     480    }
     481
     482    public boolean getMapMarkersVisible() {
     483        return mapMarkersVisible;
     484    }
     485
     486    /**
     487     * Enables or disables painting of the {@link MapMarker}
     488     *
     489     * @param mapMarkersVisible
     490     * @see #addMapMarker(MapMarker)
     491     * @see #getMapMarkerList()
     492     */
     493    public void setMapMarkerVisible(boolean mapMarkersVisible) {
     494        this.mapMarkersVisible = mapMarkersVisible;
     495        repaint();
     496    }
     497
     498    public void setMapMarkerList(List<MapMarker> mapMarkerList) {
     499        this.mapMarkerList = mapMarkerList;
     500        repaint();
     501    }
     502
     503    public List<MapMarker> getMapMarkerList() {
     504        return mapMarkerList;
     505    }
     506
     507    public void addMapMarker(MapMarker marker) {
     508        mapMarkerList.add(marker);
     509    }
     510
     511    public void setZoomContolsVisible(boolean visible) {
     512        zoomSlider.setVisible(visible);
     513        zoomInButton.setVisible(visible);
     514        zoomOutButton.setVisible(visible);
     515    }
     516
     517    public boolean getZoomContolsVisible() {
     518        return zoomSlider.isVisible();
     519    }
     520
     521    public TileCache getTileCache() {
     522        return tileCache;
     523    }
     524
     525    public TileLoader getTileLoader() {
     526        return tileLoader;
     527    }
     528
     529    public void setTileLoader(TileLoader tileLoader) {
     530        this.tileLoader = tileLoader;
     531    }
     532
     533    public TileSource getTileLayerSource() {
     534        return tileSource;
     535    }
     536
     537    public TileSource getTileSource() {
     538        return tileSource;
     539    }
     540
     541    public void setTileSource(TileSource tileSource) {
     542        if (tileSource.getMaxZoom() > MAX_ZOOM)
     543            throw new RuntimeException("Maximum zoom level too high");
     544        if (tileSource.getMinZoom() < MIN_ZOOM)
     545            throw new RuntimeException("Minumim zoom level too low");
     546        this.tileSource = tileSource;
     547        zoomSlider.setMinimum(tileSource.getMinZoom());
     548        zoomSlider.setMaximum(tileSource.getMaxZoom());
     549        jobDispatcher.cancelOutstandingJobs();
     550        if (zoom > tileSource.getMaxZoom())
     551            setZoom(tileSource.getMaxZoom());
     552        repaint();
     553    }
     554
     555    public void tileLoadingFinished(Tile tile, boolean success) {
     556        repaint();
     557    }
    572558
    573559}
  • applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/OsmFileCacheTileLoader.java

    r11103 r11783  
    1414import java.net.URLConnection;
    1515import java.nio.charset.Charset;
     16import java.util.logging.Logger;
    1617
    1718import org.openstreetmap.gui.jmapviewer.interfaces.TileCache;
     
    3031public class OsmFileCacheTileLoader extends OsmTileLoader {
    3132
    32         private static final String ETAG_FILE_EXT = ".etag";
    33 
    34         private static final Charset ETAG_CHARSET = Charset.forName("UTF-8");
    35 
    36         public static final long FILE_AGE_ONE_DAY = 1000 * 60 * 60 * 24;
    37         public static final long FILE_AGE_ONE_WEEK = FILE_AGE_ONE_DAY * 7;
    38 
    39         protected String cacheDirBase;
    40 
    41         protected long maxCacheFileAge = FILE_AGE_ONE_WEEK;
    42         protected long recheckAfter = FILE_AGE_ONE_DAY;
    43 
    44         public OsmFileCacheTileLoader(TileLoaderListener map) {
    45                 super(map);
    46                 String tempDir = System.getProperty("java.io.tmpdir");
    47                 try {
    48                         if (tempDir == null)
    49                                 throw new IOException();
    50                         File cacheDir = new File(tempDir, "JMapViewerTiles");
    51                         // System.out.println(cacheDir);
    52                         if (!cacheDir.exists() && !cacheDir.mkdirs())
    53                                 throw new IOException();
    54                         cacheDirBase = cacheDir.getAbsolutePath();
    55                 } catch (Exception e) {
    56                         cacheDirBase = "tiles";
    57                 }
    58         }
    59 
    60         public Runnable createTileLoaderJob(final TileSource source,
    61                         final int tilex, final int tiley, final int zoom) {
    62                 return new FileLoadJob(source, tilex, tiley, zoom);
    63         }
    64 
    65         protected class FileLoadJob implements Runnable {
    66                 InputStream input = null;
    67 
    68                 int tilex, tiley, zoom;
    69                 Tile tile;
    70                 TileSource source;
    71                 File tileCacheDir;
    72                 File tileFile = null;
    73                 long fileAge = 0;
    74                 boolean fileTilePainted = false;
    75 
    76                 public FileLoadJob(TileSource source, int tilex, int tiley, int zoom) {
    77                         super();
    78                         this.source = source;
    79                         this.tilex = tilex;
    80                         this.tiley = tiley;
    81                         this.zoom = zoom;
    82                 }
    83 
    84                 public void run() {
    85                         TileCache cache = listener.getTileCache();
    86                         synchronized (cache) {
    87                                 tile = cache.getTile(source, tilex, tiley, zoom);
    88                                 if (tile == null || tile.isLoaded() || tile.loading)
    89                                         return;
    90                                 tile.loading = true;
    91                         }
    92                         tileCacheDir = new File(cacheDirBase, source.getName());
    93                         if (!tileCacheDir.exists())
    94                                 tileCacheDir.mkdirs();
    95                         if (loadTileFromFile())
    96                                 return;
    97                         if (fileTilePainted) {
    98                                 Runnable job = new Runnable() {
    99 
    100                                         public void run() {
    101                                                 loadorUpdateTile();
    102                                         }
    103                                 };
    104                                 JobDispatcher.getInstance().addJob(job);
    105                         } else {
    106                                 loadorUpdateTile();
    107                         }
    108                 }
    109 
    110                 protected void loadorUpdateTile() {
    111 
    112                         try {
    113                                 // System.out.println("Loading tile from OSM: " + tile);
    114                                 HttpURLConnection urlConn = loadTileFromOsm(tile);
    115                                 if (tileFile != null) {
    116                                         switch (source.getTileUpdate()) {
    117                                         case IfModifiedSince:
    118                                                 urlConn.setIfModifiedSince(fileAge);
    119                                                 break;
    120                                         case LastModified:
    121                                                 if (!isOsmTileNewer(fileAge)) {
    122                                                         // System.out.println(
    123                                                         // "LastModified: Local version is up to date: " +
    124                                                         // tile);
    125                                                         tile.setLoaded(true);
    126                                                         tileFile.setLastModified(System.currentTimeMillis()
    127                                                                         - maxCacheFileAge + recheckAfter);
    128                                                         return;
    129                                                 }
    130                                                 break;
    131                                         }
    132                                 }
    133                                 if (source.getTileUpdate() == TileUpdate.ETag
    134                                                 || source.getTileUpdate() == TileUpdate.IfNoneMatch) {
    135                                         if (tileFile != null) {
    136                                                 String fileETag = loadETagfromFile();
    137                                                 if (fileETag != null) {
    138                                                         switch (source.getTileUpdate()) {
    139                                                         case IfNoneMatch:
    140                                                                 urlConn.addRequestProperty("If-None-Match",
    141                                                                                 fileETag);
    142                                                                 break;
    143                                                         case ETag:
    144                                                                 if (hasOsmTileETag(fileETag)) {
    145                                                                         tile.setLoaded(true);
    146                                                                         tileFile.setLastModified(System
    147                                                                                         .currentTimeMillis()
    148                                                                                         - maxCacheFileAge + recheckAfter);
    149                                                                         return;
    150                                                                 }
    151                                                         }
    152                                                 }
    153                                         }
    154 
    155                                         String eTag = urlConn.getHeaderField("ETag");
    156                                         saveETagToFile(eTag);
    157                                 }
    158                                 if (urlConn.getResponseCode() == 304) {
    159                                         // If we are isModifiedSince or If-None-Match has been set
    160                                         // and the server answers with a HTTP 304 = "Not Modified"
    161                                         System.out.println("Local version is up to date: " + tile);
    162                                         tile.setLoaded(true);
    163                                         tileFile.setLastModified(System.currentTimeMillis()
    164                                                         - maxCacheFileAge + recheckAfter);
    165                                         return;
    166                                 }
    167 
    168                                 byte[] buffer = loadTileInBuffer(urlConn);
    169                                 if (buffer != null) {
    170                                         tile.loadImage(new ByteArrayInputStream(buffer));
    171                                         tile.setLoaded(true);
    172                                         listener.tileLoadingFinished(tile);
    173                                         saveTileToFile(buffer);
    174                                 } else {
    175                                         tile.setLoaded(true);
    176                                 }
    177                         } catch (Exception e) {
    178                                 if (input == null /* || !input.isStopped() */)
    179                                         System.err.println("failed loading " + zoom + "/" + tilex
    180                                                         + "/" + tiley + " " + e.getMessage());
    181                         } finally {
    182                                 tile.loading = false;
    183                         }
    184                 }
    185 
    186                 protected boolean loadTileFromFile() {
    187                         FileInputStream fin = null;
    188                         try {
    189                                 tileFile = getTileFile();
    190                                 fin = new FileInputStream(tileFile);
    191                                 if (fin.available() == 0)
    192                                         throw new IOException("File empty");
    193                                 tile.loadImage(fin);
    194                                 fin.close();
    195                                 fileAge = tileFile.lastModified();
    196                                 boolean oldTile = System.currentTimeMillis() - fileAge > maxCacheFileAge;
    197                                 // System.out.println("Loaded from file: " + tile);
    198                                 if (!oldTile) {
    199                                         tile.setLoaded(true);
    200                                         listener.tileLoadingFinished(tile);
    201                                         fileTilePainted = true;
    202                                         return true;
    203                                 }
    204                                 listener.tileLoadingFinished(tile);
    205                                 fileTilePainted = true;
    206                         } catch (Exception e) {
    207                                 try {
    208                                         if (fin != null) {
    209                                                 fin.close();
    210                                                 tileFile.delete();
    211                                         }
    212                                 } catch (Exception e1) {
    213                                 }
    214                                 tileFile = null;
    215                                 fileAge = 0;
    216                         }
    217                         return false;
    218                 }
    219 
    220                 protected byte[] loadTileInBuffer(URLConnection urlConn)
    221                                 throws IOException {
    222                         input = urlConn.getInputStream();
    223                         ByteArrayOutputStream bout = new ByteArrayOutputStream(input
    224                                         .available());
    225                         byte[] buffer = new byte[2048];
    226                         boolean finished = false;
    227                         do {
    228                                 int read = input.read(buffer);
    229                                 if (read >= 0)
    230                                         bout.write(buffer, 0, read);
    231                                 else
    232                                         finished = true;
    233                         } while (!finished);
    234                         if (bout.size() == 0)
    235                                 return null;
    236                         return bout.toByteArray();
    237                 }
    238 
    239                 /**
    240                  * Performs a <code>HEAD</code> request for retrieving the
    241                  * <code>LastModified</code> header value.
    242                  *
    243                  * Note: This does only work with servers providing the
    244                  * <code>LastModified</code> header:
    245                  * <ul>
    246                  * <li>{@link OsmTileLoader#MAP_OSMA} - supported</li>
    247                  * <li>{@link OsmTileLoader#MAP_MAPNIK} - not supported</li>
    248                  * </ul>
    249                  *
    250                  * @param fileAge
    251                  * @return <code>true</code> if the tile on the server is newer than the
    252                  *         file
    253                  * @throws IOException
    254                  */
    255                 protected boolean isOsmTileNewer(long fileAge) throws IOException {
    256                         URL url;
    257                         url = new URL(tile.getUrl());
    258                         HttpURLConnection urlConn = (HttpURLConnection) url
    259                                         .openConnection();
    260                         urlConn.setRequestMethod("HEAD");
    261                         urlConn.setReadTimeout(30000); // 30 seconds read timeout
    262                         // System.out.println("Tile age: " + new
    263                         // Date(urlConn.getLastModified()) + " / "
    264                         // + new Date(fileAge));
    265                         long lastModified = urlConn.getLastModified();
    266                         if (lastModified == 0)
    267                                 return true; // no LastModified time returned
    268                         return (lastModified > fileAge);
    269                 }
    270 
    271                 protected boolean hasOsmTileETag(String eTag) throws IOException {
    272                         URL url;
    273                         url = new URL(tile.getUrl());
    274                         HttpURLConnection urlConn = (HttpURLConnection) url
    275                                         .openConnection();
    276                         urlConn.setRequestMethod("HEAD");
    277                         urlConn.setReadTimeout(30000); // 30 seconds read timeout
    278                         // System.out.println("Tile age: " + new
    279                         // Date(urlConn.getLastModified()) + " / "
    280                         // + new Date(fileAge));
    281                         String osmETag = urlConn.getHeaderField("ETag");
    282                         if (osmETag == null)
    283                                 return true;
    284                         return (osmETag.equals(eTag));
    285                 }
    286 
    287                 protected File getTileFile() throws IOException {
    288                         return new File(tileCacheDir + "/" + tile.getZoom() + "_"
    289                                         + tile.getXtile() + "_" + tile.getYtile() + "."
    290                                         + source.getTileType());
    291                 }
    292 
    293                 protected void saveTileToFile(byte[] rawData) {
    294                         try {
    295                                 FileOutputStream f = new FileOutputStream(tileCacheDir + "/"
    296                                                 + tile.getZoom() + "_" + tile.getXtile() + "_"
    297                                                 + tile.getYtile() + "." + source.getTileType());
    298                                 f.write(rawData);
    299                                 f.close();
    300                                 // System.out.println("Saved tile to file: " + tile);
    301                         } catch (Exception e) {
    302                                 System.err.println("Failed to save tile content: "
    303                                                 + e.getLocalizedMessage());
    304                         }
    305                 }
    306 
    307                 protected void saveETagToFile(String eTag) {
    308                         try {
    309                                 FileOutputStream f = new FileOutputStream(tileCacheDir + "/"
    310                                                 + tile.getZoom() + "_" + tile.getXtile() + "_"
    311                                                 + tile.getYtile() + ETAG_FILE_EXT);
    312                                 f.write(eTag.getBytes(ETAG_CHARSET.name()));
    313                                 f.close();
    314                         } catch (Exception e) {
    315                                 System.err.println("Failed to save ETag: "
    316                                                 + e.getLocalizedMessage());
    317                         }
    318                 }
    319 
    320                 protected String loadETagfromFile() {
    321                         try {
    322                                 FileInputStream f = new FileInputStream(tileCacheDir + "/"
    323                                                 + tile.getZoom() + "_" + tile.getXtile() + "_"
    324                                                 + tile.getYtile() + ETAG_FILE_EXT);
    325                                 byte[] buf = new byte[f.available()];
    326                                 f.read(buf);
    327                                 f.close();
    328                                 return new String(buf, ETAG_CHARSET.name());
    329                         } catch (Exception e) {
    330                                 return null;
    331                         }
    332                 }
    333 
    334         }
    335 
    336         public long getMaxFileAge() {
    337                 return maxCacheFileAge;
    338         }
    339 
    340         /**
    341          * Sets the maximum age of the local cached tile in the file system. If a
    342          * local tile is older than the specified file age
    343          * {@link OsmFileCacheTileLoader} will connect to the tile server and check
    344          * if a newer tile is available using the mechanism specified for the
    345          * selected tile source/server.
    346          *
    347          * @param maxFileAge
    348          *            maximum age in milliseconds
    349          * @see #FILE_AGE_ONE_DAY
    350          * @see #FILE_AGE_ONE_WEEK
    351          * @see TileSource#getTileUpdate()
    352          */
    353         public void setCacheMaxFileAge(long maxFileAge) {
    354                 this.maxCacheFileAge = maxFileAge;
    355         }
    356 
    357         public String getCacheDirBase() {
    358                 return cacheDirBase;
    359         }
    360 
    361         public void setTileCacheDir(String tileCacheDir) {
    362                 File dir = new File(tileCacheDir);
    363                 dir.mkdirs();
    364                 this.cacheDirBase = dir.getAbsolutePath();
    365         }
     33    private static final Logger log = Logger.getLogger(OsmFileCacheTileLoader.class.getName());
     34
     35    private static final String ETAG_FILE_EXT = ".etag";
     36
     37    private static final Charset ETAG_CHARSET = Charset.forName("UTF-8");
     38
     39    public static final long FILE_AGE_ONE_DAY = 1000 * 60 * 60 * 24;
     40    public static final long FILE_AGE_ONE_WEEK = FILE_AGE_ONE_DAY * 7;
     41
     42    protected String cacheDirBase;
     43
     44    protected long maxCacheFileAge = FILE_AGE_ONE_WEEK;
     45    protected long recheckAfter = FILE_AGE_ONE_DAY;
     46
     47    public OsmFileCacheTileLoader(TileLoaderListener map) {
     48        super(map);
     49        String tempDir = System.getProperty("java.io.tmpdir");
     50        try {
     51            if (tempDir == null)
     52                throw new IOException();
     53            File cacheDir = new File(tempDir, "JMapViewerTiles");
     54            log.finest("Tile cache directory: " + cacheDir);
     55            if (!cacheDir.exists() && !cacheDir.mkdirs())
     56                throw new IOException();
     57            cacheDirBase = cacheDir.getAbsolutePath();
     58        } catch (Exception e) {
     59            cacheDirBase = "tiles";
     60        }
     61    }
     62
     63    public Runnable createTileLoaderJob(final TileSource source, final int tilex, final int tiley, final int zoom) {
     64        return new FileLoadJob(source, tilex, tiley, zoom);
     65    }
     66
     67    protected class FileLoadJob implements Runnable {
     68        InputStream input = null;
     69
     70        int tilex, tiley, zoom;
     71        Tile tile;
     72        TileSource source;
     73        File tileCacheDir;
     74        File tileFile = null;
     75        long fileAge = 0;
     76        boolean fileTilePainted = false;
     77
     78        public FileLoadJob(TileSource source, int tilex, int tiley, int zoom) {
     79            super();
     80            this.source = source;
     81            this.tilex = tilex;
     82            this.tiley = tiley;
     83            this.zoom = zoom;
     84        }
     85
     86        public void run() {
     87            TileCache cache = listener.getTileCache();
     88            synchronized (cache) {
     89                tile = cache.getTile(source, tilex, tiley, zoom);
     90                if (tile == null || tile.isLoaded() || tile.loading)
     91                    return;
     92                tile.loading = true;
     93            }
     94            tileCacheDir = new File(cacheDirBase, source.getName());
     95            if (!tileCacheDir.exists())
     96                tileCacheDir.mkdirs();
     97            if (loadTileFromFile())
     98                return;
     99            if (fileTilePainted) {
     100                Runnable job = new Runnable() {
     101
     102                    public void run() {
     103                        loadOrUpdateTile();
     104                    }
     105                };
     106                JobDispatcher.getInstance().addJob(job);
     107            } else {
     108                loadOrUpdateTile();
     109            }
     110        }
     111
     112        protected void loadOrUpdateTile() {
     113
     114            try {
     115                // log.finest("Loading tile from OSM: " + tile);
     116                HttpURLConnection urlConn = loadTileFromOsm(tile);
     117                if (tileFile != null) {
     118                    switch (source.getTileUpdate()) {
     119                    case IfModifiedSince:
     120                        urlConn.setIfModifiedSince(fileAge);
     121                        break;
     122                    case LastModified:
     123                        if (!isOsmTileNewer(fileAge)) {
     124                            log.finest("LastModified test: local version is up to date: " + tile);
     125                            tile.setLoaded(true);
     126                            tileFile.setLastModified(System.currentTimeMillis() - maxCacheFileAge + recheckAfter);
     127                            return;
     128                        }
     129                        break;
     130                    }
     131                }
     132                if (source.getTileUpdate() == TileUpdate.ETag || source.getTileUpdate() == TileUpdate.IfNoneMatch) {
     133                    if (tileFile != null) {
     134                        String fileETag = loadETagfromFile();
     135                        if (fileETag != null) {
     136                            switch (source.getTileUpdate()) {
     137                            case IfNoneMatch:
     138                                urlConn.addRequestProperty("If-None-Match", fileETag);
     139                                break;
     140                            case ETag:
     141                                if (hasOsmTileETag(fileETag)) {
     142                                    tile.setLoaded(true);
     143                                    tileFile.setLastModified(System.currentTimeMillis() - maxCacheFileAge
     144                                            + recheckAfter);
     145                                    return;
     146                                }
     147                            }
     148                        }
     149                    }
     150
     151                    String eTag = urlConn.getHeaderField("ETag");
     152                    saveETagToFile(eTag);
     153                }
     154                if (urlConn.getResponseCode() == 304) {
     155                    // If we are isModifiedSince or If-None-Match has been set
     156                    // and the server answers with a HTTP 304 = "Not Modified"
     157                    log.finest("ETag test: local version is up to date: " + tile);
     158                    tile.setLoaded(true);
     159                    tileFile.setLastModified(System.currentTimeMillis() - maxCacheFileAge + recheckAfter);
     160                    return;
     161                }
     162
     163                byte[] buffer = loadTileInBuffer(urlConn);
     164                if (buffer != null) {
     165                    tile.loadImage(new ByteArrayInputStream(buffer));
     166                    tile.setLoaded(true);
     167                    listener.tileLoadingFinished(tile, true);
     168                    saveTileToFile(buffer);
     169                } else {
     170                    tile.setLoaded(true);
     171                }
     172            } catch (Exception e) {
     173                tile.setImage(Tile.ERROR_IMAGE);
     174                listener.tileLoadingFinished(tile, false);
     175                if (input == null)
     176                    System.err.println("failed loading " + zoom + "/" + tilex + "/" + tiley + " " + e.getMessage());
     177            } finally {
     178                tile.loading = false;
     179                tile.setLoaded(true);
     180            }
     181        }
     182
     183        protected boolean loadTileFromFile() {
     184            FileInputStream fin = null;
     185            try {
     186                tileFile = getTileFile();
     187                fin = new FileInputStream(tileFile);
     188                if (fin.available() == 0)
     189                    throw new IOException("File empty");
     190                tile.loadImage(fin);
     191                fin.close();
     192                fileAge = tileFile.lastModified();
     193                boolean oldTile = System.currentTimeMillis() - fileAge > maxCacheFileAge;
     194                // System.out.println("Loaded from file: " + tile);
     195                if (!oldTile) {
     196                    tile.setLoaded(true);
     197                    listener.tileLoadingFinished(tile, true);
     198                    fileTilePainted = true;
     199                    return true;
     200                }
     201                listener.tileLoadingFinished(tile, true);
     202                fileTilePainted = true;
     203            } catch (Exception e) {
     204                try {
     205                    if (fin != null) {
     206                        fin.close();
     207                        tileFile.delete();
     208                    }
     209                } catch (Exception e1) {
     210                }
     211                tileFile = null;
     212                fileAge = 0;
     213            }
     214            return false;
     215        }
     216
     217        protected byte[] loadTileInBuffer(URLConnection urlConn) throws IOException {
     218            input = urlConn.getInputStream();
     219            ByteArrayOutputStream bout = new ByteArrayOutputStream(input.available());
     220            byte[] buffer = new byte[2048];
     221            boolean finished = false;
     222            do {
     223                int read = input.read(buffer);
     224                if (read >= 0)
     225                    bout.write(buffer, 0, read);
     226                else
     227                    finished = true;
     228            } while (!finished);
     229            if (bout.size() == 0)
     230                return null;
     231            return bout.toByteArray();
     232        }
     233
     234        /**
     235         * Performs a <code>HEAD</code> request for retrieving the
     236         * <code>LastModified</code> header value.
     237         *
     238         * Note: This does only work with servers providing the
     239         * <code>LastModified</code> header:
     240         * <ul>
     241         * <li>{@link OsmTileLoader#MAP_OSMA} - supported</li>
     242         * <li>{@link OsmTileLoader#MAP_MAPNIK} - not supported</li>
     243         * </ul>
     244         *
     245         * @param fileAge
     246         * @return <code>true</code> if the tile on the server is newer than the
     247         *         file
     248         * @throws IOException
     249         */
     250        protected boolean isOsmTileNewer(long fileAge) throws IOException {
     251            URL url;
     252            url = new URL(tile.getUrl());
     253            HttpURLConnection urlConn = (HttpURLConnection) url.openConnection();
     254            urlConn.setRequestMethod("HEAD");
     255            urlConn.setReadTimeout(30000); // 30 seconds read timeout
     256            // System.out.println("Tile age: " + new
     257            // Date(urlConn.getLastModified()) + " / "
     258            // + new Date(fileAge));
     259            long lastModified = urlConn.getLastModified();
     260            if (lastModified == 0)
     261                return true; // no LastModified time returned
     262            return (lastModified > fileAge);
     263        }
     264
     265        protected boolean hasOsmTileETag(String eTag) throws IOException {
     266            URL url;
     267            url = new URL(tile.getUrl());
     268            HttpURLConnection urlConn = (HttpURLConnection) url.openConnection();
     269            urlConn.setRequestMethod("HEAD");
     270            urlConn.setReadTimeout(30000); // 30 seconds read timeout
     271            // System.out.println("Tile age: " + new
     272            // Date(urlConn.getLastModified()) + " / "
     273            // + new Date(fileAge));
     274            String osmETag = urlConn.getHeaderField("ETag");
     275            if (osmETag == null)
     276                return true;
     277            return (osmETag.equals(eTag));
     278        }
     279
     280        protected File getTileFile() throws IOException {
     281            return new File(tileCacheDir + "/" + tile.getZoom() + "_" + tile.getXtile() + "_" + tile.getYtile() + "."
     282                    + source.getTileType());
     283        }
     284
     285        protected void saveTileToFile(byte[] rawData) {
     286            try {
     287                FileOutputStream f = new FileOutputStream(tileCacheDir + "/" + tile.getZoom() + "_" + tile.getXtile()
     288                        + "_" + tile.getYtile() + "." + source.getTileType());
     289                f.write(rawData);
     290                f.close();
     291                // System.out.println("Saved tile to file: " + tile);
     292            } catch (Exception e) {
     293                System.err.println("Failed to save tile content: " + e.getLocalizedMessage());
     294            }
     295        }
     296
     297        protected void saveETagToFile(String eTag) {
     298            try {
     299                FileOutputStream f = new FileOutputStream(tileCacheDir + "/" + tile.getZoom() + "_" + tile.getXtile()
     300                        + "_" + tile.getYtile() + ETAG_FILE_EXT);
     301                f.write(eTag.getBytes(ETAG_CHARSET.name()));
     302                f.close();
     303            } catch (Exception e) {
     304                System.err.println("Failed to save ETag: " + e.getLocalizedMessage());
     305            }
     306        }
     307
     308        protected String loadETagfromFile() {
     309            try {
     310                FileInputStream f = new FileInputStream(tileCacheDir + "/" + tile.getZoom() + "_" + tile.getXtile()
     311                        + "_" + tile.getYtile() + ETAG_FILE_EXT);
     312                byte[] buf = new byte[f.available()];
     313                f.read(buf);
     314                f.close();
     315                return new String(buf, ETAG_CHARSET.name());
     316            } catch (Exception e) {
     317                return null;
     318            }
     319        }
     320
     321    }
     322
     323    public long getMaxFileAge() {
     324        return maxCacheFileAge;
     325    }
     326
     327    /**
     328     * Sets the maximum age of the local cached tile in the file system. If a
     329     * local tile is older than the specified file age
     330     * {@link OsmFileCacheTileLoader} will connect to the tile server and check
     331     * if a newer tile is available using the mechanism specified for the
     332     * selected tile source/server.
     333     *
     334     * @param maxFileAge
     335     *            maximum age in milliseconds
     336     * @see #FILE_AGE_ONE_DAY
     337     * @see #FILE_AGE_ONE_WEEK
     338     * @see TileSource#getTileUpdate()
     339     */
     340    public void setCacheMaxFileAge(long maxFileAge) {
     341        this.maxCacheFileAge = maxFileAge;
     342    }
     343
     344    public String getCacheDirBase() {
     345        return cacheDirBase;
     346    }
     347
     348    public void setTileCacheDir(String tileCacheDir) {
     349        File dir = new File(tileCacheDir);
     350        dir.mkdirs();
     351        this.cacheDirBase = dir.getAbsolutePath();
     352    }
    366353
    367354}
  • applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/OsmTileLoader.java

    r11103 r11783  
    2020public class OsmTileLoader implements TileLoader {
    2121
    22         protected TileLoaderListener listener;
     22    protected TileLoaderListener listener;
    2323
    24         public OsmTileLoader(TileLoaderListener listener) {
    25                 this.listener = listener;
    26         }
     24    public OsmTileLoader(TileLoaderListener listener) {
     25        this.listener = listener;
     26    }
    2727
    28         public Runnable createTileLoaderJob(final TileSource source, final int tilex, final int tiley,
    29                         final int zoom) {
    30                 return new Runnable() {
     28    public Runnable createTileLoaderJob(final TileSource source, final int tilex, final int tiley, final int zoom) {
     29        return new Runnable() {
    3130
    32                         InputStream input = null;
     31            InputStream input = null;
    3332
    34                         public void run() {
    35                                 TileCache cache = listener.getTileCache();
    36                                 Tile tile;
    37                                 synchronized (cache) {
    38                                         tile = cache.getTile(source, tilex, tiley, zoom);
    39                                         if (tile == null || tile.isLoaded() || tile.loading)
    40                                                 return;
    41                                         tile.loading = true;
    42                                 }
    43                                 try {
    44                                         // Thread.sleep(500);
    45                                         input = loadTileFromOsm(tile).getInputStream();
    46                                         tile.loadImage(input);
    47                                         tile.setLoaded(true);
    48                                         listener.tileLoadingFinished(tile);
    49                                         input.close();
    50                                         input = null;
    51                                 } catch (Exception e) {
    52                                         if (input == null /* || !input.isStopped() */)
    53                                                 System.err.println("failed loading " + zoom + "/" + tilex + "/" + tiley
    54                                                                 + " " + e.getMessage());
    55                                 } finally {
    56                                         tile.loading = false;
    57                                 }
    58                         }
     33            public void run() {
     34                TileCache cache = listener.getTileCache();
     35                Tile tile;
     36                synchronized (cache) {
     37                    tile = cache.getTile(source, tilex, tiley, zoom);
     38                    if (tile == null || tile.isLoaded() || tile.loading)
     39                        return;
     40                    tile.loading = true;
     41                }
     42                try {
     43                    // Thread.sleep(500);
     44                    input = loadTileFromOsm(tile).getInputStream();
     45                    tile.loadImage(input);
     46                    tile.setLoaded(true);
     47                    listener.tileLoadingFinished(tile, true);
     48                    input.close();
     49                    input = null;
     50                } catch (Exception e) {
     51                    tile.setImage(Tile.ERROR_IMAGE);
     52                    listener.tileLoadingFinished(tile, false);
     53                    if (input == null)
     54                        System.err.println("failed loading " + zoom + "/" + tilex + "/" + tiley + " " + e.getMessage());
     55                } finally {
     56                    tile.loading = false;
     57                    tile.setLoaded(true);
     58                }
     59            }
    5960
    60                 };
    61         }
     61        };
     62    }
    6263
    63         protected HttpURLConnection loadTileFromOsm(Tile tile) throws IOException {
    64                 URL url;
    65                 url = new URL(tile.getUrl());
    66                 HttpURLConnection urlConn = (HttpURLConnection) url.openConnection();
    67                 urlConn.setReadTimeout(30000); // 30 seconds read
    68                 // timeout
    69                 return urlConn;
    70         }
     64    protected HttpURLConnection loadTileFromOsm(Tile tile) throws IOException {
     65        URL url;
     66        url = new URL(tile.getUrl());
     67        HttpURLConnection urlConn = (HttpURLConnection) url.openConnection();
     68        urlConn.setReadTimeout(30000); // 30 seconds read
     69        // timeout
     70        return urlConn;
     71    }
    7172
    72         @Override
    73         public String toString() {
    74                 return getClass().getSimpleName();
    75         }
    76        
     73    @Override
     74    public String toString() {
     75        return getClass().getSimpleName();
     76    }
     77
    7778}
  • applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/OsmTileSource.java

    r11103 r11783  
    1414                        return 18;
    1515                }
     16
     17                public int getMinZoom() {
     18            return 0;
     19        }
    1620
    1721                public String getTileUrl(int zoom, int tilex, int tiley) {
  • applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/Tile.java

    r9784 r11783  
    2323public class Tile {
    2424
    25         protected TileSource source;
    26         protected int xtile;
    27         protected int ytile;
    28         protected int zoom;
    29         protected BufferedImage image;
    30         protected String key;
    31         protected boolean loaded = false;
    32         protected boolean loading = false;
    33         public static final int SIZE = 256;
    34 
    35         /**
    36          * Creates a tile with empty image.
    37          *
    38          * @param source
    39          * @param xtile
    40          * @param ytile
    41          * @param zoom
    42          */
    43         public Tile(TileSource source, int xtile, int ytile, int zoom) {
    44                 super();
    45                 this.source = source;
    46                 this.xtile = xtile;
    47                 this.ytile = ytile;
    48                 this.zoom = zoom;
    49                 this.image = null;
    50                 this.key = getTileKey(source, xtile, ytile, zoom);
    51         }
    52 
    53         public Tile(TileSource source, int xtile, int ytile, int zoom, BufferedImage image) {
    54                 this(source, xtile, ytile, zoom);
    55                 this.image = image;
    56         }
    57 
    58         /**
    59          * Tries to get tiles of a lower or higher zoom level (one or two level
    60          * difference) from cache and use it as a placeholder until the tile has
    61          * been loaded.
    62          */
    63         public void loadPlaceholderFromCache(TileCache cache) {
    64                 BufferedImage tmpImage = new BufferedImage(SIZE, SIZE, BufferedImage.TYPE_INT_RGB);
    65                 Graphics2D g = (Graphics2D) tmpImage.getGraphics();
    66                 // g.drawImage(image, 0, 0, null);
    67                 for (int zoomDiff = 1; zoomDiff < 5; zoomDiff++) {
    68                         // first we check if there are already the 2^x tiles
    69                         // of a higher detail level
    70                         int zoom_high = zoom + zoomDiff;
    71                         if (zoomDiff < 3 && zoom_high <= JMapViewer.MAX_ZOOM) {
    72                                 int factor = 1 << zoomDiff;
    73                                 int xtile_high = xtile << zoomDiff;
    74                                 int ytile_high = ytile << zoomDiff;
    75                                 double scale = 1.0 / factor;
    76                                 g.setTransform(AffineTransform.getScaleInstance(scale, scale));
    77                                 int paintedTileCount = 0;
    78                                 for (int x = 0; x < factor; x++) {
    79                                         for (int y = 0; y < factor; y++) {
    80                                                 Tile tile =
    81                                                                 cache.getTile(source, xtile_high + x, ytile_high + y, zoom_high);
    82                                                 if (tile != null && tile.isLoaded()) {
    83                                                         paintedTileCount++;
    84                                                         tile.paint(g, x * SIZE, y * SIZE);
    85                                                 }
    86                                         }
    87                                 }
    88                                 if (paintedTileCount == factor * factor) {
    89                                         image = tmpImage;
    90                                         return;
    91                                 }
    92                         }
    93 
    94                         int zoom_low = zoom - zoomDiff;
    95                         if (zoom_low >= JMapViewer.MIN_ZOOM) {
    96                                 int xtile_low = xtile >> zoomDiff;
    97                                 int ytile_low = ytile >> zoomDiff;
    98                                 int factor = (1 << zoomDiff);
    99                                 double scale = (double) factor;
    100                                 AffineTransform at = new AffineTransform();
    101                                 int translate_x = (xtile % factor) * SIZE;
    102                                 int translate_y = (ytile % factor) * SIZE;
    103                                 at.setTransform(scale, 0, 0, scale, -translate_x, -translate_y);
    104                                 g.setTransform(at);
    105                                 Tile tile = cache.getTile(source, xtile_low, ytile_low, zoom_low);
    106                                 if (tile != null && tile.isLoaded()) {
    107                                         tile.paint(g, 0, 0);
    108                                         image = tmpImage;
    109                                         return;
    110                                 }
    111                         }
    112                 }
    113         }
    114 
    115         public TileSource getSource() {
    116                 return source;
    117         }
    118 
    119         /**
    120          * @return tile number on the x axis of this tile
    121          */
    122         public int getXtile() {
    123                 return xtile;
    124         }
    125 
    126         /**
    127          * @return tile number on the y axis of this tile
    128          */
    129         public int getYtile() {
    130                 return ytile;
    131         }
    132 
    133         /**
    134          * @return zoom level of this tile
    135          */
    136         public int getZoom() {
    137                 return zoom;
    138         }
    139 
    140         public BufferedImage getImage() {
    141                 return image;
    142         }
    143 
    144         public void setImage(BufferedImage image) {
    145                 this.image = image;
    146         }
    147 
    148         public void loadImage(InputStream input) throws IOException {
    149                 image = ImageIO.read(input);
    150         }
    151 
    152         /**
    153          * @return key that identifies a tile
    154          */
    155         public String getKey() {
    156                 return key;
    157         }
    158 
    159         public boolean isLoaded() {
    160                 return loaded;
    161         }
    162 
    163         public void setLoaded(boolean loaded) {
    164                 this.loaded = loaded;
    165         }
    166 
    167         public String getUrl() {
    168                 return source.getTileUrl(zoom, xtile, ytile);
    169         }
    170 
    171         /**
    172          * Paints the tile-image on the {@link Graphics} <code>g</code> at the
    173          * position <code>x</code>/<code>y</code>.
    174          *
    175          * @param g
    176          * @param x
    177          *            x-coordinate in <code>g</code>
    178          * @param y
    179          *            y-coordinate in <code>g</code>
    180          */
    181         public void paint(Graphics g, int x, int y) {
    182                 if (image == null)
    183                         return;
    184                 g.drawImage(image, x, y, null);
    185         }
    186 
    187         @Override
    188         public String toString() {
    189                 return "Tile " + key;
    190         }
    191 
    192         @Override
    193         public boolean equals(Object obj) {
    194                 if (!(obj instanceof Tile))
    195                         return false;
    196                 Tile tile = (Tile) obj;
    197                 return (xtile == tile.xtile) && (ytile == tile.ytile) && (zoom == tile.zoom);
    198         }
    199 
    200         public static String getTileKey(TileSource source, int xtile, int ytile, int zoom) {
    201                 return zoom + "/" + xtile + "/" + ytile + "@" + source.getName();
    202         }
     25    /**
     26     * Hourglass image that is displayed until a map tile has been loaded
     27     */
     28    public static BufferedImage LOADING_IMAGE;
     29    public static BufferedImage ERROR_IMAGE;
     30
     31    static {
     32        try {
     33            LOADING_IMAGE = ImageIO.read(JMapViewer.class.getResourceAsStream("images/hourglass.png"));
     34            ERROR_IMAGE = ImageIO.read(JMapViewer.class.getResourceAsStream("images/error.png"));
     35        } catch (Exception e1) {
     36            LOADING_IMAGE = null;
     37            ERROR_IMAGE = null;
     38        }
     39    }
     40
     41    protected TileSource source;
     42    protected int xtile;
     43    protected int ytile;
     44    protected int zoom;
     45    protected BufferedImage image;
     46    protected String key;
     47    protected boolean loaded = false;
     48    protected boolean loading = false;
     49    public static final int SIZE = 256;
     50
     51    /**
     52     * Creates a tile with empty image.
     53     *
     54     * @param source
     55     * @param xtile
     56     * @param ytile
     57     * @param zoom
     58     */
     59    public Tile(TileSource source, int xtile, int ytile, int zoom) {
     60        super();
     61        this.source = source;
     62        this.xtile = xtile;
     63        this.ytile = ytile;
     64        this.zoom = zoom;
     65        this.image = LOADING_IMAGE;
     66        this.key = getTileKey(source, xtile, ytile, zoom);
     67    }
     68
     69    public Tile(TileSource source, int xtile, int ytile, int zoom, BufferedImage image) {
     70        this(source, xtile, ytile, zoom);
     71        this.image = image;
     72    }
     73
     74    /**
     75     * Tries to get tiles of a lower or higher zoom level (one or two level
     76     * difference) from cache and use it as a placeholder until the tile has
     77     * been loaded.
     78     */
     79    public void loadPlaceholderFromCache(TileCache cache) {
     80        BufferedImage tmpImage = new BufferedImage(SIZE, SIZE, BufferedImage.TYPE_INT_RGB);
     81        Graphics2D g = (Graphics2D) tmpImage.getGraphics();
     82        // g.drawImage(image, 0, 0, null);
     83        for (int zoomDiff = 1; zoomDiff < 5; zoomDiff++) {
     84            // first we check if there are already the 2^x tiles
     85            // of a higher detail level
     86            int zoom_high = zoom + zoomDiff;
     87            if (zoomDiff < 3 && zoom_high <= JMapViewer.MAX_ZOOM) {
     88                int factor = 1 << zoomDiff;
     89                int xtile_high = xtile << zoomDiff;
     90                int ytile_high = ytile << zoomDiff;
     91                double scale = 1.0 / factor;
     92                g.setTransform(AffineTransform.getScaleInstance(scale, scale));
     93                int paintedTileCount = 0;
     94                for (int x = 0; x < factor; x++) {
     95                    for (int y = 0; y < factor; y++) {
     96                        Tile tile = cache.getTile(source, xtile_high + x, ytile_high + y, zoom_high);
     97                        if (tile != null && tile.isLoaded()) {
     98                            paintedTileCount++;
     99                            tile.paint(g, x * SIZE, y * SIZE);
     100                        }
     101                    }
     102                }
     103                if (paintedTileCount == factor * factor) {
     104                    image = tmpImage;
     105                    return;
     106                }
     107            }
     108
     109            int zoom_low = zoom - zoomDiff;
     110            if (zoom_low >= JMapViewer.MIN_ZOOM) {
     111                int xtile_low = xtile >> zoomDiff;
     112                int ytile_low = ytile >> zoomDiff;
     113                int factor = (1 << zoomDiff);
     114                double scale = (double) factor;
     115                AffineTransform at = new AffineTransform();
     116                int translate_x = (xtile % factor) * SIZE;
     117                int translate_y = (ytile % factor) * SIZE;
     118                at.setTransform(scale, 0, 0, scale, -translate_x, -translate_y);
     119                g.setTransform(at);
     120                Tile tile = cache.getTile(source, xtile_low, ytile_low, zoom_low);
     121                if (tile != null && tile.isLoaded()) {
     122                    tile.paint(g, 0, 0);
     123                    image = tmpImage;
     124                    return;
     125                }
     126            }
     127        }
     128    }
     129
     130    public TileSource getSource() {
     131        return source;
     132    }
     133
     134    /**
     135     * @return tile number on the x axis of this tile
     136     */
     137    public int getXtile() {
     138        return xtile;
     139    }
     140
     141    /**
     142     * @return tile number on the y axis of this tile
     143     */
     144    public int getYtile() {
     145        return ytile;
     146    }
     147
     148    /**
     149     * @return zoom level of this tile
     150     */
     151    public int getZoom() {
     152        return zoom;
     153    }
     154
     155    public BufferedImage getImage() {
     156        return image;
     157    }
     158
     159    public void setImage(BufferedImage image) {
     160        this.image = image;
     161    }
     162
     163    public void loadImage(InputStream input) throws IOException {
     164        image = ImageIO.read(input);
     165    }
     166
     167    /**
     168     * @return key that identifies a tile
     169     */
     170    public String getKey() {
     171        return key;
     172    }
     173
     174    public boolean isLoaded() {
     175        return loaded;
     176    }
     177
     178    public void setLoaded(boolean loaded) {
     179        this.loaded = loaded;
     180    }
     181
     182    public String getUrl() {
     183        return source.getTileUrl(zoom, xtile, ytile);
     184    }
     185
     186    /**
     187     * Paints the tile-image on the {@link Graphics} <code>g</code> at the
     188     * position <code>x</code>/<code>y</code>.
     189     *
     190     * @param g
     191     * @param x
     192     *            x-coordinate in <code>g</code>
     193     * @param y
     194     *            y-coordinate in <code>g</code>
     195     */
     196    public void paint(Graphics g, int x, int y) {
     197        if (image == null)
     198            return;
     199        g.drawImage(image, x, y, null);
     200    }
     201
     202    @Override
     203    public String toString() {
     204        return "Tile " + key;
     205    }
     206
     207    @Override
     208    public boolean equals(Object obj) {
     209        if (!(obj instanceof Tile))
     210            return false;
     211        Tile tile = (Tile) obj;
     212        return (xtile == tile.xtile) && (ytile == tile.ytile) && (zoom == tile.zoom);
     213    }
     214
     215    public static String getTileKey(TileSource source, int xtile, int ytile, int zoom) {
     216        return zoom + "/" + xtile + "/" + ytile + "@" + source.getName();
     217    }
    203218
    204219}
  • applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/interfaces/TileLoaderListener.java

    r11103 r11783  
    1313         * @param tile
    1414         */
    15         public void tileLoadingFinished(Tile tile);
     15        public void tileLoadingFinished(Tile tile, boolean success);
    1616
    1717        public TileCache getTileCache();
  • applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/interfaces/TileSource.java

    r11103 r11783  
    1111public interface TileSource {
    1212
    13         /**
    14         * Specifies the different mechanisms for detecting updated tiles
    15         * respectively only download newer tiles than those stored locally.
    16         *
    17         * <ul>
    18         * <li>{@link #IfNoneMatch} Server provides ETag header entry for all tiles
    19         * and <b>supports</b> conditional download via <code>If-None-Match</code>
    20         * header entry.</li>
    21         * <li>{@link #ETag} Server provides ETag header entry for all tiles but
    22         * <b>does not support</b> conditional download via
    23         * <code>If-None-Match</code> header entry.</li>
    24         * <li>{@link #IfModifiedSince} Server provides Last-Modified header entry
    25         * for all tiles and <b>supports</b> conditional download via
    26         * <code>If-Modified-Since</code> header entry.</li>
    27         * <li>{@link #LastModified} Server provides Last-Modified header entry for
    28         * all tiles but <b>does not support</b> conditional download via
    29         * <code>If-Modified-Since</code> header entry.</li>
    30         * <li>{@link #None} The server does not support any of the listed
    31         * mechanisms.</li>
    32         * </ul>
    33         *
    34         */
    35         public enum TileUpdate {
    36                 IfNoneMatch, ETag, IfModifiedSince, LastModified, None
    37         };
     13    /**
     14    * Specifies the different mechanisms for detecting updated tiles
     15    * respectively only download newer tiles than those stored locally.
     16    *
     17    * <ul>
     18    * <li>{@link #IfNoneMatch} Server provides ETag header entry for all tiles
     19    * and <b>supports</b> conditional download via <code>If-None-Match</code>
     20    * header entry.</li>
     21    * <li>{@link #ETag} Server provides ETag header entry for all tiles but
     22    * <b>does not support</b> conditional download via
     23    * <code>If-None-Match</code> header entry.</li>
     24    * <li>{@link #IfModifiedSince} Server provides Last-Modified header entry
     25    * for all tiles and <b>supports</b> conditional download via
     26    * <code>If-Modified-Since</code> header entry.</li>
     27    * <li>{@link #LastModified} Server provides Last-Modified header entry for
     28    * all tiles but <b>does not support</b> conditional download via
     29    * <code>If-Modified-Since</code> header entry.</li>
     30    * <li>{@link #None} The server does not support any of the listed
     31    * mechanisms.</li>
     32    * </ul>
     33    *
     34    */
     35    public enum TileUpdate {
     36        IfNoneMatch, ETag, IfModifiedSince, LastModified, None
     37    };
    3838
    39         /**
    40         * Specifies the maximum zoom value. The number of zoom levels is [0..
    41         * {@link #getMaxZoom()}].
    42         *
    43         * @return maximum zoom value that has to be smaller or equal to
    44         *         {@link JMapViewer#MAX_ZOOM}
    45         */
    46         public int getMaxZoom();
     39    /**
     40    * Specifies the maximum zoom value. The number of zoom levels is [0..
     41    * {@link #getMaxZoom()}].
     42    *
     43    * @return maximum zoom value that has to be smaller or equal to
     44    *         {@link JMapViewer#MAX_ZOOM}
     45    */
     46    public int getMaxZoom();
    4747
    48         /**
    49          * @return The supported tile update mechanism
    50          * @see TileUpdate
    51          */
    52         public TileUpdate getTileUpdate();
     48    /**
     49     * Specifies the minimum zoom value. This value is usually 0.
     50     * Only for maps that cover a certain region up to a limited zoom level
     51     * this method should return a value different than 0. 
     52     *
     53     * @return minimum zoom value - usually 0
     54     */
     55    public int getMinZoom();
    5356
    54         /**
    55          * A tile layer name has to be unique and has to consist only of characters
    56          * valid for filenames.
    57          *
    58          * @return Name of the tile layer
    59          */
    60         public String getName();
     57    /**
     58     * @return The supported tile update mechanism
     59     * @see TileUpdate
     60     */
     61    public TileUpdate getTileUpdate();
    6162
    62         /**
    63          * Constructs the tile url.
    64          *
    65          * @param zoom
    66          * @param tilex
    67          * @param tiley
    68          * @return fully qualified url for downloading the specified tile image
    69          */
    70         public String getTileUrl(int zoom, int tilex, int tiley);
     63    /**
     64     * A tile layer name has to be unique and has to consist only of characters
     65     * valid for filenames.
     66     *
     67     * @return Name of the tile layer
     68     */
     69    public String getName();
    7170
    72         /**
    73          * Specifies the tile image type. For tiles rendered by Mapnik or
    74          * Osmarenderer this is usually <code>"png"</code>.
    75          *
    76          * @return file extension of the tile image type
    77          */
    78         public String getTileType();
     71    /**
     72     * Constructs the tile url.
     73     *
     74     * @param zoom
     75     * @param tilex
     76     * @param tiley
     77     * @return fully qualified url for downloading the specified tile image
     78     */
     79    public String getTileUrl(int zoom, int tilex, int tiley);
     80
     81    /**
     82     * Specifies the tile image type. For tiles rendered by Mapnik or
     83     * Osmarenderer this is usually <code>"png"</code>.
     84     *
     85     * @return file extension of the tile image type
     86     */
     87    public String getTileType();
    7988}
Note: See TracChangeset for help on using the changeset viewer.