]> gitweb.fperrin.net Git - GpsPrune.git/blob - src/tim/prune/gui/map/MapTileManager.java
Never overzoom when initially opening a track
[GpsPrune.git] / src / tim / prune / gui / map / MapTileManager.java
1 package tim.prune.gui.map;
2
3 import java.awt.Image;
4 import java.awt.image.ImageObserver;
5 import java.net.MalformedURLException;
6 import java.net.URL;
7
8 import tim.prune.config.Config;
9
10
11 /**
12  * Class responsible for managing the map tiles,
13  * including invoking the correct memory cacher(s) and/or disk cacher(s)
14  */
15 public class MapTileManager implements ImageObserver
16 {
17         /** Consumer object to inform when tiles received */
18         private TileConsumer _consumer = null;
19         /** Current map source */
20         private MapSource _mapSource = null;
21         /** Array of tile caches, one per layer */
22         private MemTileCacher[] _tempCaches = null;
23         /** Flag for whether to download any tiles or just pull from disk */
24         private boolean _downloadTiles = true;
25         /** Flag for whether to return incomplete images or just pass to tile cache until they're done */
26         private boolean _returnIncompleteImages = false;
27         /** Number of layers */
28         private int _numLayers = -1;
29         /** Current zoom level */
30         private int _zoom = 0;
31         /** Number of tiles in each direction for this zoom level */
32         private int _numTileIndices = 1;
33
34
35         /**
36          * Constructor
37          * @param inConsumer consumer object to be notified
38          */
39         public MapTileManager(TileConsumer inConsumer)
40         {
41                 _consumer = inConsumer;
42         }
43
44         /**
45          * Recentre the map
46          * @param inZoom zoom level
47          * @param inTileX x coord of central tile
48          * @param inTileY y coord of central tile
49          */
50         public void centreMap(int inZoom, int inTileX, int inTileY)
51         {
52                 setZoom(inZoom);
53                 // Pass params onto all memory cachers
54                 if (_tempCaches != null) {
55                         for (int i=0; i<_tempCaches.length; i++) {
56                                 _tempCaches[i].centreMap(inZoom, inTileX, inTileY);
57                         }
58                 }
59         }
60
61         /** @param inZoom zoom level to set */
62         public void setZoom(int inZoom)
63         {
64                 _zoom = inZoom;
65                 // Calculate number of tiles = 2^^zoom
66                 _numTileIndices = 1 << _zoom;
67         }
68
69         /**
70          * @return true if zoom is too high for tiles
71          */
72         public boolean isOverzoomed()
73         {
74                 return _zoom > getMaxZoomLevel();
75         }
76
77         /**
78          * @return the maximum useable zoom level for tiles
79          */
80         public int getMaxZoomLevel()
81         {
82                 // Ask current map source what maximum zoom is
83                 int maxZoom = (_mapSource == null?0:_mapSource.getMaxZoomLevel());
84                 return maxZoom;
85
86         }
87
88         /**
89          * Enable or disable tile downloading
90          * @param inEnabled true to enable downloading, false to just get tiles from disk
91          */
92         public void enableTileDownloading(boolean inEnabled)
93         {
94                 _downloadTiles = inEnabled;
95         }
96
97         /** Configure to return incomplete images instead of going via caches (and another call) */
98         public void setReturnIncompleteImages()
99         {
100                 _returnIncompleteImages = true;
101         }
102
103         /**
104          * Clear all the memory caches due to changed config / zoom
105          */
106         public void clearMemoryCaches()
107         {
108                 int numLayers = _mapSource.getNumLayers();
109                 if (_tempCaches == null || _tempCaches.length != numLayers)
110                 {
111                         // Cachers don't match, so need to create the right number of them
112                         _tempCaches = new MemTileCacher[numLayers];
113                         for (int i=0; i<numLayers; i++) {
114                                 _tempCaches[i] = new MemTileCacher();
115                         }
116                 }
117                 else {
118                         // Cachers already there, just need to be cleared
119                         for (int i=0; i<numLayers; i++) {
120                                 _tempCaches[i].clearAll();
121                         }
122                 }
123         }
124
125         /**
126          * @param inSourceNum selected map source index
127          */
128         public void setMapSource(int inSourceNum)
129         {
130                 setMapSource(MapSourceLibrary.getSource(inSourceNum));
131         }
132
133         /**
134          * @param inMapSource selected map source
135          */
136         public void setMapSource(MapSource inMapSource)
137         {
138                 _mapSource = inMapSource;
139                 if (_mapSource == null) {_mapSource = MapSourceLibrary.getSource(0);}
140                 clearMemoryCaches();
141                 _numLayers = _mapSource.getNumLayers();
142         }
143
144         /**
145          * @return the number of layers in the map
146          */
147         public int getNumLayers()
148         {
149                 return _numLayers;
150         }
151
152         /**
153          * Get a tile from the currently selected map source
154          * @param inLayer layer number, starting from 0
155          * @param inX x index of tile
156          * @param inY y index of tile
157          * @param inDownloadIfNecessary true to download the file if it's not available
158          * @return selected tile if already loaded, or null otherwise
159          */
160         public Image getTile(int inLayer, int inX, int inY, boolean inDownloadIfNecessary)
161         {
162                 if (inY < 0 || inY >= _numTileIndices) return null;
163                 // Wrap tile indices which are too big or too small
164                 inX = ((inX % _numTileIndices) + _numTileIndices) % _numTileIndices;
165
166                 // Check first in memory cache for tile
167                 Image tileImage = null;
168                 MemTileCacher tempCache = null;
169                 if (_tempCaches != null)
170                 {
171                         tempCache = _tempCaches[inLayer]; // Should probably guard array indexes here
172                         tileImage = tempCache.getTile(inX, inY);
173                         if (tileImage != null) {
174                                 //System.out.println("Got tile from memory: " + inX + ", " + inY);
175                                 return tileImage;
176                         }
177                 }
178
179                 // Tile wasn't in memory, but maybe it's in disk cache (if there is one)
180                 String diskCachePath = Config.getConfigString(Config.KEY_DISK_CACHE);
181                 boolean useDisk = (diskCachePath != null);
182                 boolean onlineMode = Config.getConfigBoolean(Config.KEY_ONLINE_MODE);
183                 MapTile mapTile = null;
184                 if (useDisk)
185                 {
186                         // Get the map tile from cache
187                         mapTile = DiskTileCacher.getTile(diskCachePath, _mapSource.makeFilePath(inLayer, _zoom, inX, inY));
188                         if (mapTile != null && mapTile.getImage() != null)
189                         {
190                                 tileImage = mapTile.getImage();
191                                 if (_returnIncompleteImages) {return tileImage;}
192                                 // Pass tile to memory cache
193                                 if (tempCache != null) {
194                                         tempCache.setTile(tileImage, inX, inY, _zoom);
195                                 }
196                                 tileImage.getWidth(this); // trigger the load from file
197                         }
198                 }
199                 // Maybe we've got an image now, maybe it's expired
200                 final boolean shouldDownload = (tileImage == null || mapTile == null || mapTile.isExpired());
201
202                 // If we're online then try to download the tile
203                 if (onlineMode && _downloadTiles && inDownloadIfNecessary && shouldDownload)
204                 {
205                         try
206                         {
207                                 URL tileUrl = new URL(_mapSource.makeURL(inLayer, _zoom, inX, inY));
208                                 if (useDisk)
209                                 {
210                                         DiskTileCacher.saveTile(tileUrl, diskCachePath,
211                                                 _mapSource.makeFilePath(inLayer, _zoom, inX, inY), this);
212                                         // Image will now be copied directly from URL stream to disk cache
213                                 }
214                                 else
215                                 {
216                                         // Load image asynchronously, using observer
217                                         // In order to set the http user agent, need to use a TileDownloader instead
218                                         TileDownloader.triggerLoad(this, tileUrl, inLayer, inX, inY, _zoom);
219                                 }
220                         }
221                         catch (MalformedURLException urle) {} // ignore
222                         catch (CacheFailure cf) {
223                                 _consumer.reportCacheFailure();
224                         }
225                 }
226                 return tileImage;
227         }
228
229         /**
230          * Method called by image loader to inform of updates to the tiles
231          * @param img the image
232          * @param infoflags flags describing how much of the image is known
233          * @param x ignored
234          * @param y ignored
235          * @param width ignored
236          * @param height ignored
237          * @return false to carry on loading, true to stop
238          */
239         public boolean imageUpdate(Image img, int infoflags, int x, int y, int width, int height)
240         {
241                 boolean loaded = (infoflags & ImageObserver.ALLBITS) > 0;
242                 boolean error = (infoflags & ImageObserver.ERROR) > 0;
243                 if (loaded || error) {
244                         _consumer.tilesUpdated(loaded);
245                 }
246                 return !loaded;
247         }
248
249         /**
250          * Callback method from TileDownloader to let us know that an image has been loaded
251          * @param inTile Loaded Image object
252          * @param inLayer layer index from 0
253          * @param inX x coordinate of tile
254          * @param inY y coordinate of tile
255          * @param inZoom zoom level of loaded image
256          */
257         public void notifyImageLoaded(Image inTile, int inLayer, int inX, int inY, int inZoom)
258         {
259                 if (inTile != null && _tempCaches != null)
260                 {
261                         MemTileCacher tempCache = _tempCaches[inLayer]; // Should probably guard against nulls and array indexes here
262                         if (tempCache.getTile(inX, inY) == null)
263                         {
264                                 // Check with cache that the zoom level is still valid
265                                 tempCache.setTile(inTile, inX, inY, inZoom);
266                                 inTile.getWidth(this); // trigger imageUpdate when image is ready
267                         }
268                 }
269                 else if (inTile != null) {
270                         inTile.getWidth(this);
271                 }
272         }
273 }