Index: trunk/src/org/openstreetmap/josm/data/imagery/GeorefImage.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/imagery/GeorefImage.java	(revision 4016)
+++ 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>
