Index: trunk/src/org/openstreetmap/josm/data/projection/AbstractProjection.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/projection/AbstractProjection.java	(revision 9117)
+++ trunk/src/org/openstreetmap/josm/data/projection/AbstractProjection.java	(revision 9118)
@@ -2,4 +2,6 @@
 package org.openstreetmap.josm.data.projection;
 
+import org.openstreetmap.josm.data.Bounds;
+import org.openstreetmap.josm.data.ProjectionBounds;
 import org.openstreetmap.josm.data.coor.EastNorth;
 import org.openstreetmap.josm.data.coor.LatLon;
@@ -31,4 +33,6 @@
     protected double pm;       /* prime meridian */
     protected double k0 = 1.0; /* general scale factor */
+
+    private volatile ProjectionBounds projectionBoundsBox;
 
     public final Ellipsoid getEllipsoid() {
@@ -105,3 +109,34 @@
         return degree + (minute/60.0) + (second/3600.0);
     }
+
+    @Override
+    public final ProjectionBounds getWorldBoundsBoxEastNorth() {
+        ProjectionBounds result = projectionBoundsBox;
+        if (result == null) {
+            synchronized (this) {
+                result = projectionBoundsBox;
+                if (result == null) {
+                    Bounds b = getWorldBoundsLatLon();
+                    // add 4 corners
+                    result = new ProjectionBounds(latlon2eastNorth(b.getMin()));
+                    result.extend(latlon2eastNorth(b.getMax()));
+                    result.extend(latlon2eastNorth(new LatLon(b.getMinLat(), b.getMaxLon())));
+                    result.extend(latlon2eastNorth(new LatLon(b.getMaxLat(), b.getMinLon())));
+                    // and trace along the outline
+                    double dLon = (b.getMaxLon() - b.getMinLon()) / 1000;
+                    double dLat = (b.getMaxLat() - b.getMinLat()) / 1000;
+                    for (double lon=b.getMinLon(); lon<b.getMaxLon(); lon += dLon) {
+                        result.extend(latlon2eastNorth(new LatLon(b.getMinLat(), lon)));
+                        result.extend(latlon2eastNorth(new LatLon(b.getMaxLat(), lon)));
+                    }
+                    for (double lat=b.getMinLat(); lat<b.getMaxLat(); lat += dLat) {
+                        result.extend(latlon2eastNorth(new LatLon(lat, b.getMinLon())));
+                        result.extend(latlon2eastNorth(new LatLon(lat, b.getMaxLat())));
+                    }
+                    projectionBoundsBox = result;
+                }
+            }
+        }
+        return projectionBoundsBox;
+    }
 }
Index: trunk/src/org/openstreetmap/josm/data/projection/Projection.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/projection/Projection.java	(revision 9117)
+++ trunk/src/org/openstreetmap/josm/data/projection/Projection.java	(revision 9118)
@@ -3,4 +3,5 @@
 
 import org.openstreetmap.josm.data.Bounds;
+import org.openstreetmap.josm.data.ProjectionBounds;
 import org.openstreetmap.josm.data.coor.EastNorth;
 import org.openstreetmap.josm.data.coor.LatLon;
@@ -70,4 +71,16 @@
 
     /**
+     * Get an approximate EastNorth box around the lat/lon world bounds.
+     *
+     * Note: The projection is only valid within the bounds returned by
+     * {@link #getWorldBoundsLatLon()}. The lat/lon bounds need not be a
+     * rectangular shape in east/north space. This method returns a box that
+     * contains this shape.
+     *
+     * @return EastNorth box around the lat/lon world bounds
+     */
+    ProjectionBounds getWorldBoundsBoxEastNorth();
+
+    /**
      * Get the number of meters per unit of this projection. This more
      * defines the scale of the map, than real conversion of unit to meters
Index: trunk/src/org/openstreetmap/josm/gui/NavigatableComponent.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/NavigatableComponent.java	(revision 9117)
+++ trunk/src/org/openstreetmap/josm/gui/NavigatableComponent.java	(revision 9118)
@@ -391,44 +391,38 @@
     public void zoomTo(EastNorth newCenter, double newScale, boolean initial) {
         Bounds b = getProjection().getWorldBoundsLatLon();
-        LatLon cl = Projections.inverseProject(newCenter);
-        boolean changed = false;
-        double lat = cl.lat();
-        double lon = cl.lon();
-        if (lat < b.getMinLat()) {
-            changed = true;
-            lat = b.getMinLat();
-        } else if (lat > b.getMaxLat()) {
-            changed = true;
-            lat = b.getMaxLat();
-        }
-        if (lon < b.getMinLon()) {
-            changed = true;
-            lon = b.getMinLon();
-        } else if (lon > b.getMaxLon()) {
-            changed = true;
-            lon = b.getMaxLon();
-        }
-        if (changed) {
-            newCenter = Projections.project(new LatLon(lat, lon));
-        }
-        int width = getWidth()/2;
-        int height = getHeight()/2;
-        LatLon l1 = new LatLon(b.getMinLat(), lon);
-        LatLon l2 = new LatLon(b.getMaxLat(), lon);
-        EastNorth e1 = getProjection().latlon2eastNorth(l1);
-        EastNorth e2 = getProjection().latlon2eastNorth(l2);
-        double d = e2.north() - e1.north();
-        if (height > 0 && d < height*newScale) {
-            double newScaleH = d/height;
-            e1 = getProjection().latlon2eastNorth(new LatLon(lat, b.getMinLon()));
-            e2 = getProjection().latlon2eastNorth(new LatLon(lat, b.getMaxLon()));
-            d = e2.east() - e1.east();
-            if (width > 0 && d < width*newScale) {
-                newScale = Math.max(newScaleH, d/width);
-            }
-        } else if (height > 0) {
-            d = d/(l1.greatCircleDistance(l2)*height*10);
-            if (newScale < d) {
-                newScale = d;
+        ProjectionBounds pb = getProjection().getWorldBoundsBoxEastNorth();
+        int width = getWidth();
+        int height = getHeight();
+
+        // make sure, the center of the screen is within projection bounds
+        double east = newCenter.east();
+        double north = newCenter.north();
+        east = Math.max(east, pb.minEast);
+        east = Math.min(east, pb.maxEast);
+        north = Math.max(north, pb.minNorth);
+        north = Math.min(north, pb.maxNorth);
+        newCenter = new EastNorth(east, north);
+
+        // don't zoom out too much, the world bounds should be at least
+        // half the size of the screen
+        double pbHeight = pb.maxNorth - pb.minNorth;
+        if (height > 0 && 2 * pbHeight < height * newScale) {
+            double newScaleH = 2 * pbHeight / height;
+            double pbWidth = pb.maxEast - pb.minEast;
+            if (width > 0 && 2 * pbWidth < width * newScale) {
+                double newScaleW = 2 * pbWidth / width;
+                newScale = Math.max(newScaleH, newScaleW);
+            }
+        }
+
+        // don't zoom in too much, minimum: 100 px = 1 cm
+        LatLon ll1 = getLatLon(width / 2 - 50, height / 2);
+        LatLon ll2 = getLatLon(width / 2 + 50, height / 2);
+        if (ll1.isValid() && ll1.isValid() && b.contains(ll1) && b.contains(ll2)) {
+            double d_m = ll1.greatCircleDistance(ll2);
+            double d_en = 100 * scale;
+            double scaleMin = 0.01 * d_en / d_m / 100;
+            if (newScale < scaleMin) {
+                newScale = scaleMin;
             }
         }
