Index: /applications/editors/josm/plugins/slippymap/src/org/openstreetmap/josm/plugins/slippymap/SlippyMapKey.java
===================================================================
--- /applications/editors/josm/plugins/slippymap/src/org/openstreetmap/josm/plugins/slippymap/SlippyMapKey.java	(revision 14731)
+++ /applications/editors/josm/plugins/slippymap/src/org/openstreetmap/josm/plugins/slippymap/SlippyMapKey.java	(revision 14732)
@@ -11,4 +11,5 @@
  * 
  * @author LuVar <lubomir.varga@freemap.sk>
+ * @author Dave Hansen <dave@sr71.net>
  *
  */
@@ -16,4 +17,5 @@
 	private final int x;
 	private final int y;
+	private final int level;
 	
 	/**
@@ -26,7 +28,15 @@
 	 * @param y	y position in tiles table
 	 */
-	public SlippyMapKey(int x, int y) {
+	public final boolean valid;
+	public SlippyMapKey(int level, int x, int y) {
 		this.x = x;
 		this.y = y;
+		this.level = level;
+		if (level <= 0 || x < 0 || y < 0) {
+			this.valid = false;
+			System.err.println("invalid SlippyMapKey("+level+", "+x+", "+y+")");
+		} else {
+			this.valid = true;
+		}
 	}
 	
@@ -42,5 +52,5 @@
 		if (obj instanceof SlippyMapKey) {
 			SlippyMapKey smk = (SlippyMapKey) obj;
-			if((smk.x == this.x) && (smk.y == this.y)) {
+			if((smk.x == this.x) && (smk.y == this.y) && (smk.level == this.level)) {
 				return true;
 			}
@@ -55,5 +65,5 @@
 	@Override
 	public int hashCode() {
-		return new Integer(this.x + this.y * 10000).hashCode();
+		return new Integer(this.x + this.y * 10000 + this.level * 100000).hashCode();
 	}
 	
@@ -63,5 +73,5 @@
 	@Override
 	public String toString() {
-		return "SlippyMapKey(x=" + this.x + ",y=" + this.y + ")";
+		return "SlippyMapKey(x=" + this.x + ",y=" + this.y + ",level=" + level + ")";
 	}
 	
Index: /applications/editors/josm/plugins/slippymap/src/org/openstreetmap/josm/plugins/slippymap/SlippyMapLayer.java
===================================================================
--- /applications/editors/josm/plugins/slippymap/src/org/openstreetmap/josm/plugins/slippymap/SlippyMapLayer.java	(revision 14731)
+++ /applications/editors/josm/plugins/slippymap/src/org/openstreetmap/josm/plugins/slippymap/SlippyMapLayer.java	(revision 14732)
@@ -12,5 +12,7 @@
 import java.awt.event.MouseEvent;
 import java.awt.image.ImageObserver;
+import java.util.Comparator;
 import java.util.HashMap;
+import java.util.TreeSet;
 
 import javax.swing.AbstractAction;
@@ -37,4 +39,5 @@
  * @author Frederik Ramm <frederik@remote.org>
  * @author LuVar <lubomir.varga@freemap.sk>
+ * @author Dave Hansen <dave@sr71.net>
  * 
  */
@@ -46,5 +49,5 @@
 	 */
 	public int currentZoomLevel = SlippyMapPreferences.getMinZoomLvl();
-	private HashMap<SlippyMapKey, SlippyMapTile>[] tileStorage = null;
+	private HashMap<SlippyMapKey, SlippyMapTile> tileStorage = null;
 
 	Point[][] pixelpos = new Point[21][21];
@@ -68,5 +71,5 @@
 			public void actionPerformed(ActionEvent ae) {
 				if (clickedTile != null) {
-					clickedTile.loadImage();
+					loadSingleTile(clickedTile);
 					needRedraw = true;
 					Main.map.repaint();
@@ -120,5 +123,4 @@
 					public void actionPerformed(ActionEvent ae) {
 						decreaseZoomLevel();
-						needRedraw = true;
 						Main.map.repaint();
 					}
@@ -162,7 +164,9 @@
 		if (currentZoomLevel < SlippyMapPreferences.getMaxZoomLvl()) {
 			currentZoomLevel++;
+			Main.debug("increasing zoom level to: " + currentZoomLevel);
+			needRedraw = true;
 		} else {
-			System.err
-					.println("current zoom lvl couldnt be increased. MaxZoomLvl reached.");
+			System.err.println("current zoom lvl ("+currentZoomLevel+") couldnt be increased. "+
+							 "MaxZoomLvl ("+SlippyMapPreferences.getMaxZoomLvl()+") reached.");
 		}
 	}
@@ -173,8 +177,9 @@
 	public void decreaseZoomLevel() {
 		if (currentZoomLevel > SlippyMapPreferences.getMinZoomLvl()) {
+			Main.debug("decreasing zoom level to: " + currentZoomLevel);
 			currentZoomLevel--;
+			needRedraw = true;
 		} else {
-			System.err
-					.println("current zoom lvl couldnt be decreased. MinZoomLvl reached.");
+			System.err.println("current zoom lvl couldnt be decreased. MinZoomLvl reached.");
 		}
 	}
@@ -184,9 +189,59 @@
 		// the setting isnt saved yet.
 		int maxZoom = 30; // SlippyMapPreferences.getMaxZoomLvl();
-		// +1 because of array indexed from 0.
-		tileStorage = new HashMap[maxZoom + 1];
-
-		for (int i = 0; i < maxZoom + 1; i++)
-			tileStorage[i] = new HashMap<SlippyMapKey, SlippyMapTile>();
+		tileStorage = new HashMap<SlippyMapKey, SlippyMapTile>();
+
+		checkTileStorage();
+	}
+
+	class TileTimeComp implements Comparator<SlippyMapTile> {
+			public int compare(SlippyMapTile s1, SlippyMapTile s2) {
+					long t1 = s1.access_time();
+					long t2 = s2.access_time();
+					if (s1 == s2)
+							return 0;
+					if (t1 == t2) {
+							t1 = s1.hashCode();
+							t2 = s2.hashCode();
+					}
+					if (t1 < t2)
+							return -1;
+					return 1;
+			}
+	}
+
+	long lastCheck = 0;
+	/**
+	 * <p>
+	 * Check if tiles.size() is not more than max_nr_tiles. If yes, oldest tiles by timestamp
+	 * are fired out from cache.
+	 * </p>
+	 */
+	public void checkTileStorage() {
+		int maxZoom = 30; // SlippyMapPreferences.getMaxZoomLvl();
+		long now = System.currentTimeMillis();
+		if (now - lastCheck < 1000)
+				return;
+		lastCheck = now;
+		TreeSet<SlippyMapTile> tiles = new TreeSet<SlippyMapTile>(new TileTimeComp());
+		tiles.addAll(tileStorage.values());
+		int max_nr_tiles = 100;
+		if (tiles.size() < max_nr_tiles) {
+			Main.debug("total of " + tiles.size() + " loaded tiles, size OK " + now);
+			return;
+		}
+		int nr_to_drop = tiles.size() - max_nr_tiles;;
+		Main.debug("total of " + tiles.size() + " tiles, need to flush " + nr_to_drop + " tiles");
+		for (SlippyMapTile t : tiles) {
+			if (nr_to_drop <= 0)
+					break;
+			t.dropImage();
+			nr_to_drop--;
+		}
+	}
+
+	void loadSingleTile(SlippyMapTile tile)
+	{
+		tile.loadImage();
+		this.checkTileStorage();
 	}
 
@@ -224,16 +279,38 @@
 		for (int x = z12x0 - 1; x <= z12x1; x++) {
 			for (int y = z12y0 - 1; y <= z12y1; y++) {
-				SlippyMapKey key = new SlippyMapKey(x, y);
-
-				SlippyMapTile tile = tileStorage[currentZoomLevel].get(key);
-
+				SlippyMapKey key = new SlippyMapKey(currentZoomLevel, x, y);
+				SlippyMapTile tile = tileStorage.get(key);
+				if (!key.valid) {
+						System.out.println("paint-1() made invalid key");
+						continue;
+				}
 				if (tile == null)
-					tileStorage[currentZoomLevel].put(key,
+					tileStorage.put(key,
 							tile = new SlippyMapTile(x, y, currentZoomLevel));
-
-				if (tile.getImage() == null)
-					tile.loadImage();
-			}
-		}
+				if (tile.getImage() == null) {
+					this.loadSingleTile(tile);
+				}
+			}
+		}
+	}
+
+	/*
+	 * Attempt to approximate how much the image is
+	 * being scaled.  For instance, a 100x100 image
+	 * being scaled to 50x50 would return 0.25.
+	 */
+	double getImageScaling(Image img, Point p0, Point p1)
+	{
+		int realWidth = img.getWidth(this);
+		int realHeight = img.getHeight(this);
+		if (realWidth == -1 || realHeight == -1)
+				return 1.0;
+		int drawWidth = p1.x - p0.x;
+		int drawHeight = p1.x - p0.x;
+
+		double drawArea = drawWidth * drawHeight;
+		double realArea = realWidth * realHeight;
+
+		return drawArea / realArea;
 	}
 
@@ -242,8 +319,13 @@
 	@Override
 	public void paint(Graphics g, MapView mv) {
+		long start = System.currentTimeMillis();
 		LatLon topLeft = mv.getLatLon(0, 0);
 		LatLon botRight = mv.getLatLon(mv.getWidth(), mv.getHeight());
 		Graphics oldg = g;
 
+		if (botRight.lon() == 0.0 || botRight.lat() == 0) {
+				// probably still initializing
+				return;
+		}
 		if (lastTopLeft != null && lastBotRight != null
 				&& topLeft.equalsEpsilon(lastTopLeft)
@@ -298,26 +380,23 @@
 		float fadeBackground = SlippyMapPreferences.getFadeBackground();
 
+		Double imageScale = null;
+		int count = 0;
 		for (int x = z12x0 - 1; x <= z12x1; x++) {
 			for (int y = z12y0 - 1; y <= z12y1; y++) {
-				SlippyMapKey key = new SlippyMapKey(x, y);
+				SlippyMapKey key = new SlippyMapKey(currentZoomLevel, x, y);
 				SlippyMapTile tile;
-				try {
-					tile = tileStorage[currentZoomLevel].get(key);
-				} catch (IndexOutOfBoundsException ex) {
-					throw new RuntimeException("currentZoomLevel="
-							+ currentZoomLevel
-							+ " and tile storage array have just size="
-							+ tileStorage.length
-							+ " and maxZoomLvl in preferences is "
-							+ SlippyMapPreferences.getMaxZoomLvl() + ".", ex);
-				}
-
+				tile = tileStorage.get(key);
+				if (!key.valid) {
+						System.out.println("loadAllTiles() made invalid key");
+						continue;
+				}
 				if (tile == null) {
 					tile = new SlippyMapTile(x, y, currentZoomLevel);
-					tileStorage[currentZoomLevel].put(key, tile);
+					tileStorage.put(key, tile);
 					if (SlippyMapPreferences.getAutoloadTiles()) {
 						// TODO probably do on background
-						tile.loadImage();
-					}
+						loadSingleTile(tile);
+					}
+					checkTileStorage();
 				}
 				Image img = tile.getImage();
@@ -327,5 +406,7 @@
 					Point p2 = pixelpos[x - z12x0 + 2][y - z12y0 + 2];
 					g.drawImage(img, p.x, p.y, p2.x - p.x, p2.y - p.y, this);
-
+					if (imageScale == null)
+						imageScale = new Double(getImageScaling(img, p, p2));
+					count++;
 					if (fadeBackground != 0f) {
 						// dimm by painting opaque rect...
@@ -336,5 +417,4 @@
 			}// end of for
 		}// end of for
-
 		g.setColor(Color.red);
 		for (int x = z12x0 - 1; x <= z12x1; x++) {
@@ -351,9 +431,16 @@
 
 			for (int y = z12y0 - 1; y <= z12y1; y++) {
-				SlippyMapKey key = new SlippyMapKey(x, y);
+				SlippyMapKey key = new SlippyMapKey(currentZoomLevel, x, y);
 				int texty = p.y + 2 + fontHeight;
-				SlippyMapTile tile = tileStorage[currentZoomLevel].get(key);
+				SlippyMapTile tile = tileStorage.get(key);
 				if (tile == null) {
 					continue;
+				}
+				if (!key.valid) {
+						System.out.println("paint-0() made invalid key");
+						continue;
+				}
+				if (tile.getImage() == null) {
+						loadSingleTile(tile);
 				}
 				p = pixelpos[x - z12x0 + 1][y - z12y0 + 2];
@@ -395,19 +482,27 @@
 		oldg.drawImage(bufferImage, 0, 0, null);
 
-		// TODO do autozoom nicer
-		if ((z12x1 - z12x0 < 2) || (z12y1 - z12y0 < 2)) {
-			if (SlippyMapPreferences.getAutozoom()) {
-				increaseZoomLevel();
-			}
-			this.paint(oldg, mv);
-		}
-
-		if ((z12x1 - z12x0 > 6) || (z12y1 - z12y0 > 6)) {
-			if (SlippyMapPreferences.getAutozoom()) {
-				decreaseZoomLevel();
-			}
-			this.paint(oldg, mv);
-		}
-		
+		if (imageScale != null) {
+			// If each source image pixel is being stretched into > 3
+			// drawn pixels, zoom in... getting too pixelated
+			if (imageScale > 3) {
+				if (SlippyMapPreferences.getAutozoom()) {
+				    Main.debug("autozoom increase: "+z12x1+" " + z12x0 + " " + z12y1 + " " + z12y0
+									+ topLeft + " " + botRight + " scale: " + imageScale);
+					increaseZoomLevel();
+				}
+				this.paint(oldg, mv);
+			}
+
+			// If each source image pixel is being squished into > 0.32
+			// of a drawn pixels, zoom out.
+			if (imageScale < 0.32) {
+				if (SlippyMapPreferences.getAutozoom()) {
+				    Main.debug("autozoom decrease: "+z12x1+" " + z12x0 + " " + z12y1 + " " + z12y0
+									+ topLeft + " " + botRight + " scale: " + imageScale);
+					decreaseZoomLevel();
+				}
+				this.paint(oldg, mv);
+			}
+		}	
 		g.setColor(Color.black);
 		g.drawString("currentZoomLevel=" + currentZoomLevel, 120, 120);
@@ -433,12 +528,17 @@
 			}
 		}
-		if (tiley == -1)
+		if (tiley == -1) {
 			return null;
-
-		SlippyMapKey key = new SlippyMapKey(tilex, tiley);
-		SlippyMapTile tile = tileStorage[currentZoomLevel].get(key);
+		}
+
+		SlippyMapKey key = new SlippyMapKey(currentZoomLevel, tilex, tiley);
+		if (!key.valid) {
+			System.err.println("getTileForPixelpos("+px+","+py+") made invalid key");
+			return null;
+		}
+		SlippyMapTile tile = tileStorage.get(key);
 		if (tile == null)
-			tileStorage[currentZoomLevel].put(key, tile = new SlippyMapTile(
-					tilex, tiley, currentZoomLevel));
+			tileStorage.put(key, tile = new SlippyMapTile(tilex, tiley, currentZoomLevel));
+		checkTileStorage();
 		return tile;
 	}
@@ -506,6 +606,18 @@
 	public boolean imageUpdate(Image img, int infoflags, int x, int y,
 			int width, int height) {
-
 		boolean done = ((infoflags & (ERROR | FRAMEBITS | ALLBITS)) != 0);
+		if ((infoflags & ERROR) != 0) {
+				String url = "unknown";
+				for (SlippyMapTile tile : tileStorage.values()) {
+						if (tile.getImage() != img)
+								continue;
+						url = tile.getImageURL().toString();
+				}
+				System.err.println("imageUpdate(" + img + ") error " + url +")");
+		}
+		if ((infoflags & SOMEBITS) != 0) {
+				//if (y%100 == 0)
+				//	System.out.println("imageUpdate("+img+") SOMEBITS ("+x+","+y+")");
+		}
 		// Repaint immediately if we are done, otherwise batch up
 		// repaint requests every 100 milliseconds
Index: /applications/editors/josm/plugins/slippymap/src/org/openstreetmap/josm/plugins/slippymap/SlippyMapTile.java
===================================================================
--- /applications/editors/josm/plugins/slippymap/src/org/openstreetmap/josm/plugins/slippymap/SlippyMapTile.java	(revision 14731)
+++ /applications/editors/josm/plugins/slippymap/src/org/openstreetmap/josm/plugins/slippymap/SlippyMapTile.java	(revision 14732)
@@ -16,4 +16,5 @@
  * @author Frederik Ramm <frederik@remote.org>
  * @author LuVar <lubomir.varga@freemap.sk>
+ * @author Dave Hansen <dave@sr71.net>
  * 
  */
@@ -21,4 +22,5 @@
 {
     private Image  tileImage;
+	long timestamp;
 
     int x;
@@ -33,4 +35,5 @@
         this.y = y;
         this.z = z;
+		timestamp = System.currentTimeMillis();
     }
 
@@ -40,12 +43,10 @@
     }
 
-    public void loadImage()
+
+    public URL getImageURL()
     {
         try
         {
-            URL imageURL = new URL(SlippyMapPreferences.getMapUrl() + "/" + z
-                    + "/" + x + "/" + y + ".png");
-            
-            tileImage = Toolkit.getDefaultToolkit().createImage(imageURL);
+            return new URL(SlippyMapPreferences.getMapUrl() + "/" + z + "/" + x + "/" + y + ".png");
         }
         catch (MalformedURLException mfu)
@@ -53,9 +54,27 @@
             mfu.printStackTrace();
         }
+            return null;
+        }
+
+    public void loadImage()
+    {
+        URL imageURL = this.getImageURL();
+        tileImage = Toolkit.getDefaultToolkit().createImage(imageURL);
+		Toolkit.getDefaultToolkit().sync();
+   		timestamp = System.currentTimeMillis();
     }
 
     public Image getImage()
     {
+        timestamp = System.currentTimeMillis();
         return tileImage;
+    }
+
+    public void dropImage()
+    {
+		tileImage = null;
+		//  This should work in theory but doesn't seem to actually
+		//  reduce the X server memory usage
+		//tileImage.flush();
     }
 
@@ -74,5 +93,5 @@
         catch (Exception ex)
         {
-            metadata = tr("error loading metadata");
+            metadata = tr("error loading metadata" + ex.toString());
         }
 
@@ -88,4 +107,5 @@
             BufferedReader in = new BufferedReader(new InputStreamReader(devc
                     .getInputStream()));
+			timestamp = System.currentTimeMillis();
             metadata = tr("requested: {0}", tr(in.readLine()));
         }
@@ -94,4 +114,9 @@
             metadata = tr("error requesting update");
         }
+    }
+
+    public long access_time()
+    {
+        return timestamp;
     }
 
