Index: applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/JMapViewer.java
===================================================================
--- applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/JMapViewer.java	(revision 16384)
+++ applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/JMapViewer.java	(revision 16391)
@@ -20,5 +20,4 @@
 import javax.swing.event.ChangeListener;
 
-import org.openstreetmap.gui.jmapviewer.JobDispatcher.JobThread;
 import org.openstreetmap.gui.jmapviewer.interfaces.MapMarker;
 import org.openstreetmap.gui.jmapviewer.interfaces.MapSquare;
@@ -48,8 +47,4 @@
     public static final int MIN_ZOOM = 0;
 
-    protected TileLoader tileLoader;
-    protected TileCache tileCache;
-    protected TileSource tileSource;
-
     protected List<MapMarker> mapMarkerList;
     protected List<MapSquare> mapSquareList;
@@ -59,4 +54,6 @@
 
     protected boolean tileGridVisible;
+    
+    protected TileController tileController; 
 
     /**
@@ -75,5 +72,5 @@
     protected JButton zoomOutButton;
 
-    JobDispatcher jobDispatcher;
+    
 
     /**
@@ -91,8 +88,5 @@
     public JMapViewer(TileCache tileCache, int downloadThreadCount) {
         super();
-        tileSource = new OsmTileSource.Mapnik();
-        tileLoader = new OsmTileLoader(this);
-        this.tileCache = tileCache;
-        jobDispatcher = JobDispatcher.getInstance();
+        tileController = new TileController(new OsmTileSource.Mapnik(), tileCache, this);
         mapMarkerList = new LinkedList<MapMarker>();
         mapSquareList = new LinkedList<MapSquare>();
@@ -108,5 +102,5 @@
 
     protected void initializeZoomSlider() {
-        zoomSlider = new JSlider(MIN_ZOOM, tileSource.getMaxZoom());
+        zoomSlider = new JSlider(MIN_ZOOM, tileController.getTileSource().getMaxZoom());
         zoomSlider.setOrientation(JSlider.VERTICAL);
         zoomSlider.setBounds(10, 10, 30, 150);
@@ -195,5 +189,5 @@
 
     public void setDisplayPosition(Point mapPoint, int x, int y, int zoom) {
-        if (zoom > tileSource.getMaxZoom() || zoom < MIN_ZOOM)
+        if (zoom > tileController.getTileSource().getMaxZoom() || zoom < MIN_ZOOM)
             return;
 
@@ -228,5 +222,5 @@
         int x_max = Integer.MIN_VALUE;
         int y_max = Integer.MIN_VALUE;
-        int mapZoomMax = tileSource.getMaxZoom();
+        int mapZoomMax = tileController.getTileSource().getMaxZoom();
         for (MapMarker marker : mapMarkerList) {
             int x = OsmMercator.LonToX(marker.getLon(), mapZoomMax);
@@ -258,4 +252,45 @@
         setDisplayPosition(x, y, newZoom);
     }
+    
+    /**
+     * Sets the displayed map pane and zoom level so that all map markers are
+     * visible.
+     */
+    public void setDisplayToFitMapSquares() {
+        if (mapSquareList == null || mapSquareList.size() == 0) {
+            return;
+        }
+        int x_min = Integer.MAX_VALUE;
+        int y_min = Integer.MAX_VALUE;
+        int x_max = Integer.MIN_VALUE;
+        int y_max = Integer.MIN_VALUE;
+        int mapZoomMax = tileController.getTileSource().getMaxZoom();
+        for (MapSquare square : mapSquareList) {
+            x_max = Math.max(x_max, OsmMercator.LonToX(square.getBottomRight().getLon(), mapZoomMax));
+            y_max = Math.max(y_max, OsmMercator.LatToY(square.getTopLeft().getLat(), mapZoomMax));
+            x_min = Math.min(x_min, OsmMercator.LonToX(square.getTopLeft().getLon(), mapZoomMax));
+            y_min = Math.min(y_min, OsmMercator.LatToY(square.getBottomRight().getLat(), mapZoomMax));
+        }
+        int height = Math.max(0, getHeight());
+        int width = Math.max(0, getWidth());
+        // System.out.println(x_min + " < x < " + x_max);
+        // System.out.println(y_min + " < y < " + y_max);
+        // System.out.println("tiles: " + width + " " + height);
+        int newZoom = mapZoomMax;
+        int x = x_max - x_min;
+        int y = y_max - y_min;
+        while (x > width || y > height) {
+            // System.out.println("zoom: " + zoom + " -> " + x + " " + y);
+            newZoom--;
+            x >>= 1;
+            y >>= 1;
+        }
+        x = x_min + (x_max - x_min) / 2;
+        y = y_min + (y_max - y_min) / 2;
+        int z = 1 << (mapZoomMax - newZoom);
+        x /= z;
+        y /= z;
+        setDisplayPosition(x, y, newZoom);
+    }
 
     public Coordinate getPosition() {
@@ -286,14 +321,57 @@
      * @param lat
      * @param lon
-     * @return point on the map or <code>null</code> if the point is not visible
-     */
-    public Point getMapPosition(double lat, double lon) {
+     * @param checkOutside
+     * @return point on the map or <code>null</code> if the point is not visible and checkOutside set to <code>true</code> 
+     */
+    public Point getMapPosition(double lat, double lon, boolean checkOutside) {
         int x = OsmMercator.LonToX(lon, zoom);
         int y = OsmMercator.LatToY(lat, zoom);
         x -= center.x - getWidth() / 2;
         y -= center.y - getHeight() / 2;
-        if (x < 0 || y < 0 || x > getWidth() || y > getHeight())
+        if (checkOutside) {
+            if (x < 0 || y < 0 || x > getWidth() || y > getHeight()) {
+                return null;
+            }
+        }
+        return new Point(x, y);
+    }
+    
+    /**
+     * Calculates the position on the map of a given coordinate
+     * 
+     * @param lat
+     * @param lon
+     * @return point on the map or <code>null</code> if the point is not visible
+     */
+    public Point getMapPosition(double lat, double lon) {
+        return getMapPosition(lat, lon, true);
+    }
+    
+    /**
+     * Calculates the position on the map of a given coordinate
+     * 
+     * @param coord
+     * @return point on the map or <code>null</code> if the point is not visible
+     */
+    public Point getMapPosition(Coordinate coord) {
+        if (coord != null) {
+            return getMapPosition(coord.getLat(), coord.getLon());
+        } else {
             return null;
-        return new Point(x, y);
+        }
+    }
+    
+    /**
+     * Calculates the position on the map of a given coordinate
+     * 
+     * @param coord
+     * @return point on the map or <code>null</code> if the point is not visible and checkOutside set to <code>true</code>
+     */
+    public Point getMapPosition(Coordinate coord, boolean checkOutside) {
+        if (coord != null) {
+            return getMapPosition(coord.getLat(), coord.getLon(), checkOutside);
+        } else {
+            return null;
+        }
     }
 
@@ -349,5 +427,5 @@
                     if (x_min <= posx && posx <= x_max && y_min <= posy && posy <= y_max) {
                         // tile is visible
-                        Tile tile = getTile(tilex, tiley, zoom);
+                        Tile tile = tileController.getTile(tilex, tiley, zoom);
                         if (tile != null) {
                             painted = true;
@@ -377,6 +455,6 @@
                 Coordinate bottomRight = square.getBottomRight();
                 if (topLeft != null && bottomRight != null) {
-                    Point pTopLeft = getMapPosition(topLeft.getLat(), topLeft.getLon());
-                    Point pBottomRight = getMapPosition(bottomRight.getLat(), bottomRight.getLon());
+                    Point pTopLeft = getMapPosition(topLeft.getLat(), topLeft.getLon(), false);
+                    Point pBottomRight = getMapPosition(bottomRight.getLat(), bottomRight.getLon(), false);
                     if (pTopLeft != null && pBottomRight != null) {
                         square.paint(g, pTopLeft, pBottomRight);
@@ -446,8 +524,8 @@
 
     public void setZoom(int zoom, Point mapPoint) {
-        if (zoom > tileSource.getMaxZoom() || zoom < tileSource.getMinZoom() || zoom == this.zoom)
+        if (zoom > tileController.getTileSource().getMaxZoom() || zoom < tileController.getTileSource().getMinZoom() || zoom == this.zoom)
             return;
         Coordinate zoomPos = getPosition(mapPoint);
-        jobDispatcher.cancelOutstandingJobs(); // Clearing outstanding load
+        tileController.cancelOutstandingJobs(); // Clearing outstanding load
         // requests
         setDisplayPositionByLatLon(mapPoint, zoomPos.getLat(), zoomPos.getLon(), zoom);
@@ -456,30 +534,4 @@
     public void setZoom(int zoom) {
         setZoom(zoom, new Point(getWidth() / 2, getHeight() / 2));
-    }
-
-    /**
-     * retrieves a tile from the cache. If the tile is not present in the cache
-     * a load job is added to the working queue of {@link JobThread}.
-     * 
-     * @param tilex
-     * @param tiley
-     * @param zoom
-     * @return specified tile from the cache or <code>null</code> if the tile
-     *         was not found in the cache.
-     */
-    protected Tile getTile(int tilex, int tiley, int zoom) {
-        int max = (1 << zoom);
-        if (tilex < 0 || tilex >= max || tiley < 0 || tiley >= max)
-            return null;
-        Tile tile = tileCache.getTile(tileSource, tilex, tiley, zoom);
-        if (tile == null) {
-            tile = new Tile(tileSource, tilex, tiley, zoom);
-            tileCache.addTile(tile);
-            tile.loadPlaceholderFromCache(tileCache);
-        }
-        if (!tile.isLoaded()) {
-            jobDispatcher.addJob(tileLoader.createTileLoaderJob(tileSource, tilex, tiley, zoom));
-        }
-        return tile;
     }
 
@@ -496,6 +548,6 @@
         zoomInButton.setToolTipText("Zoom to level " + (zoom + 1));
         zoomOutButton.setToolTipText("Zoom to level " + (zoom - 1));
-        zoomOutButton.setEnabled(zoom > tileSource.getMinZoom());
-        zoomInButton.setEnabled(zoom < tileSource.getMaxZoom());
+        zoomOutButton.setEnabled(zoom > tileController.getTileSource().getMinZoom());
+        zoomInButton.setEnabled(zoom < tileController.getTileSource().getMaxZoom());
     }
 
@@ -566,24 +618,4 @@
     public boolean getZoomContolsVisible() {
         return zoomSlider.isVisible();
-    }
-
-    public TileCache getTileCache() {
-        return tileCache;
-    }
-
-    public TileLoader getTileLoader() {
-        return tileLoader;
-    }
-
-    public void setTileLoader(TileLoader tileLoader) {
-        this.tileLoader = tileLoader;
-    }
-
-    public TileSource getTileLayerSource() {
-        return tileSource;
-    }
-
-    public TileSource getTileSource() {
-        return tileSource;
     }
 
@@ -593,8 +625,8 @@
         if (tileSource.getMinZoom() < MIN_ZOOM)
             throw new RuntimeException("Minumim zoom level too low");
-        this.tileSource = tileSource;
+        tileController.setTileSource(tileSource);
         zoomSlider.setMinimum(tileSource.getMinZoom());
         zoomSlider.setMaximum(tileSource.getMaxZoom());
-        jobDispatcher.cancelOutstandingJobs();
+        tileController.cancelOutstandingJobs();
         if (zoom > tileSource.getMaxZoom())
             setZoom(tileSource.getMaxZoom());
@@ -621,3 +653,14 @@
         repaint();
     }
+
+    /* (non-Javadoc)
+     * @see org.openstreetmap.gui.jmapviewer.interfaces.TileLoaderListener#getTileCache()
+     */
+    public TileCache getTileCache() {
+        return tileController.getTileCache();
+    }
+
+    public void setTileLoader(TileLoader loader) {
+        tileController.setTileLoader(loader);
+    }
 }
Index: applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/JobDispatcher.java
===================================================================
--- applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/JobDispatcher.java	(revision 16384)
+++ applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/JobDispatcher.java	(revision 16391)
@@ -94,5 +94,5 @@
     }
 
-    protected class JobThread extends Thread {
+    public class JobThread extends Thread {
 
         Runnable job;
Index: applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/TileController.java
===================================================================
--- applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/TileController.java	(revision 16391)
+++ applications/viewer/jmapviewer/src/org/openstreetmap/gui/jmapviewer/TileController.java	(revision 16391)
@@ -0,0 +1,79 @@
+package org.openstreetmap.gui.jmapviewer;
+
+import org.openstreetmap.gui.jmapviewer.JobDispatcher.JobThread;
+import org.openstreetmap.gui.jmapviewer.interfaces.TileCache;
+import org.openstreetmap.gui.jmapviewer.interfaces.TileLoader;
+import org.openstreetmap.gui.jmapviewer.interfaces.TileLoaderListener;
+import org.openstreetmap.gui.jmapviewer.interfaces.TileSource;
+
+public class TileController {
+    protected TileLoader tileLoader;
+    protected TileCache tileCache;
+    protected TileSource tileSource;
+    
+    JobDispatcher jobDispatcher;
+    
+    public TileController(TileSource source, TileCache tileCache, TileLoaderListener listener) {
+        tileSource = new OsmTileSource.Mapnik();
+        tileLoader = new OsmTileLoader(listener);
+        this.tileCache = tileCache;
+        jobDispatcher = JobDispatcher.getInstance();
+    }
+    
+    /**
+     * retrieves a tile from the cache. If the tile is not present in the cache
+     * a load job is added to the working queue of {@link JobThread}.
+     * 
+     * @param tilex
+     * @param tiley
+     * @param zoom
+     * @return specified tile from the cache or <code>null</code> if the tile
+     *         was not found in the cache.
+     */
+    public Tile getTile(int tilex, int tiley, int zoom) {
+        int max = (1 << zoom);
+        if (tilex < 0 || tilex >= max || tiley < 0 || tiley >= max)
+            return null;
+        Tile tile = tileCache.getTile(tileSource, tilex, tiley, zoom);
+        if (tile == null) {
+            tile = new Tile(tileSource, tilex, tiley, zoom);
+            tileCache.addTile(tile);
+            tile.loadPlaceholderFromCache(tileCache);
+        }
+        if (!tile.isLoaded()) {
+            jobDispatcher.addJob(tileLoader.createTileLoaderJob(tileSource, tilex, tiley, zoom));
+        }
+        return tile;
+    }
+    
+    public TileCache getTileCache() {
+        return tileCache;
+    }
+
+    public TileLoader getTileLoader() {
+        return tileLoader;
+    }
+
+    public void setTileLoader(TileLoader tileLoader) {
+        this.tileLoader = tileLoader;
+    }
+
+    public TileSource getTileLayerSource() {
+        return tileSource;
+    }
+
+    public TileSource getTileSource() {
+        return tileSource;
+    }
+
+    public void setTileSource(TileSource tileSource) {
+        this.tileSource = tileSource;
+    }
+    
+    /**
+     * 
+     */
+    public void cancelOutstandingJobs() {
+        jobDispatcher.cancelOutstandingJobs();
+    }
+}
