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