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 | }
|
---|