source: josm/trunk/src/org/openstreetmap/josm/data/imagery/TMSCachedTileLoaderJob.java@ 8174

Last change on this file since 8174 was 8174, checked in by bastiK, 9 years ago

see #11216 - fixes NPE when BingAttribution is not loaded, and for not showing tiles at overzoom (patch by wiktorn)

File size: 9.0 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.data.imagery;
3
4import java.io.ByteArrayInputStream;
5import java.io.IOException;
6import java.net.URL;
7import java.util.Map;
8import java.util.concurrent.Executor;
9import java.util.concurrent.LinkedBlockingDeque;
10import java.util.concurrent.ThreadPoolExecutor;
11import java.util.concurrent.TimeUnit;
12import java.util.logging.Level;
13import java.util.logging.Logger;
14
15import org.apache.commons.jcs.access.behavior.ICacheAccess;
16import org.openstreetmap.gui.jmapviewer.FeatureAdapter;
17import org.openstreetmap.gui.jmapviewer.Tile;
18import org.openstreetmap.gui.jmapviewer.interfaces.TileJob;
19import org.openstreetmap.gui.jmapviewer.interfaces.TileLoaderListener;
20import org.openstreetmap.gui.jmapviewer.interfaces.TileSource;
21import org.openstreetmap.gui.jmapviewer.tilesources.AbstractTMSTileSource;
22import org.openstreetmap.josm.data.cache.BufferedImageCacheEntry;
23import org.openstreetmap.josm.data.cache.CacheEntry;
24import org.openstreetmap.josm.data.cache.ICachedLoaderListener;
25import org.openstreetmap.josm.data.cache.JCSCachedTileLoaderJob;
26import org.openstreetmap.josm.data.preferences.IntegerProperty;
27
28/**
29 * @author Wiktor Niesiobędzki
30 *
31 * Class bridging TMS requests to JCS cache requests
32 *
33 */
34public class TMSCachedTileLoaderJob extends JCSCachedTileLoaderJob<String, BufferedImageCacheEntry> implements TileJob, ICachedLoaderListener {
35 private static final Logger log = FeatureAdapter.getLogger(TMSCachedTileLoaderJob.class.getCanonicalName());
36 private Tile tile;
37 private TileLoaderListener listener;
38 private volatile URL url;
39
40 /**
41 * overrides the THREAD_LIMIT in superclass, as we want to have separate limit and pool for TMS
42 */
43 public static IntegerProperty THREAD_LIMIT = new IntegerProperty("imagery.tms.tmsloader.maxjobs", 25);
44 /**
45 * separate from JCS thread pool for TMS loader, so we can have different thread pools for default JCS
46 * and for TMS imagery
47 */
48 private static ThreadPoolExecutor DOWNLOAD_JOB_DISPATCHER = new ThreadPoolExecutor(
49 THREAD_LIMIT.get().intValue(), // keep the thread number constant
50 THREAD_LIMIT.get().intValue(), // do not this number of threads
51 30, // keepalive for thread
52 TimeUnit.SECONDS,
53 // make queue of LIFO type - so recently requested tiles will be loaded first (assuming that these are which user is waiting to see)
54 new LinkedBlockingDeque<Runnable>(5) {
55 /* keep the queue size fairly small, we do not want to
56 download a lot of tiles, that user is not seeing anyway */
57 @Override
58 public boolean offer(Runnable t) {
59 return super.offerFirst(t);
60 }
61
62 @Override
63 public Runnable remove() {
64 return super.removeFirst();
65 }
66 }
67 );
68
69 /**
70 * Constructor for creating a job, to get a specific tile from cache
71 * @param listener
72 * @param tile to be fetched from cache
73 * @param cache object
74 * @param connectTimeout when connecting to remote resource
75 * @param readTimeout when connecting to remote resource
76 * @param headers to be sent together with request
77 */
78 public TMSCachedTileLoaderJob(TileLoaderListener listener, Tile tile, ICacheAccess<String, BufferedImageCacheEntry> cache, int connectTimeout, int readTimeout,
79 Map<String, String> headers) {
80 super(cache, connectTimeout, readTimeout, headers);
81 this.tile = tile;
82 this.listener = listener;
83 }
84
85 @Override
86 public Tile getTile() {
87 return getCachedTile();
88 }
89
90 @Override
91 public String getCacheKey() {
92 if (tile != null)
93 return tile.getKey();
94 return null;
95 }
96
97 /*
98 * this doesn't needs to be synchronized, as it's not that costly to keep only one execution
99 * in parallel, but URL creation and Tile.getUrl() are costly and are not needed when fetching
100 * data from cache
101 *
102 * We need to have static url value for TileLoaderJob, as for some TileSources we might get different
103 * URL's each call we made (servers switching), and URL's are used below as a key for duplicate detection
104 *
105 */
106 @Override
107 public URL getUrl() {
108 if (url == null) {
109 try {
110 synchronized (this) {
111 if (url == null)
112 url = new URL(tile.getUrl());
113 }
114 } catch (IOException e) {
115 log.log(Level.WARNING, "JCS TMS Cache - error creating URL for tile {0}: {1}", new Object[] {tile.getKey(), e.getMessage()});
116 log.log(Level.INFO, "Exception: ", e);
117 }
118 }
119 return url;
120 }
121
122 @Override
123 public boolean isObjectLoadable() {
124 if (cacheData != null) {
125 byte[] content = cacheData.getContent();
126 try {
127 return content != null || cacheData.getImage() != null || cacheAsEmpty();
128 } catch (IOException e) {
129 log.log(Level.WARNING, "JCS TMS - error loading from cache for tile {0}: {1}", new Object[] {tile.getKey(), e.getMessage()});
130 }
131 }
132 return false;
133 }
134
135 @Override
136 protected boolean cacheAsEmpty() {
137 if (attributes != null && attributes.isNoTileAtZoom()) {
138 // do not remove file - keep the information, that there is no tile, for further requests
139 // the code above will check, if this information is still valid
140 log.log(Level.FINE, "JCS TMS - Tile valid, but no file, as no tiles at this level {0}", tile);
141 tile.setError("No tile at this zoom level");
142 tile.putValue("tile-info", "no-tile");
143 return true;
144 }
145 return false; // as there is no other cache to cache the Tile, also cache other empty requests
146 }
147
148 @Override
149 protected Executor getDownloadExecutor() {
150 return DOWNLOAD_JOB_DISPATCHER;
151 }
152
153 public void submit() {
154 tile.initLoading();
155 super.submit(this);
156 }
157
158 @Override
159 public void loadingFinished(CacheEntry object, boolean success) {
160 try {
161 loadTile(object, success);
162 if (listener != null) {
163 listener.tileLoadingFinished(tile, success);
164 }
165 } catch (IOException e) {
166 log.log(Level.WARNING, "JCS TMS - error loading object for tile {0}: {1}", new Object[] {tile.getKey(), e.getMessage()});
167 tile.setError(e.getMessage());
168 tile.setLoaded(false);
169 if (listener != null) {
170 listener.tileLoadingFinished(tile, false);
171 }
172 }
173 }
174
175 /**
176 * Method for getting the tile from cache only, without trying to reach remote resource
177 * @return tile or null, if nothing (useful) was found in cache
178 */
179 public Tile getCachedTile() {
180 BufferedImageCacheEntry data = get();
181 if (isObjectLoadable()) {
182 try {
183 loadTile(data);
184 return tile;
185 } catch (IOException e) {
186 log.log(Level.WARNING, "JCS TMS - error loading object for tile {0}: {1}", new Object[] {tile.getKey(), e.getMessage()});
187 return null;
188 }
189
190 } else {
191 return null;
192 }
193 }
194
195 // loads tile when calling back from cache
196 private void loadTile(CacheEntry object, boolean success) throws IOException {
197 tile.finishLoading();
198 if (object != null) {
199 byte[] content = object.getContent();
200 if (content != null && content.length > 0) {
201 tile.loadImage(new ByteArrayInputStream(content));
202 }
203 }
204 if (!success) {
205 tile.setError("Problem loading tile");
206 }
207 }
208
209 // loads tile when geting stright from cache
210 private void loadTile(BufferedImageCacheEntry object) throws IOException {
211 tile.finishLoading();
212 if (cacheAsEmpty() || object != null) { // if cache as empty object, do not try to load image
213 if (object.getImage() != null) {
214 tile.setImage(object.getImage());
215 }
216 }
217 }
218
219 @Override
220 protected boolean handleNotFound() {
221 tile.setError("No tile at this zoom level");
222 tile.putValue("tile-info", "no-tile");
223 return true;
224 }
225
226 /**
227 * For TMS use BaseURL as settings discovery, so for different paths, we will have different settings (useful for developer servers)
228 *
229 * @return base URL of TMS or server url as defined in super class
230 */
231 @Override
232 protected String getServerKey() {
233 TileSource ts = tile.getSource();
234 if (ts instanceof AbstractTMSTileSource) {
235 return ((AbstractTMSTileSource) ts).getBaseUrl();
236 }
237 return super.getServerKey();
238 }
239
240 @Override
241 protected BufferedImageCacheEntry createCacheEntry(byte[] content) {
242 return new BufferedImageCacheEntry(content);
243 }
244}
Note: See TracBrowser for help on using the repository browser.