Ticket #10454: mapbox_no_tiles_handling.patch

File mapbox_no_tiles_handling.patch, 14.7 KB (added by wiktorn, 10 years ago)
  • src/org/openstreetmap/gui/jmapviewer/Tile.java

    diff --git src/org/openstreetmap/gui/jmapviewer/Tile.java src/org/openstreetmap/gui/jmapviewer/Tile.java
    index edd6554..bfd1eaa 100644
    public class Tile {  
    331331        loading = false;
    332332        loaded = true;
    333333    }
     334
     335    /**
     336     *
     337     * @return TileSource from which this tile comes
     338     */
     339    public TileSource getTileSource() {
     340        return source;
     341    }
    334342}
  • src/org/openstreetmap/gui/jmapviewer/interfaces/TileSource.java

    diff --git src/org/openstreetmap/gui/jmapviewer/interfaces/TileSource.java src/org/openstreetmap/gui/jmapviewer/interfaces/TileSource.java
    index b8680e8..d53d1d2 100644
     
    22package org.openstreetmap.gui.jmapviewer.interfaces;
    33
    44import java.io.IOException;
     5import java.util.List;
     6import java.util.Map;
    57
    68import org.openstreetmap.gui.jmapviewer.JMapViewer;
    79
    public interface TileSource extends Attributed {  
    155157     * @return [MIN_LAT..MAX_LAT]
    156158     */
    157159    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);
    158171}
  • src/org/openstreetmap/gui/jmapviewer/tilesources/AbstractTileSource.java

    diff --git src/org/openstreetmap/gui/jmapviewer/tilesources/AbstractTileSource.java src/org/openstreetmap/gui/jmapviewer/tilesources/AbstractTileSource.java
    index 66456a6..bfce358 100644
     
    22package org.openstreetmap.gui.jmapviewer.tilesources;
    33
    44import java.awt.Image;
     5import java.util.List;
     6import java.util.Map;
    57
    6 import org.openstreetmap.gui.jmapviewer.interfaces.TileSource;
    78import org.openstreetmap.gui.jmapviewer.Coordinate;
     9import org.openstreetmap.gui.jmapviewer.interfaces.TileSource;
    810
    911abstract public class AbstractTileSource implements TileSource {
    1012
    abstract public class AbstractTileSource implements TileSource {  
    7476        this.termsOfUseURL = termsOfUseURL;
    7577    }
    7678
     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    }
    7783}
  • src/org/openstreetmap/gui/jmapviewer/tilesources/BingAerialTileSource.java

    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;  
    99import java.util.ArrayList;
    1010import java.util.List;
    1111import java.util.Locale;
     12import java.util.Map;
    1213import java.util.concurrent.Callable;
    1314import java.util.concurrent.ExecutionException;
    1415import java.util.concurrent.Executors;
    public class BingAerialTileSource extends AbstractTMSTileSource {  
    302303        }
    303304        return k.toString();
    304305    }
     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    }
    305315}
  • src/org/openstreetmap/josm/actions/AddImageryLayerAction.java

    diff --git src/org/openstreetmap/josm/actions/AddImageryLayerAction.java src/org/openstreetmap/josm/actions/AddImageryLayerAction.java
    index 422e840..f320dc4 100644
    import java.awt.GridBagLayout;  
    99import java.awt.event.ActionEvent;
    1010import java.io.IOException;
    1111import java.net.MalformedURLException;
     12import java.util.Arrays;
    1213
    1314import javax.swing.JComboBox;
    1415import javax.swing.JOptionPane;
    public class AddImageryLayerAction extends JosmAction implements AdaptableAction  
    149150        // never enable blacklisted entries. Do not add same imagery layer twice (fix #2519)
    150151        if (info.isBlacklisted() /*|| isLayerAlreadyPresent()*/) { // FIXME check disabled to allow several instances with different settings (see #7981)
    151152            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())) {
    153158            setEnabled(true);
    154159        } else if (Main.isDisplayingMapView() && !Main.map.mapView.getAllLayers().isEmpty()) {
    155160            setEnabled(true);
  • src/org/openstreetmap/josm/data/cache/JCSCachedTileLoaderJob.java

    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;  
    99import java.net.URL;
    1010import java.net.URLConnection;
    1111import java.util.HashSet;
     12import java.util.List;
    1213import java.util.Map;
    1314import java.util.Random;
    1415import java.util.Set;
    public abstract class JCSCachedTileLoaderJob<K, V extends CacheEntry> implements  
    223224     *
    224225     * @return cache object as empty, regardless of what remote resource has returned (ex. based on headers)
    225226     */
    226     protected boolean cacheAsEmpty() {
     227    protected boolean cacheAsEmpty(Map<String, List<String>> headers, int statusCode, byte[] content) {
    227228        return false;
    228229    }
    229230
    public abstract class JCSCachedTileLoaderJob<K, V extends CacheEntry> implements  
    328329                log.log(Level.FINE, "JCS - cache entry verified using HEAD request: {0}", getUrl());
    329330                return true;
    330331            }
    331             URLConnection urlConn = getURLConnection();
     332            HttpURLConnection urlConn = getURLConnection();
    332333
    333334            if (isObjectLoadable()  &&
    334335                    (now - attributes.getLastModification()) <= ABSOLUTE_EXPIRE_TIME_LIMIT) {
    public abstract class JCSCachedTileLoaderJob<K, V extends CacheEntry> implements  
    337338            if (isObjectLoadable() && attributes.getEtag() != null) {
    338339                urlConn.addRequestProperty("If-None-Match", attributes.getEtag());
    339340            }
    340             if (urlConn instanceof HttpURLConnection && ((HttpURLConnection)urlConn).getResponseCode() == 304) {
     341            if (urlConn.getResponseCode() == 304) {
    341342                // If isModifiedSince or If-None-Match has been set
    342343                // and the server answers with a HTTP 304 = "Not Modified"
    343344                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  
    358359            attributes = parseHeaders(urlConn);
    359360
    360361            for (int i = 0; i < 5; ++i) {
    361                 if (urlConn instanceof HttpURLConnection && ((HttpURLConnection)urlConn).getResponseCode() == 503) {
     362                if (urlConn.getResponseCode() == 503) {
    362363                    Thread.sleep(5000+(new Random()).nextInt(5000));
    363364                    continue;
    364365                }
    365366                byte[] raw = read(urlConn);
    366367
    367                 if (!cacheAsEmpty() && raw != null && raw.length > 0) {
     368                if (!cacheAsEmpty(urlConn.getHeaderFields(), urlConn.getResponseCode(), raw) &&
     369                        raw != null && raw.length > 0) {
    368370                    cacheData = createCacheEntry(raw);
    369371                    cache.put(getCacheKey(), cacheData, attributes);
    370372                    log.log(Level.FINE, "JCS - downloaded key: {0}, length: {1}, url: {2}",
    public abstract class JCSCachedTileLoaderJob<K, V extends CacheEntry> implements  
    399401
    400402    private CacheEntryAttributes parseHeaders(URLConnection urlConn) {
    401403        CacheEntryAttributes ret = new CacheEntryAttributes();
    402         ret.setNoTileAtZoom("no-tile".equals(urlConn.getHeaderField("X-VE-Tile-Info")));
    403404
    404405        Long lng = urlConn.getExpiration();
    405406        if (lng.equals(0L)) {
  • src/org/openstreetmap/josm/data/imagery/ImageryInfo.java

    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 {  
    5151        /** TMS entry for Russian company <a href="https://wiki.openstreetmap.org/wiki/WikiProject_Russia/kosmosnimki">ScanEx</a>. **/
    5252        SCANEX("scanex"),
    5353        /** 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
    5558
    5659        private final String typeString;
    5760
  • src/org/openstreetmap/josm/data/imagery/TMSCachedTileLoaderJob.java

    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;  
    44import java.io.ByteArrayInputStream;
    55import java.io.IOException;
    66import java.net.URL;
     7import java.util.List;
    78import java.util.HashSet;
    89import java.util.Map;
    910import java.util.Set;
    public class TMSCachedTileLoaderJob extends JCSCachedTileLoaderJob<String, Buffe  
    193194        if (cacheData != null) {
    194195            byte[] content = cacheData.getContent();
    195196            try {
    196                 return content != null  || cacheData.getImage() != null || cacheAsEmpty();
     197                return content != null  || cacheData.getImage() != null || isNoTileAtZoom();
    197198            } catch (IOException e) {
    198199                log.log(Level.WARNING, "JCS TMS - error loading from cache for tile {0}: {1}", new Object[] {tile.getKey(), e.getMessage()});
    199200            }
    public class TMSCachedTileLoaderJob extends JCSCachedTileLoaderJob<String, Buffe  
    206207    }
    207208
    208209    @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;
    211216    }
    212217
    213218    private boolean handleNoTileAtZoom() {
    public class TMSCachedTileLoaderJob extends JCSCachedTileLoaderJob<String, Buffe  
    315320
    316321    @Override
    317322    protected boolean handleNotFound() {
     323        if (tile.getSource().isNoTileAtZoom(null, 404, null)) {
    318324            tile.setError("No tile at this zoom level");
    319325            tile.putValue("tile-info", "no-tile");
    320326            return true;
    321327        }
     328        return false;
     329    }
    322330
    323331    /**
    324332     * For TMS use BaseURL as settings discovery, so for different paths, we will have different settings (useful for developer servers)
  • src/org/openstreetmap/josm/gui/layer/ImageryLayer.java

    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;  
    2121import java.awt.image.Kernel;
    2222import java.text.AttributedCharacterIterator;
    2323import java.text.AttributedString;
     24import java.util.Arrays;
    2425import java.util.Hashtable;
    2526import java.util.List;
    2627import java.util.Map;
    public abstract class ImageryLayer extends Layer {  
    152153    public static ImageryLayer create(ImageryInfo info) {
    153154        if (info.getImageryType() == ImageryType.WMS || info.getImageryType() == ImageryType.HTML)
    154155            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()))
    156161            return new TMSLayer(info);
    157162        else throw new AssertionError();
    158163    }
  • src/org/openstreetmap/josm/gui/layer/TMSLayer.java

    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;  
    4848import org.openstreetmap.gui.jmapviewer.interfaces.TileLoaderListener;
    4949import org.openstreetmap.gui.jmapviewer.interfaces.TileSource;
    5050import org.openstreetmap.gui.jmapviewer.tilesources.BingAerialTileSource;
     51import org.openstreetmap.gui.jmapviewer.tilesources.MapboxTileSource;
    5152import org.openstreetmap.gui.jmapviewer.tilesources.ScanexTileSource;
    5253import org.openstreetmap.gui.jmapviewer.tilesources.TMSTileSource;
    5354import org.openstreetmap.gui.jmapviewer.tilesources.TemplatedTMSTileSource;
    public class TMSLayer extends ImageryLayer implements ImageObserver, TileLoaderL  
    340341                    info.getCookies());
    341342            info.setAttribution(t);
    342343            return t;
    343         } else if (info.getImageryType() == ImageryType.BING)
     344        } else if (info.getImageryType() == ImageryType.BING) {
    344345            return new CachedAttributionBingAerialTileSource(info.getId());
    345         else if (info.getImageryType() == ImageryType.SCANEX) {
     346        } else if (info.getImageryType() == ImageryType.SCANEX) {
    346347            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());
    347350        }
    348351        return null;
    349352    }