Index: trunk/src/org/openstreetmap/josm/data/Bounds.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/Bounds.java	(revision 4064)
+++ trunk/src/org/openstreetmap/josm/data/Bounds.java	(revision 4065)
@@ -178,8 +178,8 @@
      */
     public boolean intersects(Bounds b) {
-        return b.getMax().lat() >= minLat &&
-        b.getMax().lon() >= minLon &&
-        b.getMin().lat() <= maxLat &&
-        b.getMin().lon() <= maxLon;
+        return b.maxLat >= minLat &&
+        b.maxLon >= minLon &&
+        b.minLat <= maxLat &&
+        b.minLon <= maxLon;
     }
 
Index: trunk/src/org/openstreetmap/josm/data/ProjectionBounds.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/ProjectionBounds.java	(revision 4064)
+++ trunk/src/org/openstreetmap/josm/data/ProjectionBounds.java	(revision 4065)
@@ -6,5 +6,5 @@
 /**
  * This is a simple data class for "rectangular" areas of the world, given in
- * lat/lon min/max values.
+ * east/north min/max values.
  *
  * @author imi
@@ -14,5 +14,5 @@
      * The minimum and maximum coordinates.
      */
-    public EastNorth min, max;
+    public double minEast, minNorth, maxEast, maxNorth;
 
     /**
@@ -20,29 +20,67 @@
      */
     public ProjectionBounds(EastNorth min, EastNorth max) {
-        this.min = min;
-        this.max = max;
+        this.minEast = min.east();
+        this.minNorth = min.north();
+        this.maxEast = max.east();
+        this.maxNorth = max.north();
     }
     public ProjectionBounds(EastNorth p) {
-        this.min = p;
-        this.max = p;
+        this.minEast = this.maxEast = p.east();
+        this.minNorth = this.maxNorth = p.north();
     }
     public ProjectionBounds(EastNorth center, double east, double north) {
-        this.min = new EastNorth(center.east()-east/2.0, center.north()-north/2.0);
-        this.max = new EastNorth(center.east()+east/2.0, center.north()+north/2.0);
+        this.minEast = center.east()-east/2.0;
+        this.minNorth = center.north()-north/2.0;
+        this.maxEast = center.east()+east/2.0;
+        this.maxNorth = center.north()+north/2.0;
+    }
+    public ProjectionBounds(double minEast, double minNorth, double maxEast, double maxNorth) {
+        this.minEast = minEast;
+        this.minNorth = minNorth;
+        this.maxEast = maxEast;
+        this.maxNorth = maxNorth;
     }
     public void extend(EastNorth e)
     {
-        if (e.east() < min.east() || e.north() < min.north())
-            min = new EastNorth(Math.min(e.east(), min.east()), Math.min(e.north(), min.north()));
-        if (e.east() > max.east() || e.north() > max.north())
-            max = new EastNorth(Math.max(e.east(), max.east()), Math.max(e.north(), max.north()));
+        if (e.east() < minEast) {
+            minEast = e.east();
+        }
+        if (e.east() > maxEast) {
+            maxEast = e.east();
+        }
+        if (e.north() < minNorth) {
+            minNorth = e.north();
+        }
+        if (e.north() > maxNorth) {
+            maxNorth = e.north();
+        }
     }
     public EastNorth getCenter()
     {
-        return min.getCenter(max);
+        return new EastNorth((minEast + maxEast) / 2.0, (minNorth + maxNorth) / 2.0);
     }
 
     @Override public String toString() {
-        return "ProjectionBounds["+min.east()+","+min.north()+","+max.east()+","+max.north()+"]";
+        return "ProjectionBounds["+minEast+","+minNorth+","+maxEast+","+maxNorth+"]";
     }
+
+    /**
+     * The two bounds intersect? Compared to java Shape.intersects, if does not use
+     * the interior but the closure. (">=" instead of ">")
+     */
+    public boolean intersects(ProjectionBounds b) {
+        return b.maxEast >= minEast &&
+        b.maxNorth >= minNorth &&
+        b.minEast <= maxEast &&
+        b.minNorth <= maxNorth;
+    }
+
+    public EastNorth getMin() {
+        return new EastNorth(minEast, minNorth);
+    }
+
+    public EastNorth getMax() {
+        return new EastNorth(maxEast, maxNorth);
+    }
+
 }
Index: trunk/src/org/openstreetmap/josm/data/imagery/GeorefImage.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/imagery/GeorefImage.java	(revision 4064)
+++ trunk/src/org/openstreetmap/josm/data/imagery/GeorefImage.java	(revision 4065)
@@ -26,5 +26,5 @@
     private static final long serialVersionUID = 1L;
 
-    public enum State { IMAGE, NOT_IN_CACHE, FAILED}
+    public enum State { IMAGE, NOT_IN_CACHE, FAILED, PARTLY_IN_CACHE}
 
     private WMSLayer layer;
Index: trunk/src/org/openstreetmap/josm/data/imagery/WmsCache.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/imagery/WmsCache.java	(revision 4065)
+++ trunk/src/org/openstreetmap/josm/data/imagery/WmsCache.java	(revision 4065)
@@ -0,0 +1,542 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.data.imagery;
+
+
+import java.awt.Graphics2D;
+import java.awt.image.BufferedImage;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.lang.ref.SoftReference;
+import java.net.URLConnection;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+
+import javax.imageio.ImageIO;
+import javax.xml.bind.JAXBContext;
+import javax.xml.bind.JAXBException;
+import javax.xml.bind.Marshaller;
+import javax.xml.bind.Unmarshaller;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.data.ProjectionBounds;
+import org.openstreetmap.josm.data.coor.EastNorth;
+import org.openstreetmap.josm.data.coor.LatLon;
+import org.openstreetmap.josm.data.imagery.types.EntryType;
+import org.openstreetmap.josm.data.imagery.types.ProjectionType;
+import org.openstreetmap.josm.data.imagery.types.WmsCacheType;
+import org.openstreetmap.josm.data.preferences.StringProperty;
+import org.openstreetmap.josm.data.projection.Projection;
+import org.openstreetmap.josm.gui.NavigatableComponent;
+import org.openstreetmap.josm.tools.Utils;
+
+
+
+public class WmsCache {
+    //TODO Property for maximum cache size
+    //TODO Property for maximum age of tile, automatically remove old tiles
+    //TODO Measure time for partially loading from cache, compare with time to download tile. If slower, disable partial cache
+    //TODO Do loading from partial cache and downloading at the same time, don't wait for partical cache to load
+
+    private static final StringProperty PROP_CACHE_PATH = new StringProperty("imagery.wms-cache.path", "wms-cache");
+    private static final String INDEX_FILENAME = "index.xml";
+    private static final String LAYERS_INDEX_FILENAME = "layers.properties";
+
+    private static class CacheEntry {
+        final double pixelPerDegree;
+        final double east;
+        final double north;
+        final ProjectionBounds bounds;
+
+        long lastUsed;
+        long lastModified;
+        String filename;
+
+        CacheEntry(double pixelPerDegree, double east, double north, int tileSize) {
+            this.pixelPerDegree = pixelPerDegree;
+            this.east = east;
+            this.north = north;
+            this.bounds = new ProjectionBounds(east, north, east + tileSize / pixelPerDegree, north + tileSize / pixelPerDegree);
+        }
+    }
+
+    private static class ProjectionEntries {
+        final String projection;
+        final String cacheDirectory;
+        final List<CacheEntry> entries = new ArrayList<WmsCache.CacheEntry>();
+
+        ProjectionEntries(String projection, String cacheDirectory) {
+            this.projection = projection;
+            this.cacheDirectory = cacheDirectory;
+        }
+    }
+
+    private final Map<String, ProjectionEntries> entries = new HashMap<String, ProjectionEntries>();
+    private final File cacheDir;
+    private final int tileSize; // Should be always 500
+    private int totalFileSize;
+    private boolean totalFileSizeDirty; // Some file was missing - size needs to be recalculated
+    // No need for hashCode/equals on CacheEntry, object identity is enough. Comparing by values can lead to error - CacheEntry for wrong projection could be found
+    private Map<CacheEntry, SoftReference<BufferedImage>> memoryCache = new HashMap<WmsCache.CacheEntry, SoftReference<BufferedImage>>();
+    private Set<ProjectionBounds> areaToCache;
+
+    public WmsCache(String url, int tileSize) {
+        File globalCacheDir = new File(Main.pref.getPreferencesDir() + PROP_CACHE_PATH.get());
+        globalCacheDir.mkdirs();
+        cacheDir = new File(globalCacheDir, getCacheDirectory(url));
+        cacheDir.mkdirs();
+        this.tileSize = tileSize;
+    }
+
+    private String getCacheDirectory(String url) {
+        String cacheDirName = null;
+        InputStream fis = null;
+        OutputStream fos = null;
+        try {
+            Properties layersIndex = new Properties();
+            File layerIndexFile = new File(Main.pref.getPreferencesDir() + PROP_CACHE_PATH.get(), LAYERS_INDEX_FILENAME);
+            try {
+                fis = new FileInputStream(layerIndexFile);
+                layersIndex.load(fis);
+            } catch (FileNotFoundException e) {
+                System.out.println("Unable to load layers index for wms cache (file " + layerIndexFile + " not found)");
+            } catch (IOException e) {
+                System.err.println("Unable to load layers index for wms cache");
+                e.printStackTrace();
+            }
+
+            for (Object propKey: layersIndex.keySet()) {
+                String s = (String)propKey;
+                if (url.equals(layersIndex.getProperty(s))) {
+                    cacheDirName = s;
+                    break;
+                }
+            }
+
+            if (cacheDirName == null) {
+                int counter = 0;
+                while (true) {
+                    counter++;
+                    if (!layersIndex.keySet().contains(String.valueOf(counter))) {
+                        break;
+                    }
+                }
+                cacheDirName = String.valueOf(counter);
+                layersIndex.setProperty(cacheDirName, url);
+                try {
+                    fos = new FileOutputStream(layerIndexFile);
+                    layersIndex.store(fos, "");
+                } catch (IOException e) {
+                    System.err.println("Unable to save layer index for wms cache");
+                    e.printStackTrace();
+                }
+            }
+        } finally {
+            try {
+                if (fis != null) {
+                    fis.close();
+                }
+                if (fos != null) {
+                    fos.close();
+                }
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+        }
+
+        return cacheDirName;
+    }
+
+    private ProjectionEntries getProjectionEntries(Projection projection) {
+        return getProjectionEntries(projection.toCode(), projection.getCacheDirectoryName());
+    }
+
+    private ProjectionEntries getProjectionEntries(String projection, String cacheDirectory) {
+        ProjectionEntries result = entries.get(projection);
+        if (result == null) {
+            result = new ProjectionEntries(projection, cacheDirectory);
+            entries.put(projection, result);
+        }
+
+        return result;
+    }
+
+    public synchronized void loadIndex() {
+        try {
+            JAXBContext context = JAXBContext.newInstance(
+                    WmsCacheType.class.getPackage().getName(),
+                    WmsCacheType.class.getClassLoader());
+            Unmarshaller unmarshaller = context.createUnmarshaller();
+            WmsCacheType cacheEntries = (WmsCacheType)unmarshaller.unmarshal(
+                    new FileInputStream(new File(cacheDir, INDEX_FILENAME)));
+            totalFileSize = cacheEntries.getTotalFileSize();
+            if (cacheEntries.getTileSize() != tileSize) {
+                System.out.println("Cache created with different tileSize, cache will be discarded");
+                return;
+            }
+            for (ProjectionType projectionType: cacheEntries.getProjection()) {
+                ProjectionEntries projection = getProjectionEntries(projectionType.getName(), projectionType.getCacheDirectory());
+                for (EntryType entry: projectionType.getEntry()) {
+                    CacheEntry ce = new CacheEntry(entry.getPixelPerDegree(), entry.getEast(), entry.getNorth(), tileSize);
+                    ce.lastUsed = entry.getLastUsed().getTimeInMillis();
+                    ce.filename = entry.getFilename();
+                    ce.lastModified = entry.getLastModified().getTimeInMillis();
+                    projection.entries.add(ce);
+                }
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+            System.out.println("Unable to load index for wms-cache, new file will be created");
+        }
+
+        removeNonReferencedFiles();
+    }
+
+    private void removeNonReferencedFiles() {
+
+        Set<String> usedProjections = new HashSet<String>();
+
+        for (ProjectionEntries projectionEntries: entries.values()) {
+
+            usedProjections.add(projectionEntries.cacheDirectory);
+
+            File projectionDir = new File(cacheDir, projectionEntries.cacheDirectory);
+            Set<String> referencedFiles = new HashSet<String>();
+
+            for (CacheEntry ce: projectionEntries.entries) {
+                referencedFiles.add(ce.filename);
+            }
+
+            for (File file: projectionDir.listFiles()) {
+                if (!referencedFiles.contains(file.getName())) {
+                    file.delete();
+                }
+            }
+        }
+
+        for (File projectionDir: cacheDir.listFiles()) {
+            if (projectionDir.isDirectory() && !usedProjections.contains(projectionDir.getName())) {
+                Utils.deleteDirectory(projectionDir);
+            }
+        }
+    }
+
+    private int calculateTotalFileSize() {
+        int result = 0;
+        for (ProjectionEntries projectionEntries: entries.values()) {
+            Iterator<CacheEntry> it = projectionEntries.entries.iterator();
+            while (it.hasNext()) {
+                CacheEntry entry = it.next();
+                File imageFile = getImageFile(projectionEntries, entry);
+                if (!imageFile.exists()) {
+                    it.remove();
+                } else {
+                    result += imageFile.length();
+                }
+            }
+        }
+        return result;
+    }
+
+    public synchronized void saveIndex() {
+        WmsCacheType index = new WmsCacheType();
+
+        if (totalFileSizeDirty) {
+            totalFileSize = calculateTotalFileSize();
+        }
+
+        index.setTileSize(tileSize);
+        index.setTotalFileSize(totalFileSize);
+        for (ProjectionEntries projectionEntries: entries.values()) {
+            if (projectionEntries.entries.size() > 0) {
+                ProjectionType projectionType = new ProjectionType();
+                projectionType.setName(projectionEntries.projection);
+                projectionType.setCacheDirectory(projectionEntries.cacheDirectory);
+                index.getProjection().add(projectionType);
+                for (CacheEntry ce: projectionEntries.entries) {
+                    EntryType entry = new EntryType();
+                    entry.setPixelPerDegree(ce.pixelPerDegree);
+                    entry.setEast(ce.east);
+                    entry.setNorth(ce.north);
+                    Calendar c = Calendar.getInstance();
+                    c.setTimeInMillis(ce.lastUsed);
+                    entry.setLastUsed(c);
+                    c = Calendar.getInstance();
+                    c.setTimeInMillis(ce.lastModified);
+                    entry.setLastModified(c);
+                    entry.setFilename(ce.filename);
+                    projectionType.getEntry().add(entry);
+                }
+            }
+        }
+        try {
+            JAXBContext context = JAXBContext.newInstance(
+                    WmsCacheType.class.getPackage().getName(),
+                    WmsCacheType.class.getClassLoader());
+            Marshaller marshaller = context.createMarshaller();
+            marshaller.marshal(index, new File(cacheDir, INDEX_FILENAME));
+        } catch (JAXBException e) {
+            System.err.println("Failed to save wms-cache file");
+            e.printStackTrace();
+        }
+
+    }
+
+    private File getImageFile(ProjectionEntries projection, CacheEntry entry) {
+        return new File(cacheDir, projection.cacheDirectory + "/" + entry.filename);
+    }
+
+    private BufferedImage loadImage(ProjectionEntries projectionEntries, CacheEntry entry) throws IOException {
+        entry.lastUsed = System.currentTimeMillis();
+
+        SoftReference<BufferedImage> memCache = memoryCache.get(entry);
+        if (memCache != null) {
+            BufferedImage result = memCache.get();
+            if (result != null)
+                return result;
+        }
+
+        try {
+            BufferedImage result = ImageIO.read(getImageFile(projectionEntries, entry));
+            if (result == null) {
+                projectionEntries.entries.remove(entry);
+                totalFileSizeDirty = true;
+            }
+            return result;
+        } catch (IOException e) {
+            projectionEntries.entries.remove(entry);
+            totalFileSizeDirty = true;
+            throw e;
+        }
+    }
+
+    private CacheEntry findEntry(ProjectionEntries projectionEntries, double pixelPerDegree, double east, double north) {
+        for (CacheEntry entry: projectionEntries.entries) {
+            if (entry.pixelPerDegree == pixelPerDegree && entry.east == east && entry.north == north)
+                return entry;
+        }
+        return null;
+    }
+
+    public synchronized BufferedImage getExactMatch(Projection projection, double pixelPerDegree, double east, double north) {
+        ProjectionEntries projectionEntries = getProjectionEntries(projection);
+        CacheEntry entry = findEntry(projectionEntries, pixelPerDegree, east, north);
+        if (entry != null) {
+            try {
+                entry.lastUsed = System.currentTimeMillis();
+                return loadImage(projectionEntries, entry);
+            } catch (IOException e) {
+                System.err.println("Unable to load file from wms cache");
+                e.printStackTrace();
+                return null;
+            }
+        }
+        return null;
+    }
+
+    public synchronized BufferedImage getPartialMatch(Projection projection, double pixelPerDegree, double east, double north) {
+        List<CacheEntry> matches = new ArrayList<WmsCache.CacheEntry>();
+
+        double minPPD = pixelPerDegree / 5;
+        double maxPPD = pixelPerDegree * 5;
+        ProjectionEntries projectionEntries = getProjectionEntries(projection);
+
+        ProjectionBounds bounds = new ProjectionBounds(east, north,
+                east + tileSize / pixelPerDegree, north + tileSize / pixelPerDegree);
+
+        //TODO Do not load tile if it is completely overlapped by other tile with better ppd
+        for (CacheEntry entry: projectionEntries.entries) {
+            if (entry.pixelPerDegree >= minPPD && entry.pixelPerDegree <= maxPPD && entry.bounds.intersects(bounds)) {
+                entry.lastUsed = System.currentTimeMillis();
+                matches.add(entry);
+            }
+        }
+
+        if (matches.isEmpty())
+            return null;
+
+
+        Collections.sort(matches, new Comparator<CacheEntry>() {
+            @Override
+            public int compare(CacheEntry o1, CacheEntry o2) {
+                return Double.compare(o2.pixelPerDegree, o1.pixelPerDegree);
+            }
+        });
+
+        //TODO Use alpha layer only when enabled on wms layer
+        BufferedImage result = new BufferedImage(tileSize, tileSize, BufferedImage.TYPE_4BYTE_ABGR);
+        Graphics2D g = result.createGraphics();
+
+        boolean drawAtLeastOnce = false;
+        for (CacheEntry ce: matches) {
+            BufferedImage img;
+            try {
+                img = loadImage(projectionEntries, ce);
+                memoryCache.put(ce, new SoftReference<BufferedImage>(img));
+            } catch (IOException e) {
+                continue;
+            }
+
+            drawAtLeastOnce = true;
+
+            int xDiff = (int)((ce.east - east) * pixelPerDegree);
+            int yDiff = (int)((ce.north - north) * pixelPerDegree);
+            int size = (int)(pixelPerDegree / ce.pixelPerDegree  * tileSize);
+
+            int x = xDiff;
+            int y = -size + tileSize - yDiff;
+
+            g.drawImage(img, x, y, size, size, null);
+        }
+
+        if (drawAtLeastOnce)
+            return result;
+        else
+            return null;
+    }
+
+    private String generateFileName(ProjectionEntries projectionEntries, double pixelPerDegree, Projection projection, double east, double north, String mimeType) {
+        LatLon ll1 = projection.eastNorth2latlon(new EastNorth(east, north));
+        LatLon ll2 = projection.eastNorth2latlon(new EastNorth(east + 100 / pixelPerDegree, north));
+        LatLon ll3 = projection.eastNorth2latlon(new EastNorth(east + tileSize / pixelPerDegree, north + tileSize / pixelPerDegree));
+
+        double deltaLat = Math.abs(ll3.lat() - ll1.lat());
+        double deltaLon = Math.abs(ll3.lon() - ll1.lon());
+        int precisionLat = Math.max(0, -(int)Math.ceil(Math.log10(deltaLat)) + 1);
+        int precisionLon = Math.max(0, -(int)Math.ceil(Math.log10(deltaLon)) + 1);
+
+        String zoom = NavigatableComponent.METRIC_SOM.getDistText(ll1.greatCircleDistance(ll2));
+        String extension;
+        if ("image/jpeg".equals(mimeType) || "image/jpg".equals(mimeType)) {
+            extension = "jpg";
+        } else if ("image/png".equals(mimeType)) {
+            extension = "png";
+        } else if ("image/gif".equals(mimeType)) {
+            extension = "gif";
+        } else {
+            extension = "dat";
+        }
+
+        int counter = 0;
+        FILENAME_LOOP:
+            while (true) {
+                String result = String.format("%s_%." + precisionLat + "f_%." + precisionLon +"f%s.%s", zoom, ll1.lat(), ll1.lon(), counter==0?"":"_" + counter, extension);
+                for (CacheEntry entry: projectionEntries.entries) {
+                    if (entry.filename.equals(result)) {
+                        counter++;
+                        continue FILENAME_LOOP;
+                    }
+                }
+                return result;
+            }
+    }
+
+    /**
+     * 
+     * @param img Used only when overlapping is used, when not used, used raw from imageData
+     * @param imageData
+     * @param projection
+     * @param pixelPerDegree
+     * @param east
+     * @param north
+     * @throws IOException
+     */
+    public synchronized void saveToCache(BufferedImage img, InputStream imageData, Projection projection, double pixelPerDegree, double east, double north) throws IOException {
+        ProjectionEntries projectionEntries = getProjectionEntries(projection);
+        CacheEntry entry = findEntry(projectionEntries, pixelPerDegree, east, north);
+        File imageFile;
+        if (entry == null) {
+            entry = new CacheEntry(pixelPerDegree, east, north, tileSize);
+            entry.lastUsed = System.currentTimeMillis();
+            entry.lastModified = entry.lastUsed;
+
+            String mimeType;
+            if (img != null) {
+                mimeType = "image/png";
+            } else {
+                mimeType = URLConnection.guessContentTypeFromStream(imageData);
+            }
+            entry.filename = generateFileName(projectionEntries, pixelPerDegree, projection, east, north, mimeType);
+            projectionEntries.entries.add(entry);
+            imageFile = getImageFile(projectionEntries, entry);
+        } else {
+            imageFile = getImageFile(projectionEntries, entry);
+            totalFileSize -= imageFile.length();
+        }
+
+        imageFile.getParentFile().mkdirs();
+
+        if (img != null) {
+            BufferedImage copy = new BufferedImage(tileSize, tileSize, img.getType());
+            copy.createGraphics().drawImage(img, 0, 0, tileSize, tileSize, 0, img.getHeight() - tileSize, tileSize, img.getHeight(), null);
+            ImageIO.write(copy, "png", imageFile);
+            totalFileSize += imageFile.length();
+        } else {
+            OutputStream os = new BufferedOutputStream(new FileOutputStream(imageFile));
+            try {
+                totalFileSize += Utils.copyStream(imageData, os);
+            } finally {
+                os.close();
+            }
+        }
+    }
+
+    public synchronized void cleanSmallFiles(int size) {
+        for (ProjectionEntries projectionEntries: entries.values()) {
+            Iterator<CacheEntry> it = projectionEntries.entries.iterator();
+            while (it.hasNext()) {
+                File file = getImageFile(projectionEntries, it.next());
+                long length = file.length();
+                if (length <= size) {
+                    if (length == 0) {
+                        totalFileSizeDirty = true; // File probably doesn't exist
+                    }
+                    totalFileSize -= size;
+                    file.delete();
+                    it.remove();
+                }
+            }
+        }
+    }
+
+    public static String printDate(Calendar c) {
+        return (new SimpleDateFormat("yyyy-MM-dd")).format(c.getTime());
+    }
+
+    private boolean isInsideAreaToCache(CacheEntry cacheEntry) {
+        for (ProjectionBounds b: areaToCache) {
+            if (cacheEntry.bounds.intersects(b))
+                return true;
+        }
+        return false;
+    }
+
+    public void setAreaToCache(Set<ProjectionBounds> areaToCache) {
+        this.areaToCache = areaToCache;
+        Iterator<CacheEntry> it = memoryCache.keySet().iterator();
+        while (it.hasNext()) {
+            if (!isInsideAreaToCache(it.next())) {
+                it.remove();
+            }
+        }
+    }
+
+    public Set<ProjectionBounds> getAreaToCache() {
+        return areaToCache;
+    }
+}
Index: trunk/src/org/openstreetmap/josm/data/imagery/types/Adapter1.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/imagery/types/Adapter1.java	(revision 4065)
+++ trunk/src/org/openstreetmap/josm/data/imagery/types/Adapter1.java	(revision 4065)
@@ -0,0 +1,32 @@
+//
+// This file was generated by the JavaTM Architecture for XML Binding(JAXB) Reference Implementation, vJAXB 2.1.10 in JDK 6
+// See <a href="http://java.sun.com/xml/jaxb">http://java.sun.com/xml/jaxb</a>
+// Any modifications to this file will be lost upon recompilation of the source schema.
+// Generated on: 2011.01.08 at 06:04:30 PM CET
+//
+
+
+package org.openstreetmap.josm.data.imagery.types;
+
+import java.util.Calendar;
+
+import javax.xml.bind.annotation.adapters.XmlAdapter;
+
+public class Adapter1
+extends XmlAdapter<String, Calendar>
+{
+
+
+    @Override
+    public Calendar unmarshal(String value) {
+        return (javax.xml.bind.DatatypeConverter.parseDate(value));
+    }
+
+    @Override
+    public String marshal(Calendar value) {
+        if (value == null)
+            return null;
+        return (org.openstreetmap.josm.data.imagery.WmsCache.printDate(value));
+    }
+
+}
Index: trunk/src/org/openstreetmap/josm/data/imagery/types/EntryType.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/imagery/types/EntryType.java	(revision 4065)
+++ trunk/src/org/openstreetmap/josm/data/imagery/types/EntryType.java	(revision 4065)
@@ -0,0 +1,190 @@
+//
+// This file was generated by the JavaTM Architecture for XML Binding(JAXB) Reference Implementation, vJAXB 2.1.10 in JDK 6
+// See <a href="http://java.sun.com/xml/jaxb">http://java.sun.com/xml/jaxb</a>
+// Any modifications to this file will be lost upon recompilation of the source schema.
+// Generated on: 2011.01.09 at 07:33:18 PM CET
+//
+
+
+package org.openstreetmap.josm.data.imagery.types;
+
+import java.util.Calendar;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlSchemaType;
+import javax.xml.bind.annotation.XmlType;
+import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
+
+
+/**
+ * <p>Java class for entry complex type.
+ * 
+ * <p>The following schema fragment specifies the expected content contained within this class.
+ * 
+ * <pre>
+ * &lt;complexType name="entry">
+ *   &lt;complexContent>
+ *     &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+ *       &lt;sequence>
+ *         &lt;element name="pixelPerDegree" type="{http://www.w3.org/2001/XMLSchema}double"/>
+ *         &lt;element name="east" type="{http://www.w3.org/2001/XMLSchema}double"/>
+ *         &lt;element name="north" type="{http://www.w3.org/2001/XMLSchema}double"/>
+ *         &lt;element name="lastUsed" type="{http://www.w3.org/2001/XMLSchema}date"/>
+ *         &lt;element name="lastModified" type="{http://www.w3.org/2001/XMLSchema}date"/>
+ *         &lt;element name="filename" type="{http://www.w3.org/2001/XMLSchema}string"/>
+ *       &lt;/sequence>
+ *     &lt;/restriction>
+ *   &lt;/complexContent>
+ * &lt;/complexType>
+ * </pre>
+ * 
+ * 
+ */
+@XmlAccessorType(XmlAccessType.FIELD)
+@XmlType(name = "entry", propOrder = {
+        "pixelPerDegree",
+        "east",
+        "north",
+        "lastUsed",
+        "lastModified",
+        "filename"
+})
+public class EntryType {
+
+    protected double pixelPerDegree;
+    protected double east;
+    protected double north;
+    @XmlElement(required = true, type = String.class)
+    @XmlJavaTypeAdapter(Adapter1 .class)
+    @XmlSchemaType(name = "date")
+    protected Calendar lastUsed;
+    @XmlElement(required = true, type = String.class)
+    @XmlJavaTypeAdapter(Adapter1 .class)
+    @XmlSchemaType(name = "date")
+    protected Calendar lastModified;
+    @XmlElement(required = true)
+    protected String filename;
+
+    /**
+     * Gets the value of the pixelPerDegree property.
+     * 
+     */
+    public double getPixelPerDegree() {
+        return pixelPerDegree;
+    }
+
+    /**
+     * Sets the value of the pixelPerDegree property.
+     * 
+     */
+    public void setPixelPerDegree(double value) {
+        this.pixelPerDegree = value;
+    }
+
+    /**
+     * Gets the value of the east property.
+     * 
+     */
+    public double getEast() {
+        return east;
+    }
+
+    /**
+     * Sets the value of the east property.
+     * 
+     */
+    public void setEast(double value) {
+        this.east = value;
+    }
+
+    /**
+     * Gets the value of the north property.
+     * 
+     */
+    public double getNorth() {
+        return north;
+    }
+
+    /**
+     * Sets the value of the north property.
+     * 
+     */
+    public void setNorth(double value) {
+        this.north = value;
+    }
+
+    /**
+     * Gets the value of the lastUsed property.
+     * 
+     * @return
+     *     possible object is
+     *     {@link String }
+     * 
+     */
+    public Calendar getLastUsed() {
+        return lastUsed;
+    }
+
+    /**
+     * Sets the value of the lastUsed property.
+     * 
+     * @param value
+     *     allowed object is
+     *     {@link String }
+     * 
+     */
+    public void setLastUsed(Calendar value) {
+        this.lastUsed = value;
+    }
+
+    /**
+     * Gets the value of the lastModified property.
+     * 
+     * @return
+     *     possible object is
+     *     {@link String }
+     * 
+     */
+    public Calendar getLastModified() {
+        return lastModified;
+    }
+
+    /**
+     * Sets the value of the lastModified property.
+     * 
+     * @param value
+     *     allowed object is
+     *     {@link String }
+     * 
+     */
+    public void setLastModified(Calendar value) {
+        this.lastModified = value;
+    }
+
+    /**
+     * Gets the value of the filename property.
+     * 
+     * @return
+     *     possible object is
+     *     {@link String }
+     * 
+     */
+    public String getFilename() {
+        return filename;
+    }
+
+    /**
+     * Sets the value of the filename property.
+     * 
+     * @param value
+     *     allowed object is
+     *     {@link String }
+     * 
+     */
+    public void setFilename(String value) {
+        this.filename = value;
+    }
+
+}
Index: trunk/src/org/openstreetmap/josm/data/imagery/types/ObjectFactory.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/imagery/types/ObjectFactory.java	(revision 4065)
+++ trunk/src/org/openstreetmap/josm/data/imagery/types/ObjectFactory.java	(revision 4065)
@@ -0,0 +1,63 @@
+//
+// This file was generated by the JavaTM Architecture for XML Binding(JAXB) Reference Implementation, vJAXB 2.1.10 in JDK 6 
+// See <a href="http://java.sun.com/xml/jaxb">http://java.sun.com/xml/jaxb</a> 
+// Any modifications to this file will be lost upon recompilation of the source schema. 
+// Generated on: 2011.01.09 at 07:33:18 PM CET 
+//
+
+
+package org.openstreetmap.josm.data.imagery.types;
+
+import javax.xml.bind.annotation.XmlRegistry;
+
+
+/**
+ * This object contains factory methods for each 
+ * Java content interface and Java element interface 
+ * generated in the org.openstreetmap.josm.data.imagery.types package. 
+ * <p>An ObjectFactory allows you to programatically 
+ * construct new instances of the Java representation 
+ * for XML content. The Java representation of XML 
+ * content can consist of schema derived interfaces 
+ * and classes representing the binding of schema 
+ * type definitions, element declarations and model 
+ * groups.  Factory methods for each of these are 
+ * provided in this class.
+ * 
+ */
+@XmlRegistry
+public class ObjectFactory {
+
+
+    /**
+     * Create a new ObjectFactory that can be used to create new instances of schema derived classes for package: org.openstreetmap.josm.data.imagery.types
+     * 
+     */
+    public ObjectFactory() {
+    }
+
+    /**
+     * Create an instance of {@link WmsCacheType }
+     * 
+     */
+    public WmsCacheType createWmsCacheType() {
+        return new WmsCacheType();
+    }
+
+    /**
+     * Create an instance of {@link ProjectionType }
+     * 
+     */
+    public ProjectionType createProjectionType() {
+        return new ProjectionType();
+    }
+
+    /**
+     * Create an instance of {@link EntryType }
+     * 
+     */
+    public EntryType createEntryType() {
+        return new EntryType();
+    }
+
+}
Index: trunk/src/org/openstreetmap/josm/data/imagery/types/ProjectionType.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/imagery/types/ProjectionType.java	(revision 4065)
+++ trunk/src/org/openstreetmap/josm/data/imagery/types/ProjectionType.java	(revision 4065)
@@ -0,0 +1,129 @@
+//
+// This file was generated by the JavaTM Architecture for XML Binding(JAXB) Reference Implementation, vJAXB 2.1.10 in JDK 6 
+// See <a href="http://java.sun.com/xml/jaxb">http://java.sun.com/xml/jaxb</a> 
+// Any modifications to this file will be lost upon recompilation of the source schema. 
+// Generated on: 2011.01.09 at 07:33:18 PM CET 
+//
+
+
+package org.openstreetmap.josm.data.imagery.types;
+
+import java.util.ArrayList;
+import java.util.List;
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlAttribute;
+import javax.xml.bind.annotation.XmlType;
+
+
+/**
+ * <p>Java class for projection complex type.
+ * 
+ * <p>The following schema fragment specifies the expected content contained within this class.
+ * 
+ * <pre>
+ * &lt;complexType name="projection">
+ *   &lt;complexContent>
+ *     &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+ *       &lt;sequence>
+ *         &lt;element name="entry" type="{http://josm.openstreetmap.de/wms-cache}entry" maxOccurs="unbounded" minOccurs="0"/>
+ *       &lt;/sequence>
+ *       &lt;attribute name="name" type="{http://www.w3.org/2001/XMLSchema}string" />
+ *       &lt;attribute name="cache-directory" type="{http://www.w3.org/2001/XMLSchema}string" />
+ *     &lt;/restriction>
+ *   &lt;/complexContent>
+ * &lt;/complexType>
+ * </pre>
+ * 
+ * 
+ */
+@XmlAccessorType(XmlAccessType.FIELD)
+@XmlType(name = "projection", propOrder = {
+    "entry"
+})
+public class ProjectionType {
+
+    protected List<EntryType> entry;
+    @XmlAttribute
+    protected String name;
+    @XmlAttribute(name = "cache-directory")
+    protected String cacheDirectory;
+
+    /**
+     * Gets the value of the entry property.
+     * 
+     * <p>
+     * This accessor method returns a reference to the live list,
+     * not a snapshot. Therefore any modification you make to the
+     * returned list will be present inside the JAXB object.
+     * This is why there is not a <CODE>set</CODE> method for the entry property.
+     * 
+     * <p>
+     * For example, to add a new item, do as follows:
+     * <pre>
+     *    getEntry().add(newItem);
+     * </pre>
+     * 
+     * 
+     * <p>
+     * Objects of the following type(s) are allowed in the list
+     * {@link EntryType }
+     * 
+     * 
+     */
+    public List<EntryType> getEntry() {
+        if (entry == null) {
+            entry = new ArrayList<EntryType>();
+        }
+        return this.entry;
+    }
+
+    /**
+     * Gets the value of the name property.
+     * 
+     * @return
+     *     possible object is
+     *     {@link String }
+     *     
+     */
+    public String getName() {
+        return name;
+    }
+
+    /**
+     * Sets the value of the name property.
+     * 
+     * @param value
+     *     allowed object is
+     *     {@link String }
+     *     
+     */
+    public void setName(String value) {
+        this.name = value;
+    }
+
+    /**
+     * Gets the value of the cacheDirectory property.
+     * 
+     * @return
+     *     possible object is
+     *     {@link String }
+     *     
+     */
+    public String getCacheDirectory() {
+        return cacheDirectory;
+    }
+
+    /**
+     * Sets the value of the cacheDirectory property.
+     * 
+     * @param value
+     *     allowed object is
+     *     {@link String }
+     *     
+     */
+    public void setCacheDirectory(String value) {
+        this.cacheDirectory = value;
+    }
+
+}
Index: trunk/src/org/openstreetmap/josm/data/imagery/types/WmsCacheType.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/imagery/types/WmsCacheType.java	(revision 4065)
+++ trunk/src/org/openstreetmap/josm/data/imagery/types/WmsCacheType.java	(revision 4065)
@@ -0,0 +1,115 @@
+//
+// This file was generated by the JavaTM Architecture for XML Binding(JAXB) Reference Implementation, vJAXB 2.1.10 in JDK 6 
+// See <a href="http://java.sun.com/xml/jaxb">http://java.sun.com/xml/jaxb</a> 
+// Any modifications to this file will be lost upon recompilation of the source schema. 
+// Generated on: 2011.01.09 at 07:33:18 PM CET 
+//
+
+
+package org.openstreetmap.josm.data.imagery.types;
+
+import java.util.ArrayList;
+import java.util.List;
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlAttribute;
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlType;
+
+
+/**
+ * <p>Java class for anonymous complex type.
+ * 
+ * <p>The following schema fragment specifies the expected content contained within this class.
+ * 
+ * <pre>
+ * &lt;complexType>
+ *   &lt;complexContent>
+ *     &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+ *       &lt;sequence>
+ *         &lt;element name="projection" type="{http://josm.openstreetmap.de/wms-cache}projection" maxOccurs="unbounded" minOccurs="0"/>
+ *       &lt;/sequence>
+ *       &lt;attribute name="tileSize" use="required" type="{http://www.w3.org/2001/XMLSchema}int" />
+ *       &lt;attribute name="totalFileSize" use="required" type="{http://www.w3.org/2001/XMLSchema}int" />
+ *     &lt;/restriction>
+ *   &lt;/complexContent>
+ * &lt;/complexType>
+ * </pre>
+ * 
+ * 
+ */
+@XmlAccessorType(XmlAccessType.FIELD)
+@XmlType(name = "", propOrder = {
+    "projection"
+})
+@XmlRootElement(name = "wms-cache")
+public class WmsCacheType {
+
+    protected List<ProjectionType> projection;
+    @XmlAttribute(required = true)
+    protected int tileSize;
+    @XmlAttribute(required = true)
+    protected int totalFileSize;
+
+    /**
+     * Gets the value of the projection property.
+     * 
+     * <p>
+     * This accessor method returns a reference to the live list,
+     * not a snapshot. Therefore any modification you make to the
+     * returned list will be present inside the JAXB object.
+     * This is why there is not a <CODE>set</CODE> method for the projection property.
+     * 
+     * <p>
+     * For example, to add a new item, do as follows:
+     * <pre>
+     *    getProjection().add(newItem);
+     * </pre>
+     * 
+     * 
+     * <p>
+     * Objects of the following type(s) are allowed in the list
+     * {@link ProjectionType }
+     * 
+     * 
+     */
+    public List<ProjectionType> getProjection() {
+        if (projection == null) {
+            projection = new ArrayList<ProjectionType>();
+        }
+        return this.projection;
+    }
+
+    /**
+     * Gets the value of the tileSize property.
+     * 
+     */
+    public int getTileSize() {
+        return tileSize;
+    }
+
+    /**
+     * Sets the value of the tileSize property.
+     * 
+     */
+    public void setTileSize(int value) {
+        this.tileSize = value;
+    }
+
+    /**
+     * Gets the value of the totalFileSize property.
+     * 
+     */
+    public int getTotalFileSize() {
+        return totalFileSize;
+    }
+
+    /**
+     * Sets the value of the totalFileSize property.
+     * 
+     */
+    public void setTotalFileSize(int value) {
+        this.totalFileSize = value;
+    }
+
+}
Index: trunk/src/org/openstreetmap/josm/data/imagery/types/package-info.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/imagery/types/package-info.java	(revision 4065)
+++ trunk/src/org/openstreetmap/josm/data/imagery/types/package-info.java	(revision 4065)
@@ -0,0 +1,9 @@
+//
+// This file was generated by the JavaTM Architecture for XML Binding(JAXB) Reference Implementation, vJAXB 2.1.10 in JDK 6 
+// See <a href="http://java.sun.com/xml/jaxb">http://java.sun.com/xml/jaxb</a> 
+// Any modifications to this file will be lost upon recompilation of the source schema. 
+// Generated on: 2011.01.09 at 07:33:18 PM CET 
+//
+
+@javax.xml.bind.annotation.XmlSchema(namespace = "http://josm.openstreetmap.de/wms-cache", elementFormDefault = javax.xml.bind.annotation.XmlNsForm.QUALIFIED)
+package org.openstreetmap.josm.data.imagery.types;
Index: trunk/src/org/openstreetmap/josm/data/imagery/wms-cache.xsd
===================================================================
--- trunk/src/org/openstreetmap/josm/data/imagery/wms-cache.xsd	(revision 4065)
+++ trunk/src/org/openstreetmap/josm/data/imagery/wms-cache.xsd	(revision 4065)
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<schema xmlns="http://www.w3.org/2001/XMLSchema" targetNamespace="http://josm.openstreetmap.de/wms-cache"
+	xmlns:tns="http://josm.openstreetmap.de/wms-cache" xmlns:jaxb="http://java.sun.com/xml/ns/jaxb"
+	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://java.sun.com/xml/ns/jaxb http://java.sun.com/xml/ns/jaxb/bindingschema_2_0.xsd"
+	elementFormDefault="qualified" jaxb:version="2.0">
+	
+	<annotation>
+		<appinfo>
+			<jaxb:schemaBindings>
+				<jaxb:package name="org.openstreetmap.josm.data.imagery.types">
+				</jaxb:package>
+				<jaxb:nameXmlTransform>
+					<jaxb:typeName suffix="Type" />
+					<jaxb:elementName suffix="Type" />
+				</jaxb:nameXmlTransform>
+			</jaxb:schemaBindings>
+			<jaxb:globalBindings>
+				<jaxb:javaType name="java.util.Calendar" xmlType="date"
+					parseMethod="javax.xml.bind.DatatypeConverter.parseDate"
+					printMethod="org.openstreetmap.josm.data.imagery.WmsCache.printDate" />
+			</jaxb:globalBindings>
+		</appinfo>
+	</annotation>
+
+	<element name="wms-cache">
+		<complexType>
+			<sequence>
+				<element name="projection" type="tns:projection" minOccurs="0"
+					maxOccurs="unbounded" />
+			</sequence>
+			<attribute name="tileSize" type="int" use="required" />
+			<attribute name="totalFileSize" type="int" use="required"/>
+		</complexType>
+	</element>
+	
+	<complexType name="projection">
+		<sequence>
+			<element name="entry" type="tns:entry" minOccurs="0" maxOccurs="unbounded"/>
+		</sequence>
+		<attribute name="name" type="string"/>
+		<attribute name="cache-directory" type="string"/>
+	</complexType>
+
+	<complexType name="entry">
+		<sequence>
+			<element name="pixelPerDegree" type="double" />
+			<element name="east" type="double" />
+			<element name="north" type="double" />
+			<element name="lastUsed" type="date" />
+			<element name="lastModified" type="date" />
+			<element name="filename" type="string" />
+		</sequence>
+	</complexType>
+</schema>
Index: trunk/src/org/openstreetmap/josm/data/osm/visitor/BoundingXYVisitor.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/osm/visitor/BoundingXYVisitor.java	(revision 4064)
+++ trunk/src/org/openstreetmap/josm/data/osm/visitor/BoundingXYVisitor.java	(revision 4065)
@@ -56,6 +56,6 @@
         if(b != null)
         {
-            visit(b.min);
-            visit(b.max);
+            visit(b.getMin());
+            visit(b.getMax());
         }
     }
@@ -84,5 +84,5 @@
     public boolean hasExtend()
     {
-        return bounds != null && !bounds.min.equals(bounds.max);
+        return bounds != null && !bounds.getMin().equals(bounds.getMax());
     }
 
@@ -113,6 +113,6 @@
         if (bounds == null)
             return;
-        LatLon minLatlon = Main.proj.eastNorth2latlon(bounds.min);
-        LatLon maxLatlon = Main.proj.eastNorth2latlon(bounds.max);
+        LatLon minLatlon = Main.proj.eastNorth2latlon(bounds.getMin());
+        LatLon maxLatlon = Main.proj.eastNorth2latlon(bounds.getMax());
         bounds = new ProjectionBounds(
                 Main.proj.latlon2eastNorth(new LatLon(minLatlon.lat() - enlargeDegree, minLatlon.lon() - enlargeDegree)),
Index: trunk/src/org/openstreetmap/josm/gui/MapSlider.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/MapSlider.java	(revision 4064)
+++ trunk/src/org/openstreetmap/josm/gui/MapSlider.java	(revision 4065)
@@ -35,8 +35,8 @@
         ProjectionBounds current = this.mv.getProjectionBounds();
 
-        double cur_e = current.max.east()-current.min.east();
-        double cur_n = current.max.north()-current.min.north();
-        double e = world.max.east()-world.min.east();
-        double n = world.max.north()-world.min.north();
+        double cur_e = current.maxEast-current.minEast;
+        double cur_n = current.maxNorth-current.minNorth;
+        double e = world.maxEast-world.minEast;
+        double n = world.maxNorth-world.minNorth;
         int zoom = 0;
 
@@ -59,6 +59,6 @@
         ProjectionBounds world = this.mv.getMaxProjectionBounds();
         double fact = Math.pow(1.1, getValue());
-        double es = world.max.east()-world.min.east();
-        double n = world.max.north()-world.min.north();
+        double es = world.maxEast-world.minEast;
+        double n = world.maxNorth-world.minNorth;
 
         this.mv.zoomTo(new ProjectionBounds(this.mv.getCenter(), es/fact, n/fact));
Index: trunk/src/org/openstreetmap/josm/gui/NavigatableComponent.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/NavigatableComponent.java	(revision 4064)
+++ trunk/src/org/openstreetmap/josm/gui/NavigatableComponent.java	(revision 4065)
@@ -24,5 +24,4 @@
 
 import javax.swing.JComponent;
-import javax.swing.SwingUtilities;
 
 import org.openstreetmap.josm.Main;
@@ -367,5 +366,5 @@
 
     /**
-     * Create a thread that moves the viewport to the given center in an 
+     * Create a thread that moves the viewport to the given center in an
      * animated fashion.
      */
@@ -382,14 +381,14 @@
 
             new Thread(
-                new Runnable() {
-                    public void run() {
-                        for (int i=0; i<frames; i++)
-                        {
-                            // fixme - not use zoom history here
-                            zoomTo(oldCenter.interpolate(finalNewCenter, (double) (i+1) / (double) frames));
-                            try { Thread.sleep(1000 / fps); } catch (InterruptedException ex) { };
+                    new Runnable() {
+                        public void run() {
+                            for (int i=0; i<frames; i++)
+                            {
+                                // fixme - not use zoom history here
+                                zoomTo(oldCenter.interpolate(finalNewCenter, (i+1) / frames));
+                                try { Thread.sleep(1000 / fps); } catch (InterruptedException ex) { };
+                            }
                         }
                     }
-                }
             ).start();
         }
@@ -425,6 +424,6 @@
         }
 
-        double scaleX = (box.max.east()-box.min.east())/w;
-        double scaleY = (box.max.north()-box.min.north())/h;
+        double scaleX = (box.maxEast-box.minEast)/w;
+        double scaleY = (box.maxNorth-box.minNorth)/h;
         double newScale = Math.max(scaleX, scaleY);
 
@@ -1231,7 +1230,6 @@
         if(Cursors.size() > 0) {
             CursorInfo l = Cursors.getLast();
-            if(l != null && l.cursor == cursor && l.object == reference) {
+            if(l != null && l.cursor == cursor && l.object == reference)
                 return;
-            }
             stripCursors(reference);
         }
@@ -1253,8 +1251,9 @@
         stripCursors(reference);
         if(l != null && l.object == reference) {
-            if(Cursors.size() == 0)
+            if(Cursors.size() == 0) {
                 setCursor(null);
-            else
+            } else {
                 setCursor(Cursors.getLast().cursor);
+            }
         }
     }
@@ -1263,6 +1262,7 @@
         LinkedList<CursorInfo> c = new LinkedList<CursorInfo>();
         for(CursorInfo i : Cursors) {
-            if(i.object != reference)
+            if(i.object != reference) {
                 c.add(i);
+            }
         }
         Cursors = c;
Index: trunk/src/org/openstreetmap/josm/gui/layer/ImageryLayer.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/layer/ImageryLayer.java	(revision 4064)
+++ trunk/src/org/openstreetmap/josm/gui/layer/ImageryLayer.java	(revision 4065)
@@ -85,5 +85,5 @@
         if (Main.map == null || Main.map.mapView == null) return Main.proj.getDefaultZoomInPPD();
         ProjectionBounds bounds = Main.map.mapView.getProjectionBounds();
-        return Main.map.mapView.getWidth() / (bounds.max.east() - bounds.min.east());
+        return Main.map.mapView.getWidth() / (bounds.maxEast - bounds.minEast);
     }
 
Index: trunk/src/org/openstreetmap/josm/gui/layer/WMSLayer.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/layer/WMSLayer.java	(revision 4064)
+++ trunk/src/org/openstreetmap/josm/gui/layer/WMSLayer.java	(revision 4065)
@@ -16,6 +16,8 @@
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Set;
 import java.util.concurrent.locks.Condition;
 import java.util.concurrent.locks.Lock;
@@ -41,4 +43,5 @@
 import org.openstreetmap.josm.data.imagery.ImageryInfo.ImageryType;
 import org.openstreetmap.josm.data.imagery.ImageryLayerInfo;
+import org.openstreetmap.josm.data.imagery.WmsCache;
 import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor;
 import org.openstreetmap.josm.data.preferences.BooleanProperty;
@@ -47,5 +50,4 @@
 import org.openstreetmap.josm.gui.dialogs.LayerListDialog;
 import org.openstreetmap.josm.gui.dialogs.LayerListPopup;
-import org.openstreetmap.josm.io.CacheFiles;
 import org.openstreetmap.josm.io.imagery.Grabber;
 import org.openstreetmap.josm.io.imagery.HTMLGrabber;
@@ -79,4 +81,6 @@
     protected ImageryInfo info;
     protected final MapView mv;
+    public WmsCache cache;
+
 
     // Image index boundary for current view
@@ -118,4 +122,16 @@
         setBackgroundLayer(true); /* set global background variable */
         initializeImages();
+        if (info.getUrl() != null) {
+            for (WMSLayer layer: Main.map.mapView.getLayersOfType(WMSLayer.class)) {
+                if (layer.getInfo().getUrl().equals(info.getUrl())) {
+                    cache = layer.cache;
+                    break;
+                }
+            }
+            if (cache == null) {
+                cache = new WmsCache(info.getUrl(), imageSize);
+                cache.loadIndex();
+            }
+        }
         this.info = new ImageryInfo(info);
         if(this.info.getPixelPerDegree() == 0.0) {
@@ -157,4 +173,7 @@
         cancelGrabberThreads(false);
         Main.pref.removePreferenceChangeListener(this);
+        if (cache != null) {
+            cache.saveIndex();
+        }
     }
 
@@ -205,16 +224,17 @@
 
         ProjectionBounds bounds = mv.getProjectionBounds();
-        bminx= getImageXIndex(bounds.min.east());
-        bminy= getImageYIndex(bounds.min.north());
-        bmaxx= getImageXIndex(bounds.max.east());
-        bmaxy= getImageYIndex(bounds.max.north());
-
-        leftEdge = (int)(bounds.min.east() * getPPD());
-        bottomEdge = (int)(bounds.min.north() * getPPD());
+        bminx= getImageXIndex(bounds.minEast);
+        bminy= getImageYIndex(bounds.minNorth);
+        bmaxx= getImageXIndex(bounds.maxEast);
+        bmaxy= getImageYIndex(bounds.maxNorth);
+
+        leftEdge = (int)(bounds.minEast * getPPD());
+        bottomEdge = (int)(bounds.minNorth * getPPD());
 
         if (zoomIsTooBig()) {
-            for(int x = bminx; x<=bmaxx; ++x) {
-                for(int y = bminy; y<=bmaxy; ++y) {
-                    images[modulo(x,dax)][modulo(y,day)].paint(g, mv, x, y, leftEdge, bottomEdge);
+            for(int x = 0; x<images.length; ++x) {
+                for(int y = 0; y<images[0].length; ++y) {
+                    GeorefImage image = images[x][y];
+                    image.paint(g, mv, image.getXIndex(), image.getYIndex(), leftEdge, bottomEdge);
                 }
             }
@@ -305,4 +325,8 @@
     }
 
+    public boolean isOverlapEnabled() {
+        return WMSLayer.PROP_OVERLAP.get() && (WMSLayer.PROP_OVERLAP_EAST.get() > 0 || WMSLayer.PROP_OVERLAP_NORTH.get() > 0);
+    }
+
     /**
      * 
@@ -310,5 +334,5 @@
      */
     public BufferedImage normalizeImage(BufferedImage img) {
-        if (WMSLayer.PROP_OVERLAP.get() && (WMSLayer.PROP_OVERLAP_EAST.get() > 0 || WMSLayer.PROP_OVERLAP_NORTH.get() > 0)) {
+        if (isOverlapEnabled()) {
             BufferedImage copy = img;
             img = new BufferedImage(imageSize, imageSize, copy.getType());
@@ -357,4 +381,5 @@
 
         gatherFinishedRequests();
+        Set<ProjectionBounds> areaToCache = new HashSet<ProjectionBounds>();
 
         for(int x = bminx; x<=bmaxx; ++x) {
@@ -362,9 +387,15 @@
                 GeorefImage img = images[modulo(x,dax)][modulo(y,day)];
                 if (!img.paint(g, mv, x, y, leftEdge, bottomEdge)) {
-                    WMSRequest request = new WMSRequest(x, y, info.getPixelPerDegree(), real);
+                    WMSRequest request = new WMSRequest(x, y, info.getPixelPerDegree(), real, true);
                     addRequest(request);
-                }
-            }
-        }
+                    areaToCache.add(new ProjectionBounds(getEastNorth(x, y), getEastNorth(x + 1, y + 1)));
+                } else if (img.getState() == State.PARTLY_IN_CACHE && autoDownloadEnabled) {
+                    WMSRequest request = new WMSRequest(x, y, info.getPixelPerDegree(), real, false);
+                    addRequest(request);
+                    areaToCache.add(new ProjectionBounds(getEastNorth(x, y), getEastNorth(x + 1, y + 1)));
+                }
+            }
+        }
+        cache.setAreaToCache(areaToCache);
     }
 
@@ -548,8 +579,12 @@
         @Override
         public void actionPerformed(ActionEvent ev) {
-            initializeImages();
             resolution = mv.getDist100PixelText();
             info.setPixelPerDegree(getPPD());
             settingsChanged = true;
+            for(int x = 0; x<dax; ++x) {
+                for(int y = 0; y<day; ++y) {
+                    images[x][y].changePosition(-1, -1);
+                }
+            }
             mv.repaint();
         }
@@ -564,5 +599,5 @@
             // Delete small files, because they're probably blank tiles.
             // See https://josm.openstreetmap.de/ticket/2307
-            Grabber.cache.customCleanUp(CacheFiles.CLEAN_SMALL_FILES, 4096);
+            cache.cleanSmallFiles(4096);
 
             for (int x = 0; x < dax; ++x) {
@@ -570,6 +605,5 @@
                     GeorefImage img = images[modulo(x,dax)][modulo(y,day)];
                     if(img.getState() == State.FAILED){
-                        addRequest(new WMSRequest(img.getXIndex(), img.getYIndex(), info.getPixelPerDegree(), true));
-                        mv.repaint();
+                        addRequest(new WMSRequest(img.getXIndex(), img.getYIndex(), info.getPixelPerDegree(), true, false));
                     }
                 }
@@ -680,6 +714,11 @@
                 settingsChanged = true;
                 mv.repaint();
+                if (cache != null) {
+                    cache.saveIndex();
+                    cache = null;
+                }
                 if(info.getUrl() != null)
                 {
+                    cache = new WmsCache(info.getUrl(), imageSize);
                     startGrabberThreads();
                 }
@@ -737,5 +776,5 @@
                         GeorefImage img = images[modulo(x,dax)][modulo(y,day)];
                         if(img.getState() == State.NOT_IN_CACHE){
-                            addRequest(new WMSRequest(img.getXIndex(), img.getYIndex(), info.getPixelPerDegree(), false));
+                            addRequest(new WMSRequest(img.getXIndex(), img.getYIndex(), info.getPixelPerDegree(), false, true));
                         }
                     }
Index: trunk/src/org/openstreetmap/josm/io/imagery/Grabber.java
===================================================================
--- trunk/src/org/openstreetmap/josm/io/imagery/Grabber.java	(revision 4064)
+++ trunk/src/org/openstreetmap/josm/io/imagery/Grabber.java	(revision 4065)
@@ -9,9 +9,6 @@
 import org.openstreetmap.josm.gui.MapView;
 import org.openstreetmap.josm.gui.layer.WMSLayer;
-import org.openstreetmap.josm.io.CacheFiles;
 
 abstract public class Grabber implements Runnable {
-    public final static CacheFiles cache = new CacheFiles("imagery", false);
-
     protected final MapView mv;
     protected final WMSLayer layer;
@@ -32,15 +29,14 @@
                 layer.getEastNorth(request.getXIndex(), request.getYIndex()),
                 layer.getEastNorth(request.getXIndex() + 1, request.getYIndex() + 1));
-        if (b.min != null && b.max != null && WMSLayer.PROP_OVERLAP.get()) {
-            double eastSize =  b.max.east() - b.min.east();
-            double northSize =  b.max.north() - b.min.north();
+        if (WMSLayer.PROP_OVERLAP.get()) {
+            double eastSize =  b.maxEast - b.minEast;
+            double northSize =  b.maxNorth - b.minNorth;
 
             double eastCoef = WMSLayer.PROP_OVERLAP_EAST.get() / 100.0;
             double northCoef = WMSLayer.PROP_OVERLAP_NORTH.get() / 100.0;
 
-            this.b = new ProjectionBounds( new EastNorth(b.min.east(),
-                    b.min.north()),
-                    new EastNorth(b.max.east() + eastCoef * eastSize,
-                            b.max.north() + northCoef * northSize));
+            this.b = new ProjectionBounds(b.getMin(),
+                    new EastNorth(b.maxEast + eastCoef * eastSize,
+                            b.maxNorth + northCoef * northSize));
         }
 
Index: trunk/src/org/openstreetmap/josm/io/imagery/HTMLGrabber.java
===================================================================
--- trunk/src/org/openstreetmap/josm/io/imagery/HTMLGrabber.java	(revision 4064)
+++ trunk/src/org/openstreetmap/josm/io/imagery/HTMLGrabber.java	(revision 4065)
@@ -3,4 +3,6 @@
 
 import java.awt.image.BufferedImage;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.net.URL;
@@ -11,7 +13,9 @@
 import javax.imageio.ImageIO;
 
+import org.openstreetmap.josm.Main;
 import org.openstreetmap.josm.data.preferences.StringProperty;
 import org.openstreetmap.josm.gui.MapView;
 import org.openstreetmap.josm.gui.layer.WMSLayer;
+import org.openstreetmap.josm.tools.Utils;
 
 public class HTMLGrabber extends WMSGrabber {
@@ -43,6 +47,11 @@
         }
 
-        BufferedImage img = layer.normalizeImage(ImageIO.read(browser.getInputStream()));
-        cache.saveImg(urlstring, img);
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        Utils.copyStream(browser.getInputStream(), baos);
+        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
+        BufferedImage img = layer.normalizeImage(ImageIO.read(bais));
+        bais.reset();
+        layer.cache.saveToCache(layer.isOverlapEnabled()?img:null, bais, Main.proj, pixelPerDegree, b.minEast, b.minNorth);
+
         return img;
     }
Index: trunk/src/org/openstreetmap/josm/io/imagery/WMSGrabber.java
===================================================================
--- trunk/src/org/openstreetmap/josm/io/imagery/WMSGrabber.java	(revision 4064)
+++ trunk/src/org/openstreetmap/josm/io/imagery/WMSGrabber.java	(revision 4065)
@@ -6,4 +6,6 @@
 import java.awt.image.BufferedImage;
 import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
@@ -34,4 +36,5 @@
 import org.openstreetmap.josm.io.OsmTransferException;
 import org.openstreetmap.josm.io.ProgressInputStream;
+import org.openstreetmap.josm.tools.Utils;
 
 
@@ -53,6 +56,6 @@
         try {
             url = getURL(
-                    b.min.east(), b.min.north(),
-                    b.max.east(), b.max.north(),
+                    b.minEast, b.minNorth,
+                    b.maxEast, b.maxNorth,
                     width(), height());
             request.finish(State.IMAGE, grab(url, attempt));
@@ -143,22 +146,22 @@
     @Override
     public boolean loadFromCache(WMSRequest request) {
-        URL url = null;
-        try{
-            url = getURL(
-                    b.min.east(), b.min.north(),
-                    b.max.east(), b.max.north(),
-                    width(), height());
-        } catch(Exception e) {
-            return false;
-        }
-        BufferedImage cached = cache.getImg(url.toString());
-        if((!request.isReal() && !layer.hasAutoDownload()) || cached != null){
-            if(cached == null){
-                request.finish(State.NOT_IN_CACHE, null);
-                return true;
-            }
+        BufferedImage cached = layer.cache.getExactMatch(Main.proj, pixelPerDegree, b.minEast, b.minNorth);
+
+        if (cached != null) {
             request.finish(State.IMAGE, cached);
             return true;
-        }
+        } else if (request.isAllowPartialCacheMatch()) {
+            BufferedImage partialMatch = layer.cache.getPartialMatch(Main.proj, pixelPerDegree, b.minEast, b.minNorth);
+            if (partialMatch != null) {
+                request.finish(State.PARTLY_IN_CACHE, partialMatch);
+                return true;
+            }
+        }
+
+        if((!request.isReal() && !layer.hasAutoDownload())){
+            request.finish(State.NOT_IN_CACHE, null);
+            return true;
+        }
+
         return false;
     }
@@ -180,9 +183,16 @@
             throw new IOException(readException(conn));
 
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
         InputStream is = new ProgressInputStream(conn, null);
-        BufferedImage img = layer.normalizeImage(ImageIO.read(is));
-        is.close();
-
-        cache.saveImg(url.toString(), img);
+        try {
+            Utils.copyStream(is, baos);
+        } finally {
+            is.close();
+        }
+
+        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
+        BufferedImage img = layer.normalizeImage(ImageIO.read(bais));
+        bais.reset();
+        layer.cache.saveToCache(layer.isOverlapEnabled()?img:null, bais, Main.proj, pixelPerDegree, b.minEast, b.minNorth);
         return img;
     }
Index: trunk/src/org/openstreetmap/josm/io/imagery/WMSRequest.java
===================================================================
--- trunk/src/org/openstreetmap/josm/io/imagery/WMSRequest.java	(revision 4064)
+++ trunk/src/org/openstreetmap/josm/io/imagery/WMSRequest.java	(revision 4065)
@@ -11,4 +11,5 @@
     private final double pixelPerDegree;
     private final boolean real; // Download even if autodownloading is disabled
+    private final boolean allowPartialCacheMatch;
     private int priority;
     // Result
@@ -16,9 +17,10 @@
     private BufferedImage image;
 
-    public WMSRequest(int xIndex, int yIndex, double pixelPerDegree, boolean real) {
+    public WMSRequest(int xIndex, int yIndex, double pixelPerDegree, boolean real, boolean allowPartialCacheMatch) {
         this.xIndex = xIndex;
         this.yIndex = yIndex;
         this.pixelPerDegree = pixelPerDegree;
         this.real = real;
+        this.allowPartialCacheMatch = allowPartialCacheMatch;
     }
 
@@ -68,4 +70,6 @@
         if (yIndex != other.yIndex)
             return false;
+        if (allowPartialCacheMatch != other.allowPartialCacheMatch)
+            return false;
         return true;
     }
@@ -101,3 +105,7 @@
         return real;
     }
+
+    public boolean isAllowPartialCacheMatch() {
+        return allowPartialCacheMatch;
+    }
 }
Index: trunk/src/org/openstreetmap/josm/tools/Utils.java
===================================================================
--- trunk/src/org/openstreetmap/josm/tools/Utils.java	(revision 4064)
+++ trunk/src/org/openstreetmap/josm/tools/Utils.java	(revision 4065)
@@ -3,4 +3,8 @@
 
 import java.awt.Color;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
 import java.util.Collection;
 
@@ -15,5 +19,5 @@
     }
 
-    public static <T> boolean exists(Iterable collection, Class<? extends T> klass) {
+    public static <T> boolean exists(Iterable<T> collection, Class<? extends T> klass) {
         for (Object item : collection) {
             if (klass.isInstance(item))
@@ -31,10 +35,9 @@
     }
 
-    public static <T> T find(Iterable collection, Class<? extends T> klass) {
+    @SuppressWarnings("unchecked")
+    public static <T> T find(Iterable<? super T> collection, Class<? extends T> klass) {
         for (Object item : collection) {
-            if (klass.isInstance(item)) {
-                @SuppressWarnings("unchecked") T res = (T) item;
-                return res;
-            }
+            if (klass.isInstance(item))
+                return (T) item;
         }
         return null;
@@ -60,7 +63,6 @@
             return b;
         } else {
-            if (a < c) {
+            if (a < c)
                 return a;
-            }
             return c;
         }
@@ -160,6 +162,35 @@
     }
 
+
+    public static int copyStream(InputStream source, OutputStream destination) throws IOException {
+        int count = 0;
+        byte[] b = new byte[512];
+        int read;
+        while ((read = source.read(b)) != -1) {
+            count += read;
+            destination.write(b, 0, read);
+        }
+        return count;
+    }
+
+
+
     public static Color complement(Color clr) {
         return new Color(255 - clr.getRed(), 255 - clr.getGreen(), 255 - clr.getBlue(), clr.getAlpha());
     }
+
+    public static boolean deleteDirectory(File path) {
+        if( path.exists() ) {
+            File[] files = path.listFiles();
+            for(int i=0; i<files.length; i++) {
+                if(files[i].isDirectory()) {
+                    deleteDirectory(files[i]);
+                }
+                else {
+                    files[i].delete();
+                }
+            }
+        }
+        return( path.delete() );
+    }
 }
