diff --git src/org/openstreetmap/gui/jmapviewer/Tile.java src/org/openstreetmap/gui/jmapviewer/Tile.java
index edd6554..bfd1eaa 100644
|
|
public class Tile {
|
331 | 331 | loading = false; |
332 | 332 | loaded = true; |
333 | 333 | } |
| 334 | |
| 335 | /** |
| 336 | * |
| 337 | * @return TileSource from which this tile comes |
| 338 | */ |
| 339 | public TileSource getTileSource() { |
| 340 | return source; |
| 341 | } |
334 | 342 | } |
diff --git src/org/openstreetmap/gui/jmapviewer/interfaces/TileSource.java src/org/openstreetmap/gui/jmapviewer/interfaces/TileSource.java
index b8680e8..d53d1d2 100644
|
|
|
2 | 2 | package org.openstreetmap.gui.jmapviewer.interfaces; |
3 | 3 | |
4 | 4 | import java.io.IOException; |
| 5 | import java.util.List; |
| 6 | import java.util.Map; |
5 | 7 | |
6 | 8 | import org.openstreetmap.gui.jmapviewer.JMapViewer; |
7 | 9 | |
… |
… |
public interface TileSource extends Attributed {
|
155 | 157 | * @return [MIN_LAT..MAX_LAT] |
156 | 158 | */ |
157 | 159 | double tileYToLat(int y, int zoom); |
| 160 | |
| 161 | /** |
| 162 | * Determines, if the returned data from TileSource represent "no tile at this zoom level" situation. Detection |
| 163 | * algorithms differ per TileSource, so each TileSource should implement each own specific way. |
| 164 | * |
| 165 | * @param headers HTTP headers from response from TileSource server |
| 166 | * @param statusCode HTTP status code |
| 167 | * @param content byte array representing the data returned from the server |
| 168 | * @return true, if "no tile at this zoom level" situation detected |
| 169 | */ |
| 170 | public boolean isNoTileAtZoom(Map<String, List<String>> headers, int statusCode, byte[] content); |
158 | 171 | } |
diff --git src/org/openstreetmap/gui/jmapviewer/tilesources/AbstractTileSource.java src/org/openstreetmap/gui/jmapviewer/tilesources/AbstractTileSource.java
index 66456a6..bfce358 100644
|
|
|
2 | 2 | package org.openstreetmap.gui.jmapviewer.tilesources; |
3 | 3 | |
4 | 4 | import java.awt.Image; |
| 5 | import java.util.List; |
| 6 | import java.util.Map; |
5 | 7 | |
6 | | import org.openstreetmap.gui.jmapviewer.interfaces.TileSource; |
7 | 8 | import org.openstreetmap.gui.jmapviewer.Coordinate; |
| 9 | import org.openstreetmap.gui.jmapviewer.interfaces.TileSource; |
8 | 10 | |
9 | 11 | abstract public class AbstractTileSource implements TileSource { |
10 | 12 | |
… |
… |
abstract public class AbstractTileSource implements TileSource {
|
74 | 76 | this.termsOfUseURL = termsOfUseURL; |
75 | 77 | } |
76 | 78 | |
| 79 | public boolean isNoTileAtZoom(Map<String, List<String>> headers, int statusCode, byte[] content) { |
| 80 | // default handler - when HTTP 404 is returned, then treat this situation as no tile at this zoom level |
| 81 | return statusCode == 404; |
| 82 | } |
77 | 83 | } |
diff --git src/org/openstreetmap/gui/jmapviewer/tilesources/BingAerialTileSource.java src/org/openstreetmap/gui/jmapviewer/tilesources/BingAerialTileSource.java
index f8723b6..ee034cf 100644
|
|
import java.net.URL;
|
9 | 9 | import java.util.ArrayList; |
10 | 10 | import java.util.List; |
11 | 11 | import java.util.Locale; |
| 12 | import java.util.Map; |
12 | 13 | import java.util.concurrent.Callable; |
13 | 14 | import java.util.concurrent.ExecutionException; |
14 | 15 | import java.util.concurrent.Executors; |
… |
… |
public class BingAerialTileSource extends AbstractTMSTileSource {
|
302 | 303 | } |
303 | 304 | return k.toString(); |
304 | 305 | } |
| 306 | |
| 307 | @Override |
| 308 | public boolean isNoTileAtZoom(Map<String, List<String>> headers, int statusCode, byte[] content) { |
| 309 | List<String> headerValues = headers.get("X-VE-Tile-Info"); |
| 310 | if (headerValues != null && headerValues.contains("no-tile")) { |
| 311 | return true; |
| 312 | } |
| 313 | return false; |
| 314 | } |
305 | 315 | } |
diff --git src/org/openstreetmap/josm/actions/AddImageryLayerAction.java src/org/openstreetmap/josm/actions/AddImageryLayerAction.java
index 422e840..f320dc4 100644
|
|
import java.awt.GridBagLayout;
|
9 | 9 | import java.awt.event.ActionEvent; |
10 | 10 | import java.io.IOException; |
11 | 11 | import java.net.MalformedURLException; |
| 12 | import java.util.Arrays; |
12 | 13 | |
13 | 14 | import javax.swing.JComboBox; |
14 | 15 | import javax.swing.JOptionPane; |
… |
… |
public class AddImageryLayerAction extends JosmAction implements AdaptableAction
|
149 | 150 | // never enable blacklisted entries. Do not add same imagery layer twice (fix #2519) |
150 | 151 | if (info.isBlacklisted() /*|| isLayerAlreadyPresent()*/) { // FIXME check disabled to allow several instances with different settings (see #7981) |
151 | 152 | setEnabled(false); |
152 | | } else if (info.getImageryType() == ImageryType.TMS || info.getImageryType() == ImageryType.BING || info.getImageryType() == ImageryType.SCANEX) { |
| 153 | } else if (Arrays.asList( |
| 154 | ImageryType.BING, |
| 155 | ImageryType.MAPBOX, |
| 156 | ImageryType.SCANEX, |
| 157 | ImageryType.TMS).contains(info.getImageryType())) { |
153 | 158 | setEnabled(true); |
154 | 159 | } else if (Main.isDisplayingMapView() && !Main.map.mapView.getAllLayers().isEmpty()) { |
155 | 160 | setEnabled(true); |
diff --git src/org/openstreetmap/josm/data/cache/JCSCachedTileLoaderJob.java src/org/openstreetmap/josm/data/cache/JCSCachedTileLoaderJob.java
index b4a0fbd..81b3a3a 100644
|
|
import java.net.HttpURLConnection;
|
9 | 9 | import java.net.URL; |
10 | 10 | import java.net.URLConnection; |
11 | 11 | import java.util.HashSet; |
| 12 | import java.util.List; |
12 | 13 | import java.util.Map; |
13 | 14 | import java.util.Random; |
14 | 15 | import java.util.Set; |
… |
… |
public abstract class JCSCachedTileLoaderJob<K, V extends CacheEntry> implements
|
223 | 224 | * |
224 | 225 | * @return cache object as empty, regardless of what remote resource has returned (ex. based on headers) |
225 | 226 | */ |
226 | | protected boolean cacheAsEmpty() { |
| 227 | protected boolean cacheAsEmpty(Map<String, List<String>> headers, int statusCode, byte[] content) { |
227 | 228 | return false; |
228 | 229 | } |
229 | 230 | |
… |
… |
public abstract class JCSCachedTileLoaderJob<K, V extends CacheEntry> implements
|
328 | 329 | log.log(Level.FINE, "JCS - cache entry verified using HEAD request: {0}", getUrl()); |
329 | 330 | return true; |
330 | 331 | } |
331 | | URLConnection urlConn = getURLConnection(); |
| 332 | HttpURLConnection urlConn = getURLConnection(); |
332 | 333 | |
333 | 334 | if (isObjectLoadable() && |
334 | 335 | (now - attributes.getLastModification()) <= ABSOLUTE_EXPIRE_TIME_LIMIT) { |
… |
… |
public abstract class JCSCachedTileLoaderJob<K, V extends CacheEntry> implements
|
337 | 338 | if (isObjectLoadable() && attributes.getEtag() != null) { |
338 | 339 | urlConn.addRequestProperty("If-None-Match", attributes.getEtag()); |
339 | 340 | } |
340 | | if (urlConn instanceof HttpURLConnection && ((HttpURLConnection)urlConn).getResponseCode() == 304) { |
| 341 | if (urlConn.getResponseCode() == 304) { |
341 | 342 | // If isModifiedSince or If-None-Match has been set |
342 | 343 | // and the server answers with a HTTP 304 = "Not Modified" |
343 | 344 | log.log(Level.FINE, "JCS - IfModifiedSince/Etag test: local version is up to date: {0}", getUrl()); |
… |
… |
public abstract class JCSCachedTileLoaderJob<K, V extends CacheEntry> implements
|
358 | 359 | attributes = parseHeaders(urlConn); |
359 | 360 | |
360 | 361 | for (int i = 0; i < 5; ++i) { |
361 | | if (urlConn instanceof HttpURLConnection && ((HttpURLConnection)urlConn).getResponseCode() == 503) { |
| 362 | if (urlConn.getResponseCode() == 503) { |
362 | 363 | Thread.sleep(5000+(new Random()).nextInt(5000)); |
363 | 364 | continue; |
364 | 365 | } |
365 | 366 | byte[] raw = read(urlConn); |
366 | 367 | |
367 | | if (!cacheAsEmpty() && raw != null && raw.length > 0) { |
| 368 | if (!cacheAsEmpty(urlConn.getHeaderFields(), urlConn.getResponseCode(), raw) && |
| 369 | raw != null && raw.length > 0) { |
368 | 370 | cacheData = createCacheEntry(raw); |
369 | 371 | cache.put(getCacheKey(), cacheData, attributes); |
370 | 372 | log.log(Level.FINE, "JCS - downloaded key: {0}, length: {1}, url: {2}", |
… |
… |
public abstract class JCSCachedTileLoaderJob<K, V extends CacheEntry> implements
|
399 | 401 | |
400 | 402 | private CacheEntryAttributes parseHeaders(URLConnection urlConn) { |
401 | 403 | CacheEntryAttributes ret = new CacheEntryAttributes(); |
402 | | ret.setNoTileAtZoom("no-tile".equals(urlConn.getHeaderField("X-VE-Tile-Info"))); |
403 | 404 | |
404 | 405 | Long lng = urlConn.getExpiration(); |
405 | 406 | if (lng.equals(0L)) { |
diff --git src/org/openstreetmap/josm/data/imagery/ImageryInfo.java src/org/openstreetmap/josm/data/imagery/ImageryInfo.java
index e3c813f..1f142e7 100644
|
|
public class ImageryInfo implements Comparable<ImageryInfo>, Attributed {
|
51 | 51 | /** TMS entry for Russian company <a href="https://wiki.openstreetmap.org/wiki/WikiProject_Russia/kosmosnimki">ScanEx</a>. **/ |
52 | 52 | SCANEX("scanex"), |
53 | 53 | /** A WMS endpoint entry only stores the WMS server info, without layer, which are chosen later by the user. **/ |
54 | | WMS_ENDPOINT("wms_endpoint"); |
| 54 | WMS_ENDPOINT("wms_endpoint"), |
| 55 | /** TMS entry for Mapbox, so we can have different TileSource class for them*/ |
| 56 | MAPBOX("mapbox"); |
| 57 | |
55 | 58 | |
56 | 59 | private final String typeString; |
57 | 60 | |
diff --git src/org/openstreetmap/josm/data/imagery/TMSCachedTileLoaderJob.java src/org/openstreetmap/josm/data/imagery/TMSCachedTileLoaderJob.java
index a627c90..e6ddb3f 100644
|
|
package org.openstreetmap.josm.data.imagery;
|
4 | 4 | import java.io.ByteArrayInputStream; |
5 | 5 | import java.io.IOException; |
6 | 6 | import java.net.URL; |
| 7 | import java.util.List; |
7 | 8 | import java.util.HashSet; |
8 | 9 | import java.util.Map; |
9 | 10 | import java.util.Set; |
… |
… |
public class TMSCachedTileLoaderJob extends JCSCachedTileLoaderJob<String, Buffe
|
193 | 194 | if (cacheData != null) { |
194 | 195 | byte[] content = cacheData.getContent(); |
195 | 196 | try { |
196 | | return content != null || cacheData.getImage() != null || cacheAsEmpty(); |
| 197 | return content != null || cacheData.getImage() != null || isNoTileAtZoom(); |
197 | 198 | } catch (IOException e) { |
198 | 199 | log.log(Level.WARNING, "JCS TMS - error loading from cache for tile {0}: {1}", new Object[] {tile.getKey(), e.getMessage()}); |
199 | 200 | } |
… |
… |
public class TMSCachedTileLoaderJob extends JCSCachedTileLoaderJob<String, Buffe
|
206 | 207 | } |
207 | 208 | |
208 | 209 | @Override |
209 | | protected boolean cacheAsEmpty() { |
210 | | return isNoTileAtZoom(); |
| 210 | protected boolean cacheAsEmpty(Map<String, List<String>> headers, int statusCode, byte[] content) { |
| 211 | if (tile.getTileSource().isNoTileAtZoom(headers, statusCode, content)) { |
| 212 | attributes.setNoTileAtZoom(true); |
| 213 | return true; |
| 214 | } |
| 215 | return false; |
211 | 216 | } |
212 | 217 | |
213 | 218 | private boolean handleNoTileAtZoom() { |
… |
… |
public class TMSCachedTileLoaderJob extends JCSCachedTileLoaderJob<String, Buffe
|
315 | 320 | |
316 | 321 | @Override |
317 | 322 | protected boolean handleNotFound() { |
| 323 | if (tile.getSource().isNoTileAtZoom(null, 404, null)) { |
318 | 324 | tile.setError("No tile at this zoom level"); |
319 | 325 | tile.putValue("tile-info", "no-tile"); |
320 | 326 | return true; |
321 | 327 | } |
| 328 | return false; |
| 329 | } |
322 | 330 | |
323 | 331 | /** |
324 | 332 | * For TMS use BaseURL as settings discovery, so for different paths, we will have different settings (useful for developer servers) |
diff --git src/org/openstreetmap/josm/gui/layer/ImageryLayer.java src/org/openstreetmap/josm/gui/layer/ImageryLayer.java
index a543a39..9adaac1 100644
|
|
import java.awt.image.ConvolveOp;
|
21 | 21 | import java.awt.image.Kernel; |
22 | 22 | import java.text.AttributedCharacterIterator; |
23 | 23 | import java.text.AttributedString; |
| 24 | import java.util.Arrays; |
24 | 25 | import java.util.Hashtable; |
25 | 26 | import java.util.List; |
26 | 27 | import java.util.Map; |
… |
… |
public abstract class ImageryLayer extends Layer {
|
152 | 153 | public static ImageryLayer create(ImageryInfo info) { |
153 | 154 | if (info.getImageryType() == ImageryType.WMS || info.getImageryType() == ImageryType.HTML) |
154 | 155 | return new WMSLayer(info); |
155 | | else if (info.getImageryType() == ImageryType.TMS || info.getImageryType() == ImageryType.BING || info.getImageryType() == ImageryType.SCANEX) |
| 156 | else if (Arrays.asList( |
| 157 | ImageryType.BING, |
| 158 | ImageryType.MAPBOX, |
| 159 | ImageryType.SCANEX, |
| 160 | ImageryType.TMS).contains(info.getImageryType())) |
156 | 161 | return new TMSLayer(info); |
157 | 162 | else throw new AssertionError(); |
158 | 163 | } |
diff --git src/org/openstreetmap/josm/gui/layer/TMSLayer.java src/org/openstreetmap/josm/gui/layer/TMSLayer.java
index c749488..07c3269 100644
|
|
import org.openstreetmap.gui.jmapviewer.interfaces.TileLoader;
|
48 | 48 | import org.openstreetmap.gui.jmapviewer.interfaces.TileLoaderListener; |
49 | 49 | import org.openstreetmap.gui.jmapviewer.interfaces.TileSource; |
50 | 50 | import org.openstreetmap.gui.jmapviewer.tilesources.BingAerialTileSource; |
| 51 | import org.openstreetmap.gui.jmapviewer.tilesources.MapboxTileSource; |
51 | 52 | import org.openstreetmap.gui.jmapviewer.tilesources.ScanexTileSource; |
52 | 53 | import org.openstreetmap.gui.jmapviewer.tilesources.TMSTileSource; |
53 | 54 | import org.openstreetmap.gui.jmapviewer.tilesources.TemplatedTMSTileSource; |
… |
… |
public class TMSLayer extends ImageryLayer implements ImageObserver, TileLoaderL
|
340 | 341 | info.getCookies()); |
341 | 342 | info.setAttribution(t); |
342 | 343 | return t; |
343 | | } else if (info.getImageryType() == ImageryType.BING) |
| 344 | } else if (info.getImageryType() == ImageryType.BING) { |
344 | 345 | return new CachedAttributionBingAerialTileSource(info.getId()); |
345 | | else if (info.getImageryType() == ImageryType.SCANEX) { |
| 346 | } else if (info.getImageryType() == ImageryType.SCANEX) { |
346 | 347 | return new ScanexTileSource(info.getName(), info.getUrl(), info.getId(), info.getMaxZoom()); |
| 348 | } else if (info.getImageryType() == ImageryType.MAPBOX) { |
| 349 | return new MapboxTileSource(info.getName(), info.getUrl(), info.getId(), info.getMaxZoom()); |
347 | 350 | } |
348 | 351 | return null; |
349 | 352 | } |