Index: applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/OsmFileCacheTileLoader.java
===================================================================
--- applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/OsmFileCacheTileLoader.java	(revision 30850)
+++ applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/OsmFileCacheTileLoader.java	(revision 30854)
@@ -45,5 +45,5 @@
     private static final Logger log = FeatureAdapter.getLogger(OsmFileCacheTileLoader.class.getName());
 
-    private static final String TAGS_FILE_EXT = ".tags";
+    protected static final String TAGS_FILE_EXT = "tags";
 
     private static final Charset TAGS_CHARSET = Charset.forName("UTF-8");
@@ -392,12 +392,13 @@
 
         protected File getTagsFile() {
-            return new File(tileCacheDir + "/" + tile.getZoom() + "_" + tile.getXtile() + "_" + tile.getYtile()
+            return new File(tileCacheDir + "/" + tile.getZoom() + "_" + tile.getXtile() + "_" + tile.getYtile() + "."
                     + TAGS_FILE_EXT);
         }
 
         protected void saveTileToFile(byte[] rawData) {
+            File file = getTileFile();
+            file.getParentFile().mkdirs();
             try (
-                FileOutputStream f = new FileOutputStream(tileCacheDir + "/" + tile.getZoom() + "_" + tile.getXtile()
-                        + "_" + tile.getYtile() + "." + tile.getSource().getTileType())
+                FileOutputStream f = new FileOutputStream(file)
             ) {
                 f.write(rawData);
Index: applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/TMSFileCacheTileLoader.java
===================================================================
--- applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/TMSFileCacheTileLoader.java	(revision 30854)
+++ applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/TMSFileCacheTileLoader.java	(revision 30854)
@@ -0,0 +1,76 @@
+// License: GPL. For details, see Readme.txt file.
+package org.openstreetmap.gui.jmapviewer;
+
+import java.io.File;
+import java.io.IOException;
+import org.openstreetmap.gui.jmapviewer.interfaces.TileJob;
+import org.openstreetmap.gui.jmapviewer.interfaces.TileLoaderListener;
+import org.openstreetmap.gui.jmapviewer.interfaces.TileSource;
+
+/**
+ * Reworked version of the OsmFileCacheTileLoader.
+ *
+ * When class OsmFileCacheTileLoader is no longer needed, it can be integrated
+ * here and removed.
+ */
+public class TMSFileCacheTileLoader extends OsmFileCacheTileLoader {
+
+    public TMSFileCacheTileLoader(TileLoaderListener map, File cacheDir) throws IOException {
+        super(map, cacheDir);
+    }
+
+    @Override
+    public TileJob createTileLoaderJob(final Tile tile) {
+        return new TMSFileLoadJob(tile);
+    }
+
+    protected class TMSFileLoadJob extends FileLoadJob {
+
+        public TMSFileLoadJob(Tile tile) {
+            super(tile);
+        }
+
+        @Override
+        protected File getTileFile() {
+            return getDataFile(tile.getSource().getTileType());
+        }
+
+        @Override
+        protected File getTagsFile() {
+            return getDataFile(TAGS_FILE_EXT);
+        }
+
+        protected File getDataFile(String ext) {
+            int nDigits = (int) Math.ceil(Math.log10(1 << tile.getZoom()));
+            String x = String.format("%0" + nDigits + "d", tile.getXtile());
+            String y = String.format("%0" + nDigits + "d", tile.getYtile());
+            File path = new File(tileCacheDir, "z" + tile.getZoom());
+            for (int i=0; i<nDigits; i++) {
+                String component = "x" + x.substring(i, i+1) + "y" + y.substring(i, i+1);
+                if (i == nDigits -1 ) {
+                    component += "." + ext;
+                }
+                path = new File(path, component);
+            }
+            return path;
+        }
+    }
+
+    @Override
+    protected File getSourceCacheDir(TileSource source) {
+        File dir = sourceCacheDirMap.get(source);
+        if (dir == null) {
+            String id = source.getId();
+            if (id != null) {
+                dir = new File(cacheDirBase, id);
+            } else {
+                dir = new File(cacheDirBase, source.getName().replaceAll("[\\\\/:*?\"<>|]", "_"));
+            }
+            if (!dir.exists()) {
+                dir.mkdirs();
+            }
+        }
+        return dir;
+    }
+
+}
Index: applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/interfaces/TileSource.java
===================================================================
--- applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/interfaces/TileSource.java	(revision 30850)
+++ applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/interfaces/TileSource.java	(revision 30854)
@@ -63,10 +63,19 @@
 
     /**
-     * A tile layer name has to be unique and has to consist only of characters
-     * valid for filenames.
+     * A tile layer name as displayed to the user.
      *
      * @return Name of the tile layer
      */
     String getName();
+
+    /**
+     * A unique id for this tile source.
+     * 
+     * Unlike the name it has to be unique and has to consist only of characters
+     * valid for filenames.
+     * 
+     * @return the id
+     */
+    String getId();
 
     /**
Index: applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/tilesources/AbstractMapQuestTileSource.java
===================================================================
--- applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/tilesources/AbstractMapQuestTileSource.java	(revision 30850)
+++ applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/tilesources/AbstractMapQuestTileSource.java	(revision 30854)
@@ -31,6 +31,6 @@
     private int SERVER_NUM = 1;
 
-    public AbstractMapQuestTileSource(String name, String base_url) {
-        super(name, base_url);
+    public AbstractMapQuestTileSource(String name, String base_url, String id) {
+        super(name, base_url, id);
     }
 
Index: applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/tilesources/AbstractOsmTileSource.java
===================================================================
--- applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/tilesources/AbstractOsmTileSource.java	(revision 30850)
+++ applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/tilesources/AbstractOsmTileSource.java	(revision 30854)
@@ -20,7 +20,9 @@
      * @param name Source name as displayed in GUI
      * @param base_url Source URL
+     * @param id unique id for the tile source; contains only characters that
+     * are safe for file names; can be null
      */
-    public AbstractOsmTileSource(String name, String base_url) {
-        super(name, base_url);
+    public AbstractOsmTileSource(String name, String base_url, String id) {
+        super(name, base_url, id);
     }
 
Index: applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/tilesources/AbstractTMSTileSource.java
===================================================================
--- applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/tilesources/AbstractTMSTileSource.java	(revision 30850)
+++ applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/tilesources/AbstractTMSTileSource.java	(revision 30854)
@@ -10,6 +10,7 @@
     protected String name;
     protected String baseUrl;
+    protected String id;
 
-    public AbstractTMSTileSource(String name, String base_url) {
+    public AbstractTMSTileSource(String name, String base_url, String id) {
         this.name = name;
         this.baseUrl = base_url;
@@ -17,4 +18,5 @@
             baseUrl = baseUrl.substring(0,baseUrl.length()-1);
         }
+        this.id = id;
     }
 
@@ -22,4 +24,9 @@
     public String getName() {
         return name;
+    }
+
+    @Override
+    public String getId() {
+        return id;
     }
 
Index: applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/tilesources/BingAerialTileSource.java
===================================================================
--- applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/tilesources/BingAerialTileSource.java	(revision 30850)
+++ applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/tilesources/BingAerialTileSource.java	(revision 30854)
@@ -52,6 +52,6 @@
      * Constructs a new {@code BingAerialTileSource}.
      */
-    public BingAerialTileSource() {
-        super("Bing Aerial Maps", "http://example.com/");
+    public BingAerialTileSource(String id) {
+        super("Bing Aerial Maps", "http://example.com/", id);
     }
 
Index: applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/tilesources/MapQuestOpenAerialTileSource.java
===================================================================
--- applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/tilesources/MapQuestOpenAerialTileSource.java	(revision 30850)
+++ applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/tilesources/MapQuestOpenAerialTileSource.java	(revision 30854)
@@ -9,5 +9,5 @@
 
     public MapQuestOpenAerialTileSource() {
-        super("MapQuest Open Aerial", PATTERN);
+        super("MapQuest Open Aerial", PATTERN, "mapquest-oa");
     }
 
Index: applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/tilesources/MapQuestOsmTileSource.java
===================================================================
--- applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/tilesources/MapQuestOsmTileSource.java	(revision 30850)
+++ applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/tilesources/MapQuestOsmTileSource.java	(revision 30854)
@@ -9,5 +9,5 @@
 
     public MapQuestOsmTileSource() {
-        super("MapQuest-OSM", PATTERN);
+        super("MapQuest-OSM", PATTERN, "mapquest-osm");
     }
     
Index: applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/tilesources/OsmTileSource.java
===================================================================
--- applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/tilesources/OsmTileSource.java	(revision 30850)
+++ applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/tilesources/OsmTileSource.java	(revision 30854)
@@ -22,5 +22,5 @@
          */
         public Mapnik() {
-            super("Mapnik", PATTERN);
+            super("Mapnik", PATTERN, "MAPNIK");
         }
 
@@ -57,5 +57,5 @@
          */
         public CycleMap() {
-            super("Cyclemap", PATTERN);
+            super("Cyclemap", PATTERN, "opencylemap");
         }
 
Index: applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/tilesources/ScanexTileSource.java
===================================================================
--- applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/tilesources/ScanexTileSource.java	(revision 30850)
+++ applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/tilesources/ScanexTileSource.java	(revision 30854)
@@ -46,6 +46,6 @@
     private ScanexLayer Layer = ScanexLayer.IRS;
 
-    public ScanexTileSource(String name, String url, int maxZoom) {
-        super(name, url, maxZoom);
+    public ScanexTileSource(String name, String url, String id, int maxZoom) {
+        super(name, url, id, maxZoom);
 
         for (ScanexLayer layer : ScanexLayer.values()) {
Index: applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/tilesources/TMSTileSource.java
===================================================================
--- applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/tilesources/TMSTileSource.java	(revision 30850)
+++ applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/tilesources/TMSTileSource.java	(revision 30854)
@@ -7,11 +7,11 @@
     protected int minZoom = 0;
 
-    public TMSTileSource(String name, String url, int maxZoom) {
-        super(name, url);
+    public TMSTileSource(String name, String url, String id, int maxZoom) {
+        super(name, url, id);
         this.maxZoom = maxZoom;
     }
 
-    public TMSTileSource(String name, String url, int minZoom, int maxZoom) {
-        super(name, url);
+    public TMSTileSource(String name, String url, String id, int minZoom, int maxZoom) {
+        super(name, url, id);
         this.minZoom = minZoom;
         this.maxZoom = maxZoom;
Index: applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/tilesources/TemplatedTMSTileSource.java
===================================================================
--- applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/tilesources/TemplatedTMSTileSource.java	(revision 30850)
+++ applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/tilesources/TemplatedTMSTileSource.java	(revision 30854)
@@ -27,11 +27,11 @@
     };
 
-    public TemplatedTMSTileSource(String name, String url, int maxZoom) {
-        super(name, url, maxZoom);
+    public TemplatedTMSTileSource(String name, String url, String id, int maxZoom) {
+        super(name, url, id, maxZoom);
         handleTemplate();
     }
 
-    public TemplatedTMSTileSource(String name, String url, int minZoom, int maxZoom) {
-        super(name, url, minZoom, maxZoom);
+    public TemplatedTMSTileSource(String name, String url, String id, int minZoom, int maxZoom) {
+        super(name, url, id, minZoom, maxZoom);
         handleTemplate();
     }
