Index: applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/JMapViewer.java
===================================================================
--- applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/JMapViewer.java	(revision 9263)
+++ applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/JMapViewer.java	(revision 9288)
@@ -13,4 +13,7 @@
 import java.awt.image.BufferedImage;
 import java.io.IOException;
+import java.io.InputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
 import java.util.LinkedList;
 import java.util.List;
@@ -37,4 +40,10 @@
 
 	private static final long serialVersionUID = 1L;
+
+	/**
+	 * Vectors for clock-wise tile painting
+	 */
+	protected static final Point[] move =
+			{ new Point(1, 0), new Point(0, 1), new Point(-1, 0), new Point(0, -1) };
 
 	public static final int MAX_ZOOM = 18;
@@ -188,8 +197,4 @@
 			return;
 
-		// Substract the calculated values so that we get the x/y index of the
-		// upper left tile again including fraction where in the tile is our
-		// map origin point (0,0)
-
 		// Get the plain tile number
 		Point p = new Point();
@@ -279,39 +284,67 @@
 		super.paintComponent(g);
 
-		// Optimization for loading the centered tile in a lower zoom first
-		if (zoom > MIN_ZOOM) {
-			int center_tx = center.x / Tile.WIDTH;
-			int center_ty = center.y / Tile.HEIGHT;
-			Tile centerTile = tileCache.getTile(center_tx, center_ty, zoom);
-			if (centerTile == null || !centerTile.isLoaded()) {
-				// tile in the center of the screen is not loaded, for faster
-				// displaying anything in the center we first load a tile of a
-				// lower zoom level
-				getTile(center_tx / 2, center_ty / 2, zoom - 1);
+		int iMove = 0;
+
+		int tilex = center.x / Tile.WIDTH;
+		int tiley = center.y / Tile.HEIGHT;
+		int off_x = (center.x % Tile.WIDTH);
+		int off_y = (center.y % Tile.HEIGHT);
+
+		int posx = getWidth() / 2 - off_x;
+		int posy = getHeight() / 2 - off_y;
+
+		int diff_left = off_x;
+		int diff_right = Tile.WIDTH - off_x;
+		int diff_top = off_y;
+		int diff_bottom = Tile.HEIGHT - off_y;
+
+		boolean start_left = diff_left < diff_right;
+		boolean start_top = diff_top < diff_bottom;
+
+		if (start_top) {
+			if (start_left)
+				iMove = 2;
+			else
+				iMove = 3;
+		} else {
+			if (start_left)
+				iMove = 1;
+			else
+				iMove = 0;
+		} // calculate the visibility borders
+		int x_min = -Tile.WIDTH;
+		int y_min = -Tile.HEIGHT;
+		int x_max = getWidth();
+		int y_max = getHeight();
+
+		boolean painted = true;
+		int x = 0;
+		while (painted) {
+			painted = false;
+			for (int y = 0; y < 4; y++) {
+				if (y % 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
+						Tile tile = getTile(tilex, tiley, zoom);
+						if (tile != null) {
+							painted = true;
+							tile.paint(g, posx, posy);
+							if (tileGridVisible)
+								g.drawRect(posx, posy, Tile.WIDTH, Tile.HEIGHT);
+						}
+					}
+					Point p = move[iMove];
+					posx += p.x * Tile.WIDTH;
+					posy += p.y * Tile.HEIGHT;
+					tilex += p.x;
+					tiley += p.y;
+				}
+				iMove = (iMove + 1) % move.length;
 			}
 		}
-		// Regular tile painting
-		int left = center.x - getWidth() / 2;
-		int top = center.y - getHeight() / 2;
-
-		int tilex = left / Tile.WIDTH;
-		int tiley = top / Tile.HEIGHT;
-		int off_x = left % Tile.WIDTH;
-		int off_y = top % Tile.HEIGHT;
-		for (int x = -off_x; x < getWidth(); x += Tile.WIDTH) {
-			int tiley_tmp = tiley;
-			for (int y = -off_y; y < getHeight(); y += Tile.HEIGHT) {
-				Tile tile = getTile(tilex, tiley_tmp, zoom);
-				if (tile != null) {
-					tile.paint(g, x, y);
-					if (tileGridVisible)
-						g.drawRect(x, y, Tile.WIDTH, Tile.HEIGHT);
-				}
-				tiley_tmp++;
-			}
-			tilex++;
-		}
-		g.fillOval(getWidth()/2 - 5, getHeight()/2 - 5, 10, 10);
-		g.drawString("Test", 50, 10);
+		g.drawString("Tiles in cache: " + tileCache.getTileCount(), 50, 20);
 		if (!mapMarkersVisible || mapMarkerList == null)
 			return;
@@ -394,17 +427,41 @@
 		}
 		if (!tile.isLoaded()) {
-			jobDispatcher.addJob(new Runnable() {
+			jobDispatcher.addJob(new Job() {
+
+				InputStream input = null;
 
 				public void run() {
 					Tile tile = tileCache.getTile(tilex, tiley, zoom);
-					if (tile.isLoaded())
+					if (tile == null || tile.isLoaded())
 						return;
 					try {
-						Thread.sleep(500);
-						tile.loadTileImage();
+						//Thread.sleep(500);
+						URL url;
+						url = new URL("http://tile.openstreetmap.org/" + tile.getKey() + ".png");
+						HttpURLConnection urlConn = (HttpURLConnection) url.openConnection();
+						urlConn.setReadTimeout(30000); // 30 seconds read
+														// timeout
+						input = urlConn.getInputStream();
+						tile.setImage(ImageIO.read(input));
+						tile.setLoaded(true);
 						repaint();
+						input.close();
+						input = null;
 					} catch (Exception e) {
-						System.err.println("failed loading " + zoom + "/" + tilex + "/" + tiley
-								+ " " + e.getMessage());
+						if (input == null /* || !input.isStopped() */)
+							System.err.println("failed loading " + zoom + "/" + tilex + "/" + tiley
+									+ " " + e.getMessage());
+					}
+				}
+
+				/**
+				 * Terminating all transfers that are currently in progress
+				 */
+				public void stop() {
+
+					try {
+						// if (input != null)
+						// input.stop();
+					} catch (Exception e) {
 					}
 				}
Index: applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/Job.java
===================================================================
--- applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/Job.java	(revision 9288)
+++ applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/Job.java	(revision 9288)
@@ -0,0 +1,18 @@
+package org.openstreetmap.gui.jmapviewer;
+
+//License: GPL. Copyright 2008 by Jan Peter Stotz
+
+/**
+ * An extension to the {@link Runnable} interface adding the possibility to
+ * {@link #stop()} it.
+ * 
+ * @author Jan Peter Stotz
+ */
+public interface Job extends Runnable {
+
+	/**
+	 * Allows to stop / cancel a job without having to interrupt the executing
+	 * {@link Thread}.
+	 */
+	public void stop();
+}
Index: applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/JobDispatcher.java
===================================================================
--- applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/JobDispatcher.java	(revision 9263)
+++ applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/JobDispatcher.java	(revision 9288)
@@ -16,8 +16,8 @@
 	protected BlockingQueue<Runnable> jobQueue = new LinkedBlockingQueue<Runnable>();
 
-	Thread[] threads;
+	JobThread[] threads;
 
 	public JobDispatcher(int threadCound) {
-		threads = new Thread[threadCound];
+		threads = new JobThread[threadCound];
 		for (int i = 0; i < threadCound; i++) {
 			threads[i] = new JobThread(i + 1);
@@ -26,5 +26,6 @@
 
 	/**
-	 * Removes all jobs from the queue that are currently not being processed.
+	 * Removes all jobs from the queue that are currently not being processed
+	 * and stops those currently being processed.
 	 */
 	public void cancelOutstandingJobs() {
@@ -32,6 +33,7 @@
 		for (int i = 0; i < threads.length; i++) {
 			try {
-				threads[i].interrupt();
-				threads[i] = new JobThread(i + 1);
+				Runnable job = threads[i].getJob();
+				if ((job != null) && (job instanceof Job))
+					((Job) job).stop();
 			} catch (Exception e) {
 				e.printStackTrace();
@@ -49,6 +51,9 @@
 	protected class JobThread extends Thread {
 
+		Runnable job;
+
 		public JobThread(int threadId) {
 			super("OSMJobThread " + threadId);
+			job = null;
 			start();
 		}
@@ -57,5 +62,4 @@
 		public void run() {
 			while (!isInterrupted()) {
-				Runnable job;
 				try {
 					job = jobQueue.take();
@@ -65,4 +69,5 @@
 				try {
 					job.run();
+					job = null;
 				} catch (Exception e) {
 					e.printStackTrace();
@@ -70,4 +75,13 @@
 			}
 		}
+
+		/**
+		 * @return the job being executed at the moment or <code>null</code> if
+		 *         the thread is idle.
+		 */
+		public Runnable getJob() {
+			return job;
+		}
+
 	}
 
Index: applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/MemoryTileCache.java
===================================================================
--- applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/MemoryTileCache.java	(revision 9263)
+++ applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/MemoryTileCache.java	(revision 9288)
@@ -3,13 +3,11 @@
 //License: GPL. Copyright 2008 by Jan Peter Stotz
 
-import java.util.Comparator;
 import java.util.Hashtable;
-import java.util.Map;
-import java.util.Set;
-import java.util.TreeSet;
-import java.util.Map.Entry;
 import java.util.logging.Logger;
 
 /**
+ * {@link TileCache} implementation that stores all {@link Tile} objects in
+ * memory up to a certain limit ({@link #getCacheSize()}). If the limit is
+ * exceeded the least recently used {@link Tile} objects will be deleted.
  * 
  * @author Jan Peter Stotz
@@ -17,29 +15,28 @@
 public class MemoryTileCache implements TileCache {
 
-	private static final Logger log = Logger.getLogger(MemoryTileCache.class
-			.getName());
-
-	protected int cacheSizeMax = 200;
+	private static final Logger log = Logger.getLogger(MemoryTileCache.class.getName());
 
 	/**
-	 * Number of tiles after a cache cleanup via {@link #removeOldTiles()};
+	 * Default cache size
 	 */
-	protected int cacheSizeDefault = 100;
+	protected int cacheSize = 200;
 
 	protected Hashtable<String, CacheEntry> hashtable;
 
 	/**
-	 * A logical clock that "ticks" every time a tile is retrieved from the
-	 * cache
+	 * List of all tiles in their last recently used order
 	 */
-	protected int currentAccessTime = 0;
+	protected CacheLinkedListElement lruTiles;
 
 	public MemoryTileCache() {
-		hashtable = new Hashtable<String, CacheEntry>(200);
+		hashtable = new Hashtable<String, CacheEntry>(cacheSize);
+		lruTiles = new CacheLinkedListElement();
 	}
 
-	public synchronized void addTile(Tile tile) {
-		hashtable.put(tile.getKey(), new CacheEntry(tile, currentAccessTime));
-		if (hashtable.size() > cacheSizeMax)
+	public void addTile(Tile tile) {
+		CacheEntry entry = new CacheEntry(tile);
+		hashtable.put(tile.getKey(), entry);
+		lruTiles.addFirst(entry);
+		if (hashtable.size() > cacheSize)
 			removeOldTiles();
 	}
@@ -49,82 +46,137 @@
 		if (entry == null)
 			return null;
-		currentAccessTime++;
-		entry.lastAccess = currentAccessTime;
-		// We are right before an integer overflow!!
-		if (currentAccessTime == Integer.MAX_VALUE)
-			removeOldTiles();
+		// We don't care about placeholder tiles and hourglass image tiles, the
+		// important tiles are the loaded ones
+		if (entry.tile.isLoaded())
+			lruTiles.moveElementToFirstPos(entry);
 		return entry.tile;
 	}
 
 	/**
-	 * Removes the least recently used tiles and rewrites the
-	 * {@link CacheEntry#lastAccess} of all remaining entries (-n to 0).
-	 * 
-	 * WARNING: While this method is running modifying the {@link #hashtable} is
-	 * forbidden! Therefore this method and {@link #addTile(Tile)} are declared
-	 * as synchronized.
+	 * Removes the least recently used tiles
 	 */
-	protected synchronized void removeOldTiles() {
-		try {
-			Set<Map.Entry<String, CacheEntry>> entries = hashtable.entrySet();
-			TreeSet<Map.Entry<String, CacheEntry>> sortedEntries;
-			// Sort the entries according to their access time
-			sortedEntries = new TreeSet<Map.Entry<String, CacheEntry>>(
-					new MEComparator());
-			sortedEntries.addAll(entries);
-			// System.out.println("Tiles in Cache: " + hashtable.size() +
-			// " lru=" + currentAccessTime);
-			int tilecount = 0;
-			for (Map.Entry<String, CacheEntry> entry : sortedEntries) {
-				tilecount++;
-				if (tilecount < cacheSizeDefault) {
-					entry.getValue().lastAccess = -tilecount;
-				} else {
-					// System.out.println("removing entry :"
-					// + entry.getValue().lastAccess);
-					entries.remove(entry);
+	protected void removeOldTiles() {
+		synchronized (lruTiles) {
+			try {
+				while (lruTiles.getElementCount() > cacheSize) {
+					CacheEntry entry = lruTiles.getLastElement();
+					hashtable.remove(entry.tile.getKey());
+					lruTiles.removeEntry(entry);
 				}
+			} catch (Exception e) {
+				log.warning(e.getMessage());
 			}
-			// We can now safely reset the the logical clock
-			currentAccessTime = 1;
-			// System.out.println("Tiles in Cache: " + hashtable.size() +
-			// " lru=" + currentAccessTime);
-		} catch (Exception e) {
-			log.severe(e.toString());
 		}
 	}
 
-	public int getCacheSizeMax() {
-		return cacheSizeMax;
+	public int getTileCount() {
+		return hashtable.size();
 	}
 
-	public void setCacheSizeMax(int cacheSizeMax) {
-		this.cacheSizeMax = cacheSizeMax;
-		this.cacheSizeDefault = cacheSizeMax / 2;
+	public int getCacheSize() {
+		return cacheSize;
 	}
 
-	protected static class CacheEntry implements Comparable<CacheEntry> {
-		int lastAccess;
+	/**
+	 * Changes the maximum number of {@link Tile} objects that this cache holds.
+	 * 
+	 * @param cacheSize
+	 *            new maximum number of tiles
+	 */
+	public void setCacheSize(int cacheSize) {
+		this.cacheSize = cacheSize;
+		if (hashtable.size() > cacheSize)
+			removeOldTiles();
+	}
+
+	/**
+	 * Linked list element holding the {@link Tile} and links to the
+	 * {@link #next} and {@link #prev} item in the list.
+	 */
+	protected static class CacheEntry {
 		Tile tile;
 
-		protected CacheEntry(Tile tile, int currentAccessTime) {
+		CacheEntry next;
+		CacheEntry prev;
+
+		protected CacheEntry(Tile tile) {
 			this.tile = tile;
-			lastAccess = currentAccessTime;
 		}
 
-		public int compareTo(CacheEntry o) {
-			if (lastAccess > o.lastAccess)
-				return -1;
-			else
-				return 1;
-		}
 	}
 
-	protected static class MEComparator implements
-			Comparator<Map.Entry<String, CacheEntry>> {
+	/**
+	 * Special implementation of a double linked list for {@link CacheEntry}
+	 * elements. It supports element removal in constant time - in difference to
+	 * the Java implementation which needs O(n).
+	 * 
+	 * @author Jan Peter Stotz
+	 */
+	protected static class CacheLinkedListElement {
+		protected CacheEntry firstElement = null;
+		protected CacheEntry lastElement;
+		protected int elementCount;
 
-		public int compare(Entry<String, CacheEntry> o1,
-				Entry<String, CacheEntry> o2) {
-			return o1.getValue().compareTo(o2.getValue());
+		public CacheLinkedListElement() {
+			elementCount = 0;
+			firstElement = null;
+			lastElement = null;
+		}
+
+		/**
+		 * Add the element to the head of the list.
+		 * 
+		 * @param new element to be added
+		 */
+		public synchronized void addFirst(CacheEntry element) {
+			if (elementCount == 0) {
+				firstElement = element;
+				lastElement = element;
+				element.prev = null;
+				element.next = null;
+			} else {
+				element.next = firstElement;
+				firstElement.prev = element;
+				element.prev = null;
+				firstElement = element;
+			}
+			elementCount++;
+		}
+
+		/**
+		 * Removes the specified elemntent form the list.
+		 * 
+		 * @param element
+		 *            to be removed
+		 */
+		public synchronized void removeEntry(CacheEntry element) {
+			if (element.next != null) {
+				element.next.prev = element.prev;
+			}
+			if (element.prev != null) {
+				element.prev.next = element.next;
+			}
+			if (element == firstElement)
+				firstElement = element.next;
+			if (element == lastElement)
+				lastElement = element.prev;
+			element.next = null;
+			element.prev = null;
+			elementCount--;
+		}
+
+		public synchronized void moveElementToFirstPos(CacheEntry entry) {
+			if (firstElement == entry)
+				return;
+			removeEntry(entry);
+			addFirst(entry);
+		}
+
+		public int getElementCount() {
+			return elementCount;
+		}
+
+		public CacheEntry getLastElement() {
+			return lastElement;
 		}
 	}
Index: applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/Tile.java
===================================================================
--- applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/Tile.java	(revision 9263)
+++ applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/Tile.java	(revision 9288)
@@ -7,10 +7,4 @@
 import java.awt.geom.AffineTransform;
 import java.awt.image.BufferedImage;
-import java.io.DataInputStream;
-import java.io.IOException;
-import java.net.URL;
-import java.net.URLConnection;
-
-import javax.imageio.ImageIO;
 
 /**
@@ -30,6 +24,4 @@
 	public static final int WIDTH = 256;
 	public static final int HEIGHT = 256;
-	public static final int WIDTH_HALF = 128;
-	public static final int HEIGHT_HALF = 128;
 
 	/**
@@ -63,9 +55,9 @@
 		Graphics2D g = (Graphics2D) tmpImage.getGraphics();
 		// g.drawImage(image, 0, 0, null);
-		for (int zoomDiff = 1; zoomDiff < 3; zoomDiff++) {
+		for (int zoomDiff = 1; zoomDiff < 5; zoomDiff++) {
 			// first we check if there are already the 2^x tiles
 			// of a higher detail level
 			int zoom_high = zoom + zoomDiff;
-			if (zoom_high <= JMapViewer.MAX_ZOOM) {
+			if (zoomDiff < 3 && zoom_high <= JMapViewer.MAX_ZOOM) {
 				int factor = 1 << zoomDiff;
 				int xtile_high = xtile << zoomDiff;
@@ -135,4 +127,8 @@
 	}
 
+	public void setImage(BufferedImage image) {
+		this.image = image;
+	}
+
 	/**
 	 * @return key that identifies a tile
@@ -146,18 +142,6 @@
 	}
 
-	public synchronized void loadTileImage() throws IOException {
-		if (loaded)
-			return;
-		URL url;
-		URLConnection urlConn;
-		DataInputStream input;
-		url = new URL("http://tile.openstreetmap.org/" + zoom + "/" + xtile + "/" + ytile + ".png");
-		// System.out.println(url);
-		urlConn = url.openConnection();
-		// urlConn.setUseCaches(false);
-		input = new DataInputStream(urlConn.getInputStream());
-		image = ImageIO.read(input);
-		input.close();
-		loaded = true;
+	public void setLoaded(boolean loaded) {
+		this.loaded = loaded;
 	}
 
@@ -179,4 +163,9 @@
 
 	@Override
+	public String toString() {
+		return "Tile " + getTileKey(xtile, ytile, zoom);
+	}
+
+	@Override
 	public boolean equals(Object obj) {
 		if (!(obj instanceof Tile))
Index: applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/TileCache.java
===================================================================
--- applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/TileCache.java	(revision 9263)
+++ applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/TileCache.java	(revision 9288)
@@ -34,3 +34,8 @@
 	 */
 	public void addTile(Tile tile);
+
+	/**
+	 * @return the number of tiles hold by the cache
+	 */
+	public int getTileCount();
 }
