Ticket #10454: 10454_no_tiles.patch

File 10454_no_tiles.patch, 39.2 KB (added by wiktorn, 10 years ago)
  • data/maps.xsd

    diff --git data/maps.xsd data/maps.xsd
    index d7b8c5d..2643b2d 100644
     
    670670                                                                        </xs:all>
    671671                                                                </xs:complexType>
    672672                                                        </xs:element>
     673                                                        <xs:element name="no-tile-header" minOccurs="0" maxOccurs="unbounded">
     674                                                                <xs:complexType>
     675                                                                        <xs:attribute name="name" type="xs:string" />
     676                                                                        <xs:attribute name="value" type="xs:string" />
     677                                                                </xs:complexType>
     678                                                        </xs:element>
    673679                                                </xs:choice>
    674680                                        </xs:sequence>
    675681                                        <xs:attribute name="last-check" type="xs:date" use="optional" />
  • 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/AbstractOsmTileSource.java

    diff --git src/org/openstreetmap/gui/jmapviewer/tilesources/AbstractOsmTileSource.java src/org/openstreetmap/gui/jmapviewer/tilesources/AbstractOsmTileSource.java
    index 90e892c..e3762f4 100644
    import java.awt.Image;  
    66import org.openstreetmap.gui.jmapviewer.Coordinate;
    77
    88/**
    9  * Abstract clas for OSM Tile sources
     9 * Abstract class for OSM Tile sources
    1010 */
    1111public abstract class AbstractOsmTileSource extends AbstractTMSTileSource {
    1212
    public abstract class AbstractOsmTileSource extends AbstractTMSTileSource {  
    2323     * are safe for file names; can be null
    2424     */
    2525    public AbstractOsmTileSource(String name, String base_url, String id) {
    26         super(name, base_url, id);
     26        super(new TileSourceInfo(name, base_url, id));
     27
    2728    }
    2829
     30    @Override
    2931    public int getMaxZoom() {
    3032        return 19;
    3133    }
  • src/org/openstreetmap/gui/jmapviewer/tilesources/AbstractTMSTileSource.java

    diff --git src/org/openstreetmap/gui/jmapviewer/tilesources/AbstractTMSTileSource.java src/org/openstreetmap/gui/jmapviewer/tilesources/AbstractTMSTileSource.java
    index e3cfd19..027b550 100644
     
    22package org.openstreetmap.gui.jmapviewer.tilesources;
    33
    44import java.io.IOException;
     5import java.util.List;
     6import java.util.Map;
     7import java.util.Map.Entry;
    58
    69import org.openstreetmap.gui.jmapviewer.OsmMercator;
    710
    public abstract class AbstractTMSTileSource extends AbstractTileSource {  
    1013    protected String name;
    1114    protected String baseUrl;
    1215    protected String id;
     16    private Map<String, String> noTileHeaders;
    1317
    14     public AbstractTMSTileSource(String name, String base_url, String id) {
    15         this.name = name;
    16         this.baseUrl = base_url;
     18    public AbstractTMSTileSource(TileSourceInfo info) {
     19        this.name = info.getName();
     20        this.baseUrl = info.getUrl();
    1721        if(baseUrl.endsWith("/")) {
    1822            baseUrl = baseUrl.substring(0,baseUrl.length()-1);
    1923        }
    20         this.id = id;
     24        this.id = info.getUrl();
     25        this.noTileHeaders = info.getNoTileHeaders();
    2126    }
    2227
    2328    @Override
    public abstract class AbstractTMSTileSource extends AbstractTileSource {  
    122127    public double tileXToLon(int x, int zoom) {
    123128        return OsmMercator.XToLon(x * OsmMercator.TILE_SIZE, zoom);
    124129    }
     130
     131    @Override
     132    public boolean isNoTileAtZoom(Map<String, List<String>> headers, int statusCode, byte[] content) {
     133        if(noTileHeaders != null) {
     134            for (Entry<String, String> searchEntry: noTileHeaders.entrySet()) {
     135                List<String> headerVals = headers.get(searchEntry.getKey());
     136                if (headerVals != null && headerVals.contains(searchEntry.getValue())) {
     137                    return true;
     138                }
     139            }
     140        }
     141        return super.isNoTileAtZoom(headers, statusCode, content);
     142    }
    125143}
  • 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..e2dce05 100644
    public class BingAerialTileSource extends AbstractTMSTileSource {  
    5252     * Constructs a new {@code BingAerialTileSource}.
    5353     */
    5454    public BingAerialTileSource() {
    55         this("Bing");
     55        super(new TileSourceInfo("Bing", null, null));
    5656    }
    5757
    58     /**
    59      * Constructs a new {@code BingAerialTileSource}.
    60      */
    61     public BingAerialTileSource(String id) {
    62         super("Bing Aerial Maps", "http://example.com/", id);
     58    public BingAerialTileSource(TileSourceInfo info) {
     59        super(info);
    6360    }
    6461
    6562    protected class Attribution {
  • src/org/openstreetmap/gui/jmapviewer/tilesources/ScanexTileSource.java

    diff --git src/org/openstreetmap/gui/jmapviewer/tilesources/ScanexTileSource.java src/org/openstreetmap/gui/jmapviewer/tilesources/ScanexTileSource.java
    index be7128a..6acd716 100644
    public class ScanexTileSource extends TMSTileSource {  
    4545    /* IRS by default */
    4646    private ScanexLayer Layer = ScanexLayer.IRS;
    4747
    48     public ScanexTileSource(String name, String url, String id, int maxZoom) {
    49         super(name, url, id, maxZoom);
     48    public ScanexTileSource(TileSourceInfo info) {
     49        super(info);
     50        String url = info.getUrl();
    5051
    5152        for (ScanexLayer layer : ScanexLayer.values()) {
    5253            if (url.equalsIgnoreCase(layer.getName())) {
    public class ScanexTileSource extends TMSTileSource {  
    7778        return this.Layer.getUri() + "&apikey=" + API_KEY + "&x=" + tilex + "&y=" + tiley + "&z=" + zoom;
    7879    }
    7980
     81    @Override
    8082    public TileUpdate getTileUpdate() {
    8183        return TileUpdate.IfNoneMatch;
    8284    }
  • src/org/openstreetmap/gui/jmapviewer/tilesources/TMSTileSource.java

    diff --git src/org/openstreetmap/gui/jmapviewer/tilesources/TMSTileSource.java src/org/openstreetmap/gui/jmapviewer/tilesources/TMSTileSource.java
    index 23c792d..21f2611 100644
     
    11// License: GPL. For details, see Readme.txt file.
    22package org.openstreetmap.gui.jmapviewer.tilesources;
    33
     4
    45public class TMSTileSource extends AbstractTMSTileSource {
    56
    67    protected int maxZoom;
    78    protected int minZoom = 0;
    89
    9     public TMSTileSource(String name, String url, String id, int maxZoom) {
    10         super(name, url, id);
    11         this.maxZoom = maxZoom;
    12     }
    13 
    14     public TMSTileSource(String name, String url, String id, int minZoom, int maxZoom) {
    15         super(name, url, id);
    16         this.minZoom = minZoom;
    17         this.maxZoom = maxZoom;
     10    public TMSTileSource(TileSourceInfo info) {
     11        super(info);
     12        minZoom = info.getMinZoom();
     13        maxZoom = info.getMaxZoom();
    1814    }
    1915
    2016    @Override
  • src/org/openstreetmap/gui/jmapviewer/tilesources/TemplatedTMSTileSource.java

    diff --git src/org/openstreetmap/gui/jmapviewer/tilesources/TemplatedTMSTileSource.java src/org/openstreetmap/gui/jmapviewer/tilesources/TemplatedTMSTileSource.java
    index 0b59c5d..4b43ff0 100644
     
    11// License: GPL. For details, see Readme.txt file.
    22package org.openstreetmap.gui.jmapviewer.tilesources;
    33
    4 import java.util.Map;
    54import java.util.HashMap;
     5import java.util.Map;
    66import java.util.Random;
    7 import java.util.regex.Pattern;
    87import java.util.regex.Matcher;
     8import java.util.regex.Pattern;
    99
    1010public class TemplatedTMSTileSource extends TMSTileSource {
    1111
    public class TemplatedTMSTileSource extends TMSTileSource {  
    2727        PATTERN_SWITCH
    2828    };
    2929
    30     public TemplatedTMSTileSource(String name, String url, String id, int maxZoom) {
    31         super(name, url, id, maxZoom);
    32         handleTemplate();
    33     }
    34 
    35     public TemplatedTMSTileSource(String name, String url, String id, int minZoom, int maxZoom) {
    36         super(name, url, id, minZoom, maxZoom);
    37         handleTemplate();
    38     }
    39 
    40     public TemplatedTMSTileSource(String name, String url, String id, int minZoom, int maxZoom, String cookies) {
    41         super(name, url, id, minZoom, maxZoom);
    42         if (cookies != null) {
    43             headers.put(COOKIE_HEADER, cookies);
     30    public TemplatedTMSTileSource(TileSourceInfo info) {
     31        super(info);
     32        if (info.getCookies() != null) {
     33            headers.put(COOKIE_HEADER, info.getCookies());
    4434        }
    4535        handleTemplate();
    4636    }
  • new file src/org/openstreetmap/gui/jmapviewer/tilesources/TileSourceInfo.java

    diff --git src/org/openstreetmap/gui/jmapviewer/tilesources/TileSourceInfo.java src/org/openstreetmap/gui/jmapviewer/tilesources/TileSourceInfo.java
    new file mode 100644
    index 0000000..379c8f7
    - +  
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.gui.jmapviewer.tilesources;
     3
     4import java.util.Map;
     5
     6public class TileSourceInfo {
     7    /** id for this imagery entry, optional at the moment */
     8    protected String id;
     9    /** URL of the imagery service */
     10    protected  String url = null;
     11
     12    /** name of the imagery layer */
     13    protected String name;
     14
     15    /** headers meaning, that there is no tile at this zoom level */
     16    protected Map<String, String> notileHeaders;
     17
     18    /** minimum zoom level supported by the tile source */
     19    protected int minZoom;
     20
     21    /** maximum zoom level supported by the tile source */
     22    protected int maxZoom;
     23
     24    /** cookies that needs to be sent to tile source */
     25    protected String cookies;
     26
     27
     28    /**
     29     * Create a TileSourceInfo class
     30     *
     31     * @param name
     32     * @param base_url
     33     * @param id
     34     */
     35    public TileSourceInfo(String name, String base_url, String id) {
     36        this(name);
     37        this.url = base_url;
     38        this.id = id;
     39    }
     40
     41    /**
     42     * Create a TileSourceInfo class
     43     *
     44     * @param name
     45     */
     46    public TileSourceInfo(String name) {
     47        this.name = name;
     48    }
     49
     50    /**
     51     * Creates empty TileSourceInfo class
     52     */
     53    public TileSourceInfo() {
     54    }
     55
     56    /**
     57     *
     58     * @return name of the tile source
     59     */
     60    public String getName() {
     61        return name;
     62    }
     63
     64    /**
     65     *
     66     * @return url of the tile source
     67     */
     68    public String getUrl() {
     69        return url;
     70    }
     71
     72    /**
     73     *
     74     * @return map of headers, that when set, means that this is "no tile at this zoom level" situation
     75     */
     76    public Map<String, String> getNoTileHeaders() {
     77        return notileHeaders;
     78    }
     79
     80    /**
     81     *
     82     * @return minimum zoom level supported by tile source
     83     */
     84    public int getMinZoom() {
     85        return minZoom;
     86    }
     87
     88    /**
     89     *
     90     * @return maximum zoom level supported by tile source
     91     */
     92    public int getMaxZoom() {
     93        return maxZoom;
     94    }
     95
     96    /**
     97     *
     98     * @return cookies to be sent along with request to tile source
     99     */
     100    public String getCookies() {
     101        return cookies;
     102    }
     103
     104}
  • src/org/openstreetmap/josm/data/Preferences.java

    diff --git src/org/openstreetmap/josm/data/Preferences.java src/org/openstreetmap/josm/data/Preferences.java
    index a686728..fb766c7 100644
    import java.io.InputStream;  
    1313import java.io.OutputStreamWriter;
    1414import java.io.PrintWriter;
    1515import java.io.Reader;
     16import java.io.StringReader;
     17import java.io.StringWriter;
    1618import java.lang.annotation.Retention;
    1719import java.lang.annotation.RetentionPolicy;
    1820import java.lang.reflect.Field;
    import java.nio.file.Files;  
    2123import java.util.ArrayList;
    2224import java.util.Collection;
    2325import java.util.Collections;
     26import java.util.HashMap;
    2427import java.util.HashSet;
    2528import java.util.Iterator;
    2629import java.util.LinkedHashMap;
    import java.util.concurrent.CopyOnWriteArrayList;  
    3740import java.util.regex.Matcher;
    3841import java.util.regex.Pattern;
    3942
     43import javax.json.Json;
     44import javax.json.JsonObject;
     45import javax.json.JsonObjectBuilder;
     46import javax.json.JsonReader;
     47import javax.json.JsonValue;
     48import javax.json.JsonWriter;
    4049import javax.swing.JOptionPane;
    4150import javax.swing.UIManager;
    4251import javax.xml.XMLConstants;
    public class Preferences {  
    12651274        return vals;
    12661275    }
    12671276
     1277    @SuppressWarnings("rawtypes")
     1278    private static String mapToJson(Map map) {
     1279        StringWriter stringWriter = new StringWriter();
     1280        try (JsonWriter writer = Json.createWriter(stringWriter)) {
     1281            JsonObjectBuilder object = Json.createObjectBuilder();
     1282            for(Object o: map.entrySet()) {
     1283                Entry e = (Entry) o;
     1284                object.add(e.getKey().toString(), e.getValue().toString());
     1285            }
     1286            writer.writeObject(object.build());
     1287        }
     1288        return stringWriter.toString();
     1289    }
     1290
     1291    @SuppressWarnings({ "rawtypes", "unchecked" })
     1292    private static Map mapFromJson(String s) {
     1293        Map ret = null;
     1294        try (JsonReader reader = Json.createReader(new StringReader(s))) {
     1295            JsonObject object = reader.readObject();
     1296            ret = new HashMap(object.size());
     1297            for (Entry<String, JsonValue> e: object.entrySet()) {
     1298                ret.put(e.getKey(), e.getValue().toString());
     1299            }
     1300        }
     1301        return ret;
     1302    }
     1303
    12681304    public static <T> Map<String,String> serializeStruct(T struct, Class<T> klass) {
    12691305        T structPrototype;
    12701306        try {
    public class Preferences {  
    12841320                Object defaultFieldValue = f.get(structPrototype);
    12851321                if (fieldValue != null) {
    12861322                    if (f.getAnnotation(writeExplicitly.class) != null || !Objects.equals(fieldValue, defaultFieldValue)) {
    1287                         hash.put(f.getName().replace("_", "-"), fieldValue.toString());
     1323                        String key = f.getName().replace("_", "-");
     1324                        if (fieldValue instanceof Map) {
     1325                            hash.put(key, mapToJson((Map) fieldValue));
     1326                        } else {
     1327                            hash.put(key, fieldValue.toString());
     1328                        }
    12881329                    }
    12891330                }
    12901331            } catch (IllegalArgumentException | IllegalAccessException ex) {
    public class Preferences {  
    13311372                }
    13321373            } else  if (f.getType() == String.class) {
    13331374                value = key_value.getValue();
    1334             } else
     1375            } else if (f.getType().isAssignableFrom(Map.class)) {
     1376                value = mapFromJson(key_value.getValue());
     1377            }
     1378            else
    13351379                throw new RuntimeException("unsupported preference primitive type");
    13361380
    13371381            try {
  • src/org/openstreetmap/josm/data/cache/BufferedImageCacheEntry.java

    diff --git src/org/openstreetmap/josm/data/cache/BufferedImageCacheEntry.java src/org/openstreetmap/josm/data/cache/BufferedImageCacheEntry.java
    index 63a765f..ea9b51b 100644
    public class BufferedImageCacheEntry extends CacheEntry {  
    4242            if (img != null)
    4343                return img;
    4444            byte[] content = getContent();
    45             if (content != null) {
     45            if (content != null && content.length > 0) {
    4646                img = ImageIO.read(new ByteArrayInputStream(content));
    4747
    4848                if (writtenToDisk)
  • src/org/openstreetmap/josm/data/cache/ICachedLoaderListener.java

    diff --git src/org/openstreetmap/josm/data/cache/ICachedLoaderListener.java src/org/openstreetmap/josm/data/cache/ICachedLoaderListener.java
    index fe26c09..01c5199 100644
    public interface ICachedLoaderListener {  
    1919     * LoadResult.REJECTED when job was rejected because of full queue
    2020     *
    2121     * @param data
     22     * @param attributes
    2223     * @param result
    2324     */
    24     public void loadingFinished(CacheEntry data, LoadResult result);
     25    public void loadingFinished(CacheEntry data, CacheEntryAttributes attributes, LoadResult result);
    2526
    2627}
  • 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..a31fddb 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  
    279280        }
    280281        try {
    281282            for (ICachedLoaderListener l: listeners) {
    282                 l.loadingFinished(cacheData, result);
     283                l.loadingFinished(cacheData, attributes, result);
    283284            }
    284285        } catch (Exception e) {
    285286            log.log(Level.WARNING, "JCS - Error while loading object from cache: {0}; {1}", new Object[]{e.getMessage(), getUrl()});
    286287            Main.warn(e);
    287288            for (ICachedLoaderListener l: listeners) {
    288                 l.loadingFinished(cacheData, LoadResult.FAILURE);
     289                l.loadingFinished(cacheData, attributes, LoadResult.FAILURE);
    289290            }
    290291
    291292        }
    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..608ebed 100644
    import java.util.Arrays;  
    99import java.util.Collection;
    1010import java.util.Collections;
    1111import java.util.List;
     12import java.util.Map;
    1213import java.util.Objects;
    1314import java.util.TreeSet;
    1415import java.util.regex.Matcher;
    import org.openstreetmap.gui.jmapviewer.Coordinate;  
    2021import org.openstreetmap.gui.jmapviewer.interfaces.Attributed;
    2122import org.openstreetmap.gui.jmapviewer.tilesources.AbstractTileSource;
    2223import org.openstreetmap.gui.jmapviewer.tilesources.OsmTileSource.Mapnik;
     24import org.openstreetmap.gui.jmapviewer.tilesources.TileSourceInfo;
    2325import org.openstreetmap.josm.Main;
    2426import org.openstreetmap.josm.data.Bounds;
    2527import org.openstreetmap.josm.data.Preferences.pref;
    import org.openstreetmap.josm.tools.LanguageInfo;  
    3436 *
    3537 * @author Frederik Ramm
    3638 */
    37 public class ImageryInfo implements Comparable<ImageryInfo>, Attributed {
     39public class ImageryInfo extends TileSourceInfo implements Comparable<ImageryInfo>, Attributed {
    3840
    3941    /**
    4042     * Type of imagery entry.
    public class ImageryInfo implements Comparable<ImageryInfo>, Attributed {  
    5355        /** A WMS endpoint entry only stores the WMS server info, without layer, which are chosen later by the user. **/
    5456        WMS_ENDPOINT("wms_endpoint");
    5557
     58
    5659        private final String typeString;
    5760
    5861        private ImageryType(String urlString) {
    public class ImageryInfo implements Comparable<ImageryInfo>, Attributed {  
    150153        }
    151154    }
    152155
    153     /** name of the imagery entry (gets translated by josm usually) */
    154     private String name;
     156
    155157    /** original name of the imagery entry in case of translation call, for multiple languages English when possible */
    156158    private String origName;
    157159    /** (original) language of the translated name entry */
    158160    private String langName;
    159     /** id for this imagery entry, optional at the moment */
    160     private String id;
    161     /** URL of the imagery service */
    162     private String url = null;
    163161    /** whether this is a entry activated by default or not */
    164162    private boolean defaultEntry = false;
    165163    /** The data part of HTTP cookies header in case the service requires cookies to work */
    public class ImageryInfo implements Comparable<ImageryInfo>, Attributed {  
    198196    /** icon used in menu */
    199197    private String icon;
    200198    // when adding a field, also adapt the ImageryInfo(ImageryInfo) constructor
     199    private Map<String, String> noTileHeaders;
    201200
    202201    /**
    203202     * Auxiliary class to save an {@link ImageryInfo} object in the preferences.
    public class ImageryInfo implements Comparable<ImageryInfo>, Attributed {  
    224223        @pref String projections;
    225224        @pref String icon;
    226225        @pref String description;
     226        @pref Map<String, String> noTileHeaders;
    227227
    228228        /**
    229229         * Constructs a new empty WMS {@code ImageryPreferenceEntry}.
    public class ImageryInfo implements Comparable<ImageryInfo>, Attributed {  
    277277                }
    278278                projections = val.toString();
    279279            }
     280            if (i.noTileHeaders != null && !i.noTileHeaders.isEmpty()) {
     281                noTileHeaders = i.noTileHeaders;
     282            }
    280283        }
    281284
    282285        @Override
    public class ImageryInfo implements Comparable<ImageryInfo>, Attributed {  
    294297     * Constructs a new WMS {@code ImageryInfo}.
    295298     */
    296299    public ImageryInfo() {
     300        super();
    297301    }
    298302
    299303    /**
    public class ImageryInfo implements Comparable<ImageryInfo>, Attributed {  
    301305     * @param name The entry name
    302306     */
    303307    public ImageryInfo(String name) {
    304         this.name=name;
     308        super(name);
    305309    }
    306310
    307311    /**
    public class ImageryInfo implements Comparable<ImageryInfo>, Attributed {  
    310314     * @param url The entry extended URL
    311315     */
    312316    public ImageryInfo(String name, String url) {
    313         this.name=name;
     317        this(name);
    314318        setExtendedUrl(url);
    315319    }
    316320
    public class ImageryInfo implements Comparable<ImageryInfo>, Attributed {  
    321325     * @param eulaAcceptanceRequired The EULA URL
    322326     */
    323327    public ImageryInfo(String name, String url, String eulaAcceptanceRequired) {
    324         this.name=name;
     328        this(name);
    325329        setExtendedUrl(url);
    326330        this.eulaAcceptanceRequired = eulaAcceptanceRequired;
    327331    }
    public class ImageryInfo implements Comparable<ImageryInfo>, Attributed {  
    336340     * @throws IllegalArgumentException if type refers to an unknown imagery type
    337341     */
    338342    public ImageryInfo(String name, String url, String type, String eulaAcceptanceRequired, String cookies) {
    339         this.name=name;
     343        this(name);
    340344        setExtendedUrl(url);
    341345        ImageryType t = ImageryType.fromString(type);
    342346        this.cookies=cookies;
    public class ImageryInfo implements Comparable<ImageryInfo>, Attributed {  
    348352        }
    349353    }
    350354
     355    public ImageryInfo(String name, String url, String type, String eulaAcceptanceRequired, String cookies, String id) {
     356        this(name, url, type, eulaAcceptanceRequired, cookies);
     357        setId(id);
     358    }
     359
    351360    /**
    352361     * Constructs a new {@code ImageryInfo} from an imagery preference entry.
    353362     * @param e The imagery preference entry
    public class ImageryInfo implements Comparable<ImageryInfo>, Attributed {  
    389398        termsOfUseURL = e.terms_of_use_url;
    390399        countryCode = e.country_code;
    391400        icon = e.icon;
     401        if (e.noTileHeaders != null) {
     402            noTileHeaders = e.noTileHeaders;
     403        }
    392404    }
    393405
    394406    /**
    public class ImageryInfo implements Comparable<ImageryInfo>, Attributed {  
    687699     * Returns the entry name.
    688700     * @return The entry name
    689701     */
     702    @Override
    690703    public String getName() {
    691704        return this.name;
    692705    }
    public class ImageryInfo implements Comparable<ImageryInfo>, Attributed {  
    758771     * Returns the entry URL.
    759772     * @return The entry URL
    760773     */
     774    @Override
    761775    public String getUrl() {
    762776        return this.url;
    763777    }
    public class ImageryInfo implements Comparable<ImageryInfo>, Attributed {  
    790804     * Return the data part of HTTP cookies header in case the service requires cookies to work
    791805     * @return the cookie data part
    792806     */
     807    @Override
    793808    public String getCookies() {
    794809        return this.cookies;
    795810    }
    public class ImageryInfo implements Comparable<ImageryInfo>, Attributed {  
    802817     * Returns the maximum zoom level.
    803818     * @return The maximum zoom level
    804819     */
     820    @Override
    805821    public int getMaxZoom() {
    806822        return this.defaultMaxZoom;
    807823    }
    public class ImageryInfo implements Comparable<ImageryInfo>, Attributed {  
    810826     * Returns the minimum zoom level.
    811827     * @return The minimum zoom level
    812828     */
     829    @Override
    813830    public int getMinZoom() {
    814831        return this.defaultMinZoom;
    815832    }
    public class ImageryInfo implements Comparable<ImageryInfo>, Attributed {  
    10251042        Capabilities capabilities = OsmApi.getOsmApi().getCapabilities();
    10261043        return capabilities != null && capabilities.isOnImageryBlacklist(this.url);
    10271044    }
     1045
     1046    public void setNoTileHeaders(Map<String, String> noTileHeaders) {
     1047       this.noTileHeaders = noTileHeaders;
     1048    }
     1049
     1050    @Override
     1051    public Map<String, String> getNoTileHeaders() {
     1052        return noTileHeaders;
     1053    }
    10281054}
  • src/org/openstreetmap/josm/data/imagery/ImageryLayerInfo.java

    diff --git src/org/openstreetmap/josm/data/imagery/ImageryLayerInfo.java src/org/openstreetmap/josm/data/imagery/ImageryLayerInfo.java
    index 96f8b50..f641d17 100644
    public class ImageryLayerInfo {  
    148148        Collection<String> knownDefaults = Main.pref.getCollection("imagery.layers.default");
    149149        Collection<String> newKnownDefaults = new TreeSet<>(knownDefaults);
    150150        for (ImageryInfo def : defaultLayers) {
     151            // temporary migration code, so all user preferences will get updated with new settings from JOSM site
     152            if(def.getNoTileHeaders() != null) {
     153                for(ImageryInfo i: layers) {
     154                    if (isSimilar(def,  i)) {
     155                        i.setNoTileHeaders(def.getNoTileHeaders());
     156                        changed = true;
     157                    }
     158                }
     159            }
     160
    151161            if (def.isDefaultEntry()) {
    152162                boolean isKnownDefault = false;
    153163                for (String url : knownDefaults) {
  • 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..9a473d2 100644
    import java.io.ByteArrayInputStream;  
    55import java.io.IOException;
    66import java.net.URL;
    77import java.util.HashSet;
     8import java.util.List;
    89import java.util.Map;
    910import java.util.Set;
    1011import java.util.concurrent.ConcurrentHashMap;
    import org.openstreetmap.gui.jmapviewer.tilesources.AbstractTMSTileSource;  
    2627import org.openstreetmap.josm.Main;
    2728import org.openstreetmap.josm.data.cache.BufferedImageCacheEntry;
    2829import org.openstreetmap.josm.data.cache.CacheEntry;
     30import org.openstreetmap.josm.data.cache.CacheEntryAttributes;
    2931import org.openstreetmap.josm.data.cache.ICachedLoaderListener;
    3032import org.openstreetmap.josm.data.cache.JCSCachedTileLoaderJob;
    3133import org.openstreetmap.josm.data.preferences.IntegerProperty;
    public class TMSCachedTileLoaderJob extends JCSCachedTileLoaderJob<String, Buffe  
    193195        if (cacheData != null) {
    194196            byte[] content = cacheData.getContent();
    195197            try {
    196                 return content != null  || cacheData.getImage() != null || cacheAsEmpty();
     198                return content != null  || cacheData.getImage() != null || isNoTileAtZoom();
    197199            } catch (IOException e) {
    198200                log.log(Level.WARNING, "JCS TMS - error loading from cache for tile {0}: {1}", new Object[] {tile.getKey(), e.getMessage()});
    199201            }
    public class TMSCachedTileLoaderJob extends JCSCachedTileLoaderJob<String, Buffe  
    202204    }
    203205
    204206    private boolean isNoTileAtZoom() {
     207        if (attributes == null) {
     208            log.warning("Cache attributes are null");
     209        }
    205210        return attributes != null && attributes.isNoTileAtZoom();
    206211    }
    207212
    208213    @Override
    209     protected boolean cacheAsEmpty() {
    210         return isNoTileAtZoom();
     214    protected boolean cacheAsEmpty(Map<String, List<String>> headers, int statusCode, byte[] content) {
     215        if (tile.getTileSource().isNoTileAtZoom(headers, statusCode, content)) {
     216            attributes.setNoTileAtZoom(true);
     217            return true;
     218        }
     219        return false;
    211220    }
    212221
    213222    private boolean handleNoTileAtZoom() {
    public class TMSCachedTileLoaderJob extends JCSCachedTileLoaderJob<String, Buffe  
    241250    }
    242251
    243252    @Override
    244     public void loadingFinished(CacheEntry object, LoadResult result) {
     253    public void loadingFinished(CacheEntry object, CacheEntryAttributes attributes, LoadResult result) {
     254        this.attributes = attributes; // as we might get notification from other object than our selfs, pass attributes along
    245255        Set<TileLoaderListener> listeners;
    246256        synchronized (inProgress) {
    247257            listeners = inProgress.remove(getCacheKey());
    public class TMSCachedTileLoaderJob extends JCSCachedTileLoaderJob<String, Buffe  
    315325
    316326    @Override
    317327    protected boolean handleNotFound() {
     328        if (tile.getSource().isNoTileAtZoom(null, 404, null)) {
    318329            tile.setError("No tile at this zoom level");
    319330            tile.putValue("tile-info", "no-tile");
    320331            return true;
    321332        }
     333        return false;
     334    }
    322335
    323336    /**
    324337     * 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/TMSLayer.java

    diff --git src/org/openstreetmap/josm/gui/layer/TMSLayer.java src/org/openstreetmap/josm/gui/layer/TMSLayer.java
    index c749488..5e2073d 100644
    public class TMSLayer extends ImageryLayer implements ImageObserver, TileLoaderL  
    277277
    278278    private static class CachedAttributionBingAerialTileSource extends BingAerialTileSource {
    279279
    280         public CachedAttributionBingAerialTileSource(String id) {
    281             super(id);
     280        public CachedAttributionBingAerialTileSource(ImageryInfo info) {
     281            super(info);
    282282        }
    283283
    284284        class BingAttributionData extends CacheCustomContent<IOException> {
    public class TMSLayer extends ImageryLayer implements ImageObserver, TileLoaderL  
    336336    public static TileSource getTileSource(ImageryInfo info) {
    337337        if (info.getImageryType() == ImageryType.TMS) {
    338338            checkUrl(info.getUrl());
    339             TMSTileSource t = new TemplatedTMSTileSource(info.getName(), info.getUrl(), info.getId(), info.getMinZoom(), info.getMaxZoom(),
    340                     info.getCookies());
     339            TMSTileSource t = new TemplatedTMSTileSource(info);
    341340            info.setAttribution(t);
    342341            return t;
    343         } else if (info.getImageryType() == ImageryType.BING)
    344             return new CachedAttributionBingAerialTileSource(info.getId());
    345         else if (info.getImageryType() == ImageryType.SCANEX) {
    346             return new ScanexTileSource(info.getName(), info.getUrl(), info.getId(), info.getMaxZoom());
     342        } else if (info.getImageryType() == ImageryType.BING) {
     343            //return new CachedAttributionBingAerialTileSource(info.getId());
     344            return new CachedAttributionBingAerialTileSource(info);
     345        } else if (info.getImageryType() == ImageryType.SCANEX) {
     346            //return new ScanexTileSource(info.getName(), info.getUrl(), info.getId(), info.getMaxZoom());
     347            return new ScanexTileSource(info);
    347348        }
    348349        return null;
    349350    }
  • src/org/openstreetmap/josm/io/imagery/ImageryReader.java

    diff --git src/org/openstreetmap/josm/io/imagery/ImageryReader.java src/org/openstreetmap/josm/io/imagery/ImageryReader.java
    index 6978c5f..15ed396 100644
    import java.io.IOException;  
    55import java.io.InputStream;
    66import java.util.ArrayList;
    77import java.util.Arrays;
     8import java.util.HashMap;
    89import java.util.List;
     10import java.util.Map;
    911import java.util.Objects;
    1012import java.util.Stack;
    1113
    public class ImageryReader {  
    3840        CODE,
    3941        BOUNDS,
    4042        SHAPE,
     43        NO_TILE,
    4144        UNKNOWN,            // element is not recognized in the current context
    4245    }
    4346
    public class ImageryReader {  
    8386        // language of last element, does only work for simple ENTRY_ATTRIBUTE's
    8487        private String lang;
    8588        private List<String> projections;
     89        private Map<String, String> noTileHeaders;
    8690
    8791        @Override
    8892        public void startDocument() {
    public class ImageryReader {  
    9498            entry = null;
    9599            bounds = null;
    96100            projections = null;
     101            noTileHeaders = null;
    97102        }
    98103
    99104        @Override
    public class ImageryReader {  
    149154                } else if ("projections".equals(qName)) {
    150155                    projections = new ArrayList<>();
    151156                    newState = State.PROJECTIONS;
     157                } else if ("no-tile-header".equals(qName)) {
     158                    noTileHeaders = new HashMap<>();
     159                    noTileHeaders.put(atts.getValue("name"), atts.getValue("value"));
     160                    newState = State.NO_TILE;
    152161                }
    153162                break;
    154163            case BOUNDS:
    public class ImageryReader {  
    307316                entry.setServerProjections(projections);
    308317                projections = null;
    309318                break;
     319            case NO_TILE:
     320                entry.setNoTileHeaders(noTileHeaders);
     321                noTileHeaders = null;
     322                break;
     323
    310324            }
    311325        }
    312326    }