Index: applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/Demo.java
===================================================================
--- applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/Demo.java	(revision 9755)
+++ applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/Demo.java	(revision 9780)
@@ -6,10 +6,15 @@
 import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
 
 import javax.swing.JButton;
 import javax.swing.JCheckBox;
+import javax.swing.JComboBox;
 import javax.swing.JFrame;
 import javax.swing.JLabel;
 import javax.swing.JPanel;
+
+import org.openstreetmap.gui.jmapviewer.interfaces.TileSource;
 
 /**
@@ -29,6 +34,5 @@
 		final JMapViewer map = new JMapViewer();
 		// final JMapViewer map = new JMapViewer(new MemoryTileCache(),4);
-		// map.setTileLoader(new OsmFileCacheTileLoader(map,
-		// OsmTileLoader.MAP_MAPNIK));
+		map.setTileLoader(new OsmFileCacheTileLoader(map));
 		// new DefaultMapController(map);
 		setLayout(new BorderLayout());
@@ -36,9 +40,11 @@
 		setExtendedState(JFrame.MAXIMIZED_BOTH);
 		JPanel panel = new JPanel();
+		JPanel helpPanel = new JPanel();
 		add(panel, BorderLayout.NORTH);
-		JLabel label =
+		add(helpPanel, BorderLayout.SOUTH);
+		JLabel helpLabel =
 				new JLabel("Use right mouse button to move,\n "
 						+ "left double click or mouse wheel to zoom.");
-		panel.add(label);
+		helpPanel.add(helpLabel);
 		JButton button = new JButton("setDisplayToFitMapMarkers");
 		button.addActionListener(new ActionListener() {
@@ -48,5 +54,13 @@
 			}
 		});
-		panel.add(button);
+		JComboBox tileSourceSelector =
+				new JComboBox(new Object[] { new OsmTileSource.Mapnik(),
+						new OsmTileSource.TilesAtHome(), new OsmTileSource.CycleMap() });
+		tileSourceSelector.addItemListener(new ItemListener() {
+			public void itemStateChanged(ItemEvent e) {
+				map.setTileSource((TileSource) e.getItem());
+			}
+		});
+		panel.add(tileSourceSelector);
 		final JCheckBox showMapMarker = new JCheckBox("Map markers visible");
 		showMapMarker.setSelected(map.getMapMarkersVisible());
@@ -76,4 +90,5 @@
 		});
 		panel.add(showZoomControls);
+		panel.add(button);
 		add(map, BorderLayout.CENTER);
 
Index: applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/JMapController.java
===================================================================
--- applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/JMapController.java	(revision 9755)
+++ applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/JMapController.java	(revision 9780)
@@ -21,5 +21,5 @@
 public abstract class JMapController {
 
-	JMapViewer map;
+	protected JMapViewer map;
 
 	public JMapController(JMapViewer map) {
Index: applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/JMapViewer.java
===================================================================
--- applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/JMapViewer.java	(revision 9755)
+++ applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/JMapViewer.java	(revision 9780)
@@ -26,4 +26,5 @@
 import org.openstreetmap.gui.jmapviewer.interfaces.MapMarker;
 import org.openstreetmap.gui.jmapviewer.interfaces.TileCache;
+import org.openstreetmap.gui.jmapviewer.interfaces.TileSource;
 import org.openstreetmap.gui.jmapviewer.interfaces.TileLoader;
 
@@ -51,4 +52,6 @@
 	protected TileLoader tileLoader;
 	protected TileCache tileCache;
+	protected TileSource tileSource;
+
 	protected List<MapMarker> mapMarkerList;
 	protected boolean mapMarkersVisible;
@@ -91,4 +94,5 @@
 	public JMapViewer(TileCache tileCache, int downloadThreadCount) {
 		super();
+		tileSource = new OsmTileSource.Mapnik();
 		tileLoader = new OsmTileLoader(this);
 		this.tileCache = tileCache;
@@ -111,5 +115,5 @@
 
 	protected void initializeZoomSlider() {
-		zoomSlider = new JSlider(MIN_ZOOM, MAX_ZOOM);
+		zoomSlider = new JSlider(MIN_ZOOM, tileSource.getMaxZoom());
 		zoomSlider.setOrientation(JSlider.VERTICAL);
 		zoomSlider.setBounds(10, 10, 30, 150);
@@ -184,5 +188,6 @@
 	 *            longitude of the specified coordinate
 	 * @param zoom
-	 *            {@link #MIN_ZOOM} <= zoom level <= {@link #MAX_ZOOM}
+	 *            {@link #MIN_ZOOM} <= zoom level <=
+	 *            {@link TileSource#getMaxZoom()}
 	 */
 	public void setDisplayPositionByLatLon(Point mapPoint, double lat, double lon, int zoom) {
@@ -197,5 +202,5 @@
 
 	public void setDisplayPosition(Point mapPoint, int x, int y, int zoom) {
-		if (zoom > MAX_ZOOM || zoom < MIN_ZOOM)
+		if (zoom > tileSource.getMaxZoom() || zoom < MIN_ZOOM)
 			return;
 
@@ -329,15 +334,15 @@
 		int y_max = getHeight();
 
+		// paint the tiles in a spiral, starting from center of the map
 		boolean painted = true;
 		int x = 0;
 		while (painted) {
 			painted = false;
-			for (int y = 0; y < 4; y++) {
-				if (y % 2 == 0)
+			for (int i = 0; i < 4; i++) {
+				if (i % 2 == 0)
 					x++;
-				for (int z = 0; z < x; z++) {
-					if (x_min <= posx && posx <= x_max && y_min <= posy && posy <= y_max) { // tile
-						// is
-						// visible
+				for (int j = 0; j < x; j++) {
+					if (x_min <= posx && posx <= x_max && y_min <= posy && posy <= y_max) {
+						// tile is visible
 						Tile tile = getTile(tilex, tiley, zoom);
 						if (tile != null) {
@@ -418,5 +423,5 @@
 
 	public void setZoom(int zoom, Point mapPoint) {
-		if (zoom > MAX_ZOOM || zoom == this.zoom)
+		if (zoom > tileSource.getMaxZoom() || zoom == this.zoom)
 			return;
 		Point2D.Double zoomPos = getPosition(mapPoint);
@@ -444,12 +449,12 @@
 		if (tilex < 0 || tilex >= max || tiley < 0 || tiley >= max)
 			return null;
-		Tile tile = tileCache.getTile(tilex, tiley, zoom);
+		Tile tile = tileCache.getTile(tileSource, tilex, tiley, zoom);
 		if (tile == null) {
-			tile = new Tile(tilex, tiley, zoom, loadingImage);
+			tile = new Tile(tileSource, tilex, tiley, zoom, loadingImage);
 			tileCache.addTile(tile);
 			tile.loadPlaceholderFromCache(tileCache);
 		}
 		if (!tile.isLoaded()) {
-			jobDispatcher.addJob(tileLoader.createTileLoaderJob(tilex, tiley, zoom));
+			jobDispatcher.addJob(tileLoader.createTileLoaderJob(tileSource, tilex, tiley, zoom));
 		}
 		return tile;
@@ -527,3 +532,16 @@
 	}
 
+	public TileSource getTileLayerSource() {
+		return tileSource;
+	}
+
+	public void setTileSource(TileSource tileSource) {
+		this.tileSource = tileSource;
+		zoomSlider.setMaximum(tileSource.getMaxZoom());
+		jobDispatcher.cancelOutstandingJobs();
+		if (zoom > tileSource.getMaxZoom())
+			setZoom(tileSource.getMaxZoom());
+		repaint();
+	}
+
 }
Index: applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/MemoryTileCache.java
===================================================================
--- applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/MemoryTileCache.java	(revision 9755)
+++ applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/MemoryTileCache.java	(revision 9780)
@@ -7,4 +7,5 @@
 
 import org.openstreetmap.gui.jmapviewer.interfaces.TileCache;
+import org.openstreetmap.gui.jmapviewer.interfaces.TileSource;
 
 /**
@@ -44,6 +45,6 @@
 	}
 
-	public Tile getTile(int x, int y, int z) {
-		CacheEntry entry = hashtable.get(Tile.getTileKey(x, y, z));
+	public Tile getTile(TileSource source, int x, int y, int z) {
+		CacheEntry entry = hashtable.get(Tile.getTileKey(source, x, y, z));
 		if (entry == null)
 			return null;
Index: applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/OsmFileCacheTileLoader.java
===================================================================
--- applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/OsmFileCacheTileLoader.java	(revision 9755)
+++ applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/OsmFileCacheTileLoader.java	(revision 9780)
@@ -16,4 +16,5 @@
 import org.openstreetmap.gui.jmapviewer.interfaces.Job;
 import org.openstreetmap.gui.jmapviewer.interfaces.TileCache;
+import org.openstreetmap.gui.jmapviewer.interfaces.TileSource;
 import org.openstreetmap.gui.jmapviewer.interfaces.TileLoader;
 
@@ -32,10 +33,10 @@
 	public static final long FILE_AGE_ONE_WEEK = FILE_AGE_ONE_DAY * 7;
 
-	protected String tileCacheDir;
+	protected String cacheDirBase;
 
 	protected long maxFileAge = FILE_AGE_ONE_WEEK;
 
-	public OsmFileCacheTileLoader(JMapViewer map, String baseUrl) {
-		super(map, baseUrl);
+	public OsmFileCacheTileLoader(JMapViewer map) {
+		super(map);
 		String tempDir = System.getProperty("java.io.tmpdir");
 		try {
@@ -43,20 +44,16 @@
 				throw new IOException();
 			File cacheDir = new File(tempDir, "JMapViewerTiles");
-			cacheDir = new File(cacheDir, Integer.toString(baseUrl.hashCode()));
 			// System.out.println(cacheDir);
 			if (!cacheDir.exists() && !cacheDir.mkdirs())
 				throw new IOException();
-			tileCacheDir = cacheDir.getAbsolutePath();
+			cacheDirBase = cacheDir.getAbsolutePath();
 		} catch (Exception e) {
-			tileCacheDir = "tiles";
-		}
-	}
-
-	public OsmFileCacheTileLoader(JMapViewer map) {
-		this(map, MAP_MAPNIK);
-	}
-
-	public Job createTileLoaderJob(final int tilex, final int tiley, final int zoom) {
-		return new FileLoadJob(tilex, tiley, zoom);
+			cacheDirBase = "tiles";
+		}
+	}
+
+	public Job createTileLoaderJob(final TileSource source, final int tilex, final int tiley,
+			final int zoom) {
+		return new FileLoadJob(source, tilex, tiley, zoom);
 	}
 
@@ -65,7 +62,10 @@
 
 		int tilex, tiley, zoom;
-
-		public FileLoadJob(int tilex, int tiley, int zoom) {
+		TileSource source;
+		File tileCacheDir;
+
+		public FileLoadJob(TileSource source, int tilex, int tiley, int zoom) {
 			super();
+			this.source = source;
 			this.tilex = tilex;
 			this.tiley = tiley;
@@ -77,9 +77,12 @@
 			Tile tile;
 			synchronized (cache) {
-				tile = cache.getTile(tilex, tiley, zoom);
+				tile = cache.getTile(source, tilex, tiley, zoom);
 				if (tile == null || tile.isLoaded() || tile.loading)
 					return;
 				tile.loading = true;
 			}
+			tileCacheDir = new File(cacheDirBase, source.getName());
+			if (!tileCacheDir.exists())
+				tileCacheDir.mkdirs();
 			try {
 				long fileAge = 0;
@@ -135,6 +138,6 @@
 			} catch (Exception e) {
 				if (input == null /* || !input.isStopped() */)
-					System.err.println("failed loading " + zoom + "/" + tilex
-							+ "/" + tiley + " " + e.getMessage());
+					System.err.println("failed loading " + zoom + "/" + tilex + "/" + tiley + " "
+							+ e.getMessage());
 			} finally {
 				tile.loading = false;
@@ -142,9 +145,7 @@
 		}
 
-		protected byte[] loadTileInBuffer(URLConnection urlConn)
-				throws IOException {
+		protected byte[] loadTileInBuffer(URLConnection urlConn) throws IOException {
 			input = urlConn.getInputStream();
-			ByteArrayOutputStream bout = new ByteArrayOutputStream(input
-					.available());
+			ByteArrayOutputStream bout = new ByteArrayOutputStream(input.available());
 			byte[] buffer = new byte[2048];
 			boolean finished = false;
@@ -176,10 +177,8 @@
 		 * @throws IOException
 		 */
-		protected boolean isOsmTileNewer(Tile tile, long fileAge)
-				throws IOException {
+		protected boolean isOsmTileNewer(Tile tile, long fileAge) throws IOException {
 			URL url;
-			url = new URL(baseUrl + "/" + tile.getKey() + ".png");
-			HttpURLConnection urlConn = (HttpURLConnection) url
-					.openConnection();
+			url = new URL(tile.getUrl());
+			HttpURLConnection urlConn = (HttpURLConnection) url.openConnection();
 			urlConn.setRequestMethod("HEAD");
 			urlConn.setReadTimeout(30000); // 30 seconds read
@@ -194,19 +193,18 @@
 
 		protected File getTileFile(Tile tile) throws IOException {
-			return new File(tileCacheDir + "/" + tile.getZoom() + "_"
-					+ tile.getXtile() + "_" + tile.getYtile() + FILE_EXT);
+			return new File(tileCacheDir + "/" + tile.getZoom() + "_" + tile.getXtile() + "_"
+					+ tile.getYtile() + FILE_EXT);
 		}
 
 		protected void saveTileToFile(Tile tile, byte[] rawData) {
 			try {
-				FileOutputStream f = new FileOutputStream(tileCacheDir + "/"
-						+ tile.getZoom() + "_" + tile.getXtile() + "_"
-						+ tile.getYtile() + FILE_EXT);
+				FileOutputStream f =
+						new FileOutputStream(tileCacheDir + "/" + tile.getZoom() + "_"
+								+ tile.getXtile() + "_" + tile.getYtile() + FILE_EXT);
 				f.write(rawData);
 				f.close();
 				// System.out.println("Saved tile to file: " + tile);
 			} catch (Exception e) {
-				System.err.println("Failed to save tile content: "
-						+ e.getLocalizedMessage());
+				System.err.println("Failed to save tile content: " + e.getLocalizedMessage());
 			}
 		}
@@ -232,6 +230,6 @@
 	}
 
-	public String getTileCacheDir() {
-		return tileCacheDir;
+	public String getCacheDirBase() {
+		return cacheDirBase;
 	}
 
@@ -239,5 +237,5 @@
 		File dir = new File(tileCacheDir);
 		dir.mkdirs();
-		this.tileCacheDir = dir.getAbsolutePath();
+		this.cacheDirBase = dir.getAbsolutePath();
 	}
 
Index: applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/OsmTileLoader.java
===================================================================
--- applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/OsmTileLoader.java	(revision 9755)
+++ applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/OsmTileLoader.java	(revision 9780)
@@ -10,4 +10,5 @@
 import org.openstreetmap.gui.jmapviewer.interfaces.Job;
 import org.openstreetmap.gui.jmapviewer.interfaces.TileCache;
+import org.openstreetmap.gui.jmapviewer.interfaces.TileSource;
 import org.openstreetmap.gui.jmapviewer.interfaces.TileLoader;
 
@@ -19,21 +20,11 @@
 public class OsmTileLoader implements TileLoader {
 
-	public static final String MAP_MAPNIK = "http://tile.openstreetmap.org";
-	public static final String MAP_OSMA = "http://tah.openstreetmap.org/Tiles/tile";
-
-	protected String baseUrl;
-
 	protected JMapViewer map;
 
 	public OsmTileLoader(JMapViewer map) {
-		this(map, MAP_MAPNIK);
+		this.map = map;
 	}
 
-	public OsmTileLoader(JMapViewer map, String baseUrl) {
-		this.map = map;
-		this.baseUrl = baseUrl;
-	}
-
-	public Job createTileLoaderJob(final int tilex, final int tiley,
+	public Job createTileLoaderJob(final TileSource source, final int tilex, final int tiley,
 			final int zoom) {
 		return new Job() {
@@ -45,5 +36,5 @@
 				Tile tile;
 				synchronized (cache) {
-					tile = cache.getTile(tilex, tiley, zoom);
+					tile = cache.getTile(source, tilex, tiley, zoom);
 					if (tile == null || tile.isLoaded() || tile.loading)
 						return;
@@ -83,5 +74,5 @@
 	protected HttpURLConnection loadTileFromOsm(Tile tile) throws IOException {
 		URL url;
-		url = new URL(baseUrl + "/" + tile.getKey() + ".png");
+		url = new URL(tile.getUrl());
 		HttpURLConnection urlConn = (HttpURLConnection) url.openConnection();
 		urlConn.setReadTimeout(30000); // 30 seconds read
Index: applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/OsmTileSource.java
===================================================================
--- applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/OsmTileSource.java	(revision 9780)
+++ applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/OsmTileSource.java	(revision 9780)
@@ -0,0 +1,68 @@
+package org.openstreetmap.gui.jmapviewer;
+
+import org.openstreetmap.gui.jmapviewer.interfaces.TileSource;
+
+public class OsmTileSource {
+
+	public static final String MAP_MAPNIK = "http://tile.openstreetmap.org";
+	public static final String MAP_OSMA = "http://tah.openstreetmap.org/Tiles/tile";
+	public static final String MAP_CYCLE = "http://www.thunderflames.org/tiles/cycle";
+
+	protected static abstract class AbstractOsmTileSource implements TileSource {
+
+		public int getMaxZoom() {
+			return 18;
+		}
+
+		public String getTileUrl(int zoom, int tilex, int tiley) {
+			return "/" + zoom + "/" + tilex + "/" + tiley + ".png";
+		}
+
+		@Override
+		public String toString() {
+			return getName();
+		}
+	}
+
+	public static class Mapnik extends AbstractOsmTileSource {
+
+		public String getName() {
+			return "Mapnik";
+		}
+
+		@Override
+		public String getTileUrl(int zoom, int tilex, int tiley) {
+			return MAP_MAPNIK + super.getTileUrl(zoom, tilex, tiley);
+		}
+
+	}
+
+	public static class CycleMap extends AbstractOsmTileSource {
+
+		public String getName() {
+			return "OSM Cycle Map";
+		}
+
+		@Override
+		public String getTileUrl(int zoom, int tilex, int tiley) {
+			return MAP_CYCLE + super.getTileUrl(zoom, tilex, tiley);
+		}
+	}
+
+	public static class TilesAtHome extends AbstractOsmTileSource {
+
+		public int getMaxZoom() {
+			return 17;
+		}
+
+		public String getName() {
+			return "TilesAtHome";
+		}
+
+		@Override
+		public String getTileUrl(int zoom, int tilex, int tiley) {
+			return MAP_OSMA + super.getTileUrl(zoom, tilex, tiley);
+		}
+
+	}
+}
Index: applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/StoppableInputStream.java
===================================================================
--- applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/StoppableInputStream.java	(revision 9780)
+++ applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/StoppableInputStream.java	(revision 9780)
@@ -0,0 +1,53 @@
+package org.openstreetmap.gui.jmapviewer;
+
+import java.io.EOFException;
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * An {@link FilterInputStream} implementation that offers a {@link #stop()}
+ * method. The difference between {@link #stop()} and {@link #close()} is that
+ * {@link #stop()} guarantees
+ * 
+ * @author Jan Peter Stotz
+ */
+public class StoppableInputStream extends FilterInputStream {
+
+	boolean stopped;
+
+	public StoppableInputStream(InputStream in) {
+		super(in);
+		stopped = false;
+	}
+
+	public void stop() {
+		stopped = true;
+	}
+
+	@Override
+	public int read() throws IOException {
+		if (stopped)
+			return -1;
+		return super.read();
+	}
+
+	@Override
+	public int read(byte[] b, int off, int len) throws IOException {
+		if (stopped)
+			throw new EOFException();
+		return super.read(b, off, len);
+	}
+
+	@Override
+	public int read(byte[] b) throws IOException {
+		if (stopped)
+			throw new EOFException();
+		return super.read(b);
+	}
+
+	public boolean isStopped() {
+		return stopped;
+	}
+
+}
Index: applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/Tile.java
===================================================================
--- applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/Tile.java	(revision 9755)
+++ applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/Tile.java	(revision 9780)
@@ -13,4 +13,5 @@
 
 import org.openstreetmap.gui.jmapviewer.interfaces.TileCache;
+import org.openstreetmap.gui.jmapviewer.interfaces.TileSource;
 
 /**
@@ -22,4 +23,5 @@
 public class Tile {
 
+	protected TileSource source;
 	protected int xtile;
 	protected int ytile;
@@ -35,19 +37,21 @@
 	 * Creates a tile with empty image.
 	 * 
+	 * @param source
 	 * @param xtile
 	 * @param ytile
 	 * @param zoom
 	 */
-	public Tile(int xtile, int ytile, int zoom) {
+	public Tile(TileSource source, int xtile, int ytile, int zoom) {
 		super();
+		this.source = source;
 		this.xtile = xtile;
 		this.ytile = ytile;
 		this.zoom = zoom;
 		this.image = null;
-		this.key = getTileKey(xtile, ytile, zoom);
-	}
-
-	public Tile(int xtile, int ytile, int zoom, BufferedImage image) {
-		this(xtile, ytile, zoom);
+		this.key = getTileKey(source, xtile, ytile, zoom);
+	}
+
+	public Tile(TileSource source, int xtile, int ytile, int zoom, BufferedImage image) {
+		this(source, xtile, ytile, zoom);
 		this.image = image;
 	}
@@ -59,6 +63,5 @@
 	 */
 	public void loadPlaceholderFromCache(TileCache cache) {
-		BufferedImage tmpImage = new BufferedImage(WIDTH, HEIGHT,
-				BufferedImage.TYPE_INT_RGB);
+		BufferedImage tmpImage = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);
 		Graphics2D g = (Graphics2D) tmpImage.getGraphics();
 		// g.drawImage(image, 0, 0, null);
@@ -76,6 +79,6 @@
 				for (int x = 0; x < factor; x++) {
 					for (int y = 0; y < factor; y++) {
-						Tile tile = cache.getTile(xtile_high + x, ytile_high
-								+ y, zoom_high);
+						Tile tile =
+								cache.getTile(source, xtile_high + x, ytile_high + y, zoom_high);
 						if (tile != null && tile.isLoaded()) {
 							paintedTileCount++;
@@ -101,5 +104,5 @@
 				at.setTransform(scale, 0, 0, scale, -translate_x, -translate_y);
 				g.setTransform(at);
-				Tile tile = cache.getTile(xtile_low, ytile_low, zoom_low);
+				Tile tile = cache.getTile(source, xtile_low, ytile_low, zoom_low);
 				if (tile != null && tile.isLoaded()) {
 					tile.paint(g, 0, 0);
@@ -111,4 +114,8 @@
 	}
 
+	public TileSource getSource() {
+		return source;
+	}
+
 	/**
 	 * @return tile number on the x axis of this tile
@@ -157,4 +164,8 @@
 	public void setLoaded(boolean loaded) {
 		this.loaded = loaded;
+	}
+
+	public String getUrl() {
+		return source.getTileUrl(zoom, xtile, ytile);
 	}
 
@@ -177,5 +188,5 @@
 	@Override
 	public String toString() {
-		return "Tile " + getTileKey(xtile, ytile, zoom);
+		return "Tile " + key;
 	}
 
@@ -185,10 +196,9 @@
 			return false;
 		Tile tile = (Tile) obj;
-		return (xtile == tile.xtile) && (ytile == tile.ytile)
-				&& (zoom == tile.zoom);
-	}
-
-	public static String getTileKey(int xtile, int ytile, int zoom) {
-		return zoom + "/" + xtile + "/" + ytile;
+		return (xtile == tile.xtile) && (ytile == tile.ytile) && (zoom == tile.zoom);
+	}
+
+	public static String getTileKey(TileSource source, int xtile, int ytile, int zoom) {
+		return zoom + "/" + xtile + "/" + ytile + "@" + source.getName();
 	}
 
Index: applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/interfaces/TileCache.java
===================================================================
--- applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/interfaces/TileCache.java	(revision 9755)
+++ applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/interfaces/TileCache.java	(revision 9780)
@@ -18,4 +18,5 @@
 	 * will be returned.
 	 * 
+	 * @param source
 	 * @param x
 	 *            tile number on the x axis of the tile to be retrieved
@@ -27,5 +28,5 @@
 	 *         present in the cache
 	 */
-	public Tile getTile(int x, int y, int z);
+	public Tile getTile(TileSource source, int x, int y, int z);
 
 	/**
Index: applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/interfaces/TileLayerSource.java
===================================================================
--- applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/interfaces/TileLayerSource.java	(revision 9755)
+++ 	(revision )
@@ -1,30 +1,0 @@
-package org.openstreetmap.gui.jmapviewer.interfaces;
-
-//License: GPL. Copyright 2008 by Jan Peter Stotz
-
-/**
- * 
- * @author Jan Peter Stotz
- */
-public interface TileLayerSource {
-
-	/**
-	 * Specifies the maximum zoom value. The number of zoom levels is [0..{@link #getMaxZoom()}] 
-	 *  
-	 * @return maximum zoom value
-	 */
-	public int getMaxZoom();
-	
-	/**
-	 * @return Name of the tile layer 
-	 */
-	public String getName();
-	
-	/**
-	 * @param zoom
-	 * @param tilex
-	 * @param tiley
-	 * @return fully qualified url for downloading the specified tile image
-	 */
-	public String getTileUrl(int zoom, int tilex, int tiley);
-}
Index: applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/interfaces/TileLoader.java
===================================================================
--- applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/interfaces/TileLoader.java	(revision 9755)
+++ applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/interfaces/TileLoader.java	(revision 9780)
@@ -16,4 +16,5 @@
 	 * load action.
 	 * 
+	 * @param tileLayerSource
 	 * @param tilex
 	 * @param tiley
@@ -22,4 +23,4 @@
 	 *          action.
 	 */
-	public Job createTileLoaderJob(int tilex, int tiley, int zoom);
+	public Job createTileLoaderJob(TileSource tileLayerSource, int tilex, int tiley, int zoom);
 }
Index: applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/interfaces/TileSource.java
===================================================================
--- applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/interfaces/TileSource.java	(revision 9780)
+++ applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/interfaces/TileSource.java	(revision 9780)
@@ -0,0 +1,61 @@
+package org.openstreetmap.gui.jmapviewer.interfaces;
+
+import org.openstreetmap.gui.jmapviewer.JMapViewer;
+
+//License: GPL. Copyright 2008 by Jan Peter Stotz
+
+/**
+ * 
+ * @author Jan Peter Stotz
+ */
+public interface TileSource {
+
+	/**
+	 * Specifies the different mechanisms for detecting updated tiles
+	 * respectively only download newer tiles than those stored locally.
+	 * 
+	 * <ul>
+	 * <li>{@link #IfNoneMatch} Server provides ETag header entry for all tiles
+	 * and <b>supports</b> conditional download via <code>If-None-Match</code>
+	 * header entry.</li>
+	 * <li>{@link #ETag} Server provides ETag header entry for all tiles but
+	 * <b>does not support</b> conditional download via
+	 * <code>If-None-Match</code> header entry.</li>
+	 * <li>{@link #IfModifiedSince} Server provides Last-Modified header entry
+	 * for all tiles and <b>supports</b> conditional download via
+	 * <code>If-Modified-Since</code> header entry.</li>
+	 * <li>{@link #LastModified} Server provides Last-Modified header entry for
+	 * all tiles but <b>does not support</b> conditional download via
+	 * <code>If-Modified-Since</code> header entry.</li>
+	 * </ul>
+	 * 
+	 */
+	public enum TileUpdateDetection {
+		IfNoneMatch, ETag, IfModifiedSince, LastModified
+	};
+
+	/**
+	 * Specifies the maximum zoom value. The number of zoom levels is [0..
+	 * {@link #getMaxZoom()}].
+	 * 
+	 * @return maximum zoom value that has to be smaller or equal to
+	 *         {@link JMapViewer#MAX_ZOOM}
+	 */
+	public int getMaxZoom();
+
+	/**
+	 * A tile layer name has to be unique and has to consist only of characters
+	 * valid for filenames.
+	 * 
+	 * @return Name of the tile layer
+	 */
+	public String getName();
+
+	/**
+	 * @param zoom
+	 * @param tilex
+	 * @param tiley
+	 * @return fully qualified url for downloading the specified tile image
+	 */
+	public String getTileUrl(int zoom, int tilex, int tiley);
+}
