| 1 | // License: GPL. Copyright 2007 by Tim Haussmann
|
|---|
| 2 |
|
|---|
| 3 |
|
|---|
| 4 | import java.io.IOException;
|
|---|
| 5 | import java.io.InputStream;
|
|---|
| 6 | import java.net.MalformedURLException;
|
|---|
| 7 | import java.net.URL;
|
|---|
| 8 | import java.util.Enumeration;
|
|---|
| 9 | import java.util.Hashtable;
|
|---|
| 10 | import java.util.Vector;
|
|---|
| 11 |
|
|---|
| 12 | import javax.imageio.ImageIO;
|
|---|
| 13 | import javax.swing.JComponent;
|
|---|
| 14 |
|
|---|
| 15 |
|
|---|
| 16 |
|
|---|
| 17 | /**
|
|---|
| 18 | * The TileDB is responsible for fetching and storing all needed map tiles. The tiles
|
|---|
| 19 | * are stored in memory and are fetched one by one in background.
|
|---|
| 20 | * Tiles are stored in a HashTable. The path of the tile on the tile server (i.e. '/12/3/5.png')
|
|---|
| 21 | * acts as key and the OsmTile as value.
|
|---|
| 22 | * @author Tim Haussmann
|
|---|
| 23 | *
|
|---|
| 24 | */
|
|---|
| 25 | public class TileDB{
|
|---|
| 26 |
|
|---|
| 27 | public static final int PRIO_HIGH = 1;
|
|---|
| 28 | public static final int PRIO_LOW = 2;
|
|---|
| 29 |
|
|---|
| 30 | //Osm tile server address
|
|---|
| 31 | private static final String OSM_TILE_SERVER = "http://tile.openstreetmap.org";
|
|---|
| 32 |
|
|---|
| 33 | //Queue for loading tiles
|
|---|
| 34 | private Vector<OsmTile> iTileQueue = new Vector<OsmTile>();
|
|---|
| 35 |
|
|---|
| 36 | //DB for holding the downloaded tiles
|
|---|
| 37 | private Hashtable<String,OsmTile> iHashTable = new Hashtable<String,OsmTile>(SlippyMapChooserPlugin.MAX_TILES_IN_DB + 50);
|
|---|
| 38 |
|
|---|
| 39 | //the order in that the tiles have been downloaded, needed to delete old ones
|
|---|
| 40 | private Vector<String> iTileOrder = new Vector<String>(SlippyMapChooserPlugin.MAX_TILES_IN_DB + 50);
|
|---|
| 41 |
|
|---|
| 42 | //to update the map after a tile has been loaded
|
|---|
| 43 | private JComponent iSlippyMapChooser;
|
|---|
| 44 |
|
|---|
| 45 | private int[] iMaxLatValues;
|
|---|
| 46 | private int[] iMaxLonValues;
|
|---|
| 47 |
|
|---|
| 48 | /**
|
|---|
| 49 | * Creates the TileDB.
|
|---|
| 50 | * @param aWorldChooser
|
|---|
| 51 | */
|
|---|
| 52 | public TileDB(JComponent aWorldChooser){
|
|---|
| 53 | iSlippyMapChooser = aWorldChooser;
|
|---|
| 54 |
|
|---|
| 55 | iMaxLatValues = new int[20];
|
|---|
| 56 | iMaxLonValues = new int[20];
|
|---|
| 57 | for(int i=0; i<20; i++){
|
|---|
| 58 | iMaxLatValues[i] = OsmMercator.LatToY(OsmMercator.MIN_LAT, i)/OsmTile.HEIGHT;
|
|---|
| 59 | iMaxLonValues[i] = OsmMercator.LonToX(180, i)/OsmTile.WIDTH;
|
|---|
| 60 | }
|
|---|
| 61 |
|
|---|
| 62 | new TileFetcherThread(this);
|
|---|
| 63 | new TileFetcherThread(this);
|
|---|
| 64 | new TileFetcherThread(this);
|
|---|
| 65 | new TileFetcherThread(this);
|
|---|
| 66 | }
|
|---|
| 67 |
|
|---|
| 68 | /**
|
|---|
| 69 | * Creates a OsmTile if it's not in the Hashtable. New created tiles are added to the
|
|---|
| 70 | * end of the download queue (Vector) and the DB (Hashtable). After that the fetching thread is
|
|---|
| 71 | * notified.
|
|---|
| 72 | * @param aZoomLevel the zoomlevel for the tile to create
|
|---|
| 73 | * @param aIndexX the X-index of the tile to create. Must be within [0..2^zoomlevel[
|
|---|
| 74 | * @param aIndexY the Y-index of the tile to create. Must be within [0..2^zoomlevel[
|
|---|
| 75 | * @param aPriority PRIO_HIGH adds this tile at the waiting queue start PRIO_LOW at the end
|
|---|
| 76 | */
|
|---|
| 77 | public synchronized void loadTile(int aZoomLevel, int aIndexX, int aIndexY, int aPriority)
|
|---|
| 78 | {
|
|---|
| 79 |
|
|---|
| 80 | if(aZoomLevel <0 || aIndexX <0 || aIndexY <0){
|
|---|
| 81 | return;
|
|---|
| 82 | }else if(iMaxLonValues[aZoomLevel] <= aIndexX ||
|
|---|
| 83 | iMaxLatValues[aZoomLevel] <= aIndexY ){
|
|---|
| 84 | return;
|
|---|
| 85 | }
|
|---|
| 86 | String key = OsmTile.key(aZoomLevel, aIndexX, aIndexY);
|
|---|
| 87 | OsmTile t = iHashTable.get(key);
|
|---|
| 88 | if(t != null){
|
|---|
| 89 | //if a tile is already in the DB and still in the laoding queue
|
|---|
| 90 | //move it to the beginning of the queue in order to make the currently
|
|---|
| 91 | //visible tiles load first
|
|---|
| 92 | if(iTileQueue.remove(t)){
|
|---|
| 93 | iTileQueue.add(0,t);
|
|---|
| 94 | }
|
|---|
| 95 | return;
|
|---|
| 96 | }
|
|---|
| 97 |
|
|---|
| 98 | t = new OsmTile(aZoomLevel, aIndexX, aIndexY);
|
|---|
| 99 | iHashTable.put(t.toString(),t);
|
|---|
| 100 | iTileOrder.add(0,key);
|
|---|
| 101 |
|
|---|
| 102 | if(aPriority == PRIO_LOW){
|
|---|
| 103 | iTileQueue.addElement(t);
|
|---|
| 104 | }else if(aPriority == PRIO_HIGH){
|
|---|
| 105 | iTileQueue.add(0,t);
|
|---|
| 106 | }else{
|
|---|
| 107 | iTileQueue.addElement(t);
|
|---|
| 108 | }
|
|---|
| 109 |
|
|---|
| 110 | //check if old tiles need to be deleted
|
|---|
| 111 | if(iTileOrder.size() > SlippyMapChooserPlugin.MAX_TILES_IN_DB){
|
|---|
| 112 | for(int i=0; i<SlippyMapChooserPlugin.MAX_TILES_REDUCE_BY; i++){
|
|---|
| 113 | String removedKey = iTileOrder.remove(iTileOrder.size()-1);
|
|---|
| 114 | iHashTable.remove(removedKey);
|
|---|
| 115 | }
|
|---|
| 116 | }
|
|---|
| 117 |
|
|---|
| 118 | //Wake up the fetching threads
|
|---|
| 119 | synchronized (this) {
|
|---|
| 120 | notifyAll();
|
|---|
| 121 | }
|
|---|
| 122 | }
|
|---|
| 123 |
|
|---|
| 124 | /**
|
|---|
| 125 | * Returns the next tile that needs to be loaded. This method is called by the threads
|
|---|
| 126 | * that fetch the tiles. If the download queue is empty the calling threads are waiting.
|
|---|
| 127 | * @return the first tile in the download queue with the same zoom level that is currently displayed or just the first.
|
|---|
| 128 | */
|
|---|
| 129 | private OsmTile getNextTile(){
|
|---|
| 130 | if(iTileQueue.size() > 0){
|
|---|
| 131 | OsmTile t;
|
|---|
| 132 | Enumeration<OsmTile> e = iTileQueue.elements();
|
|---|
| 133 | while(e.hasMoreElements()){
|
|---|
| 134 | t = e.nextElement();
|
|---|
| 135 | if(t.getZoomlevel() == SlippyMapChooser.iZoomlevel){
|
|---|
| 136 | return t;
|
|---|
| 137 | }
|
|---|
| 138 | }
|
|---|
| 139 | return iTileQueue.firstElement();
|
|---|
| 140 | }
|
|---|
| 141 | else{
|
|---|
| 142 | synchronized (this) {
|
|---|
| 143 | try {
|
|---|
| 144 | wait();
|
|---|
| 145 | } catch (InterruptedException e) {
|
|---|
| 146 | e.printStackTrace();
|
|---|
| 147 | }
|
|---|
| 148 | }
|
|---|
| 149 | return iTileQueue.firstElement();
|
|---|
| 150 | }
|
|---|
| 151 | }
|
|---|
| 152 |
|
|---|
| 153 | /**
|
|---|
| 154 | * Access tiles in the TileDB.
|
|---|
| 155 | * @return an Enumeration that holds all OsmTiles currently stored in the TileDB (Hashtable)
|
|---|
| 156 | */
|
|---|
| 157 | public Enumeration<OsmTile> elements() {
|
|---|
| 158 | return iHashTable.elements();
|
|---|
| 159 | }
|
|---|
| 160 |
|
|---|
| 161 | /**
|
|---|
| 162 | * Returns a OsmTile by key
|
|---|
| 163 | * @param aKey
|
|---|
| 164 | * @return
|
|---|
| 165 | */
|
|---|
| 166 | public OsmTile get(String aKey){
|
|---|
| 167 | return iHashTable.get(aKey);
|
|---|
| 168 | }
|
|---|
| 169 |
|
|---|
| 170 | /**
|
|---|
| 171 | * Delete OsmTiles from the TileDB.
|
|---|
| 172 | * @param aTile that is to be deleted.
|
|---|
| 173 | */
|
|---|
| 174 | public void remove(OsmTile aTile){
|
|---|
| 175 | iHashTable.remove(aTile.toString());
|
|---|
| 176 | iTileQueue.removeElement(aTile);
|
|---|
| 177 | }
|
|---|
| 178 |
|
|---|
| 179 | /**
|
|---|
| 180 | * Returns the number of OsmTiles waiting to be fetched from the server.
|
|---|
| 181 | * @return
|
|---|
| 182 | */
|
|---|
| 183 | public int getLoadingQueueSize() {
|
|---|
| 184 | return iTileQueue.size();
|
|---|
| 185 | }
|
|---|
| 186 |
|
|---|
| 187 | /**
|
|---|
| 188 | * Returns the number of tiles stored in the TileDB
|
|---|
| 189 | * @return
|
|---|
| 190 | */
|
|---|
| 191 | public int getCachedTilesSize() {
|
|---|
| 192 | return iHashTable.size();
|
|---|
| 193 | }
|
|---|
| 194 |
|
|---|
| 195 |
|
|---|
| 196 | /***************************************************
|
|---|
| 197 | * Private inner class to fetch the tiles over http
|
|---|
| 198 | * *************************************************
|
|---|
| 199 | */
|
|---|
| 200 |
|
|---|
| 201 | class TileFetcherThread implements Runnable{
|
|---|
| 202 |
|
|---|
| 203 | private TileDB iTileDB;
|
|---|
| 204 | private Thread iThread;
|
|---|
| 205 |
|
|---|
| 206 | public TileFetcherThread(TileDB aTileDB){
|
|---|
| 207 | iTileDB = aTileDB;
|
|---|
| 208 | iThread = new Thread(this);
|
|---|
| 209 | iThread.start();
|
|---|
| 210 | }
|
|---|
| 211 |
|
|---|
| 212 | public void run(){
|
|---|
| 213 |
|
|---|
| 214 | while(true){
|
|---|
| 215 |
|
|---|
| 216 | //get the next tile to load
|
|---|
| 217 | OsmTile t = iTileDB.getNextTile();
|
|---|
| 218 |
|
|---|
| 219 | URL tileUrl = null;
|
|---|
| 220 | try {
|
|---|
| 221 |
|
|---|
| 222 | //build the url to the tile
|
|---|
| 223 | tileUrl = new URL(OSM_TILE_SERVER + t.getRemotePath());
|
|---|
| 224 |
|
|---|
| 225 | //get the tile
|
|---|
| 226 | InputStream in = tileUrl.openStream();
|
|---|
| 227 |
|
|---|
| 228 | //the tile needs the image...
|
|---|
| 229 | t.setImage(ImageIO.read(in));
|
|---|
| 230 |
|
|---|
| 231 | } catch (MalformedURLException e) {
|
|---|
| 232 | // System.out.println("Catched: " + e.getMessage());
|
|---|
| 233 | // e.printStackTrace();
|
|---|
| 234 | t.setImage(null);
|
|---|
| 235 | } catch (IOException e) {
|
|---|
| 236 | // System.out.println("Catched: " + e.getMessage());
|
|---|
| 237 | // e.printStackTrace();
|
|---|
| 238 | t.setImage(null);
|
|---|
| 239 | } catch (Exception e){
|
|---|
| 240 | // System.out.println("Catched: " + e.getMessage());
|
|---|
| 241 | // e.printStackTrace();
|
|---|
| 242 | t.setImage(null);
|
|---|
| 243 | }
|
|---|
| 244 |
|
|---|
| 245 | iTileQueue.removeElement(t);
|
|---|
| 246 | iSlippyMapChooser.repaint();
|
|---|
| 247 | }
|
|---|
| 248 | }
|
|---|
| 249 | }
|
|---|
| 250 | }
|
|---|