diff --git a/src/org/openstreetmap/josm/gui/MapSlider.java b/src/org/openstreetmap/josm/gui/MapSlider.java
index fab953d..2b3f726 100644
--- a/src/org/openstreetmap/josm/gui/MapSlider.java
+++ b/src/org/openstreetmap/josm/gui/MapSlider.java
@@ -12,14 +12,16 @@ import javax.swing.event.ChangeListener;
 
 import org.openstreetmap.josm.data.ProjectionBounds;
 import org.openstreetmap.josm.gui.help.Helpful;
+import static org.openstreetmap.josm.gui.MapView.snapZoomMode;
 
 class MapSlider extends JSlider implements PropertyChangeListener, ChangeListener, Helpful {
 
     private final MapView mv;
     private boolean preventChange;
+    private static final double zoomStep = Math.pow(2, 0.2);
 
     MapSlider(MapView mv) {
-        super(35, 150);
+        super(0, 150);
         setOpaque(false);
         this.mv = mv;
         mv.addPropertyChangeListener("scale", this);
@@ -30,25 +32,8 @@ class MapSlider extends JSlider implements PropertyChangeListener, ChangeListene
 
     @Override
     public void propertyChange(PropertyChangeEvent evt) {
-        if (getModel().getValueIsAdjusting()) return;
-
-        ProjectionBounds world = this.mv.getMaxProjectionBounds();
-        ProjectionBounds current = this.mv.getProjectionBounds();
-
-        double cur_e = current.maxEast-current.minEast;
-        double cur_n = current.maxNorth-current.minNorth;
-        double e = world.maxEast-world.minEast;
-        double n = world.maxNorth-world.minNorth;
-        int zoom = 0;
-
-        while (zoom <= 150) {
-            e /= 1.1;
-            n /= 1.1;
-            if (e < cur_e && n < cur_n) {
-                break;
-            }
-            ++zoom;
-        }
+        double maxScale = this.mv.getMaxScale();
+        int zoom = (int) Math.round(Math.log(maxScale/mv.getScale())/Math.log(zoomStep));
         preventChange = true;
         setValue(zoom);
         preventChange = false;
@@ -57,13 +42,13 @@ class MapSlider extends JSlider implements PropertyChangeListener, ChangeListene
     @Override
     public void stateChanged(ChangeEvent e) {
         if (preventChange) return;
-
-        ProjectionBounds world = this.mv.getMaxProjectionBounds();
-        double fact = Math.pow(1.1, getValue());
-        double es = world.maxEast-world.minEast;
-        double n = world.maxNorth-world.minNorth;
-
-        this.mv.zoomTo(new ProjectionBounds(this.mv.getCenter(), es/fact, n/fact));
+        double maxScale = this.mv.getMaxScale();
+        double scale = maxScale/Math.pow(zoomStep, getValue());
+        boolean isAdjusting = getModel().getValueIsAdjusting();
+        snapZoomMode snap = isAdjusting ? snapZoomMode.FLOOR : snapZoomMode.STEP;
+        double snapped = mv.getSnappedScale(scale, snap);
+        this.mv.zoomTo(this.mv.getCenter(), snapped);
+        propertyChange(null);
     }
 
     @Override
diff --git a/src/org/openstreetmap/josm/gui/MapView.java b/src/org/openstreetmap/josm/gui/MapView.java
index ad767f2..23ef09a 100644
--- a/src/org/openstreetmap/josm/gui/MapView.java
+++ b/src/org/openstreetmap/josm/gui/MapView.java
@@ -1103,6 +1103,7 @@ implements PropertyChangeListener, PreferenceChangedListener, OsmDataLayer.Layer
 
     @Override
     public void preferenceChanged(PreferenceChangeEvent e) {
+        super.preferenceChanged(e);
         synchronized (this) {
             paintPreferencesChanged = true;
         }
diff --git a/src/org/openstreetmap/josm/gui/NavigatableComponent.java b/src/org/openstreetmap/josm/gui/NavigatableComponent.java
index feae89f..e82ee85 100644
--- a/src/org/openstreetmap/josm/gui/NavigatableComponent.java
+++ b/src/org/openstreetmap/josm/gui/NavigatableComponent.java
@@ -45,8 +45,10 @@ import org.openstreetmap.josm.data.osm.WaySegment;
 import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor;
 import org.openstreetmap.josm.data.osm.visitor.paint.PaintColors;
 import org.openstreetmap.josm.data.preferences.IntegerProperty;
+import org.openstreetmap.josm.data.preferences.BooleanProperty;
 import org.openstreetmap.josm.data.projection.Projection;
 import org.openstreetmap.josm.data.projection.Projections;
+import org.openstreetmap.josm.data.projection.proj.Mercator;
 import org.openstreetmap.josm.gui.download.DownloadDialog;
 import org.openstreetmap.josm.gui.help.Helpful;
 import org.openstreetmap.josm.gui.mappaint.MapPaintStyles;
@@ -54,6 +56,8 @@ import org.openstreetmap.josm.gui.mappaint.mapcss.MapCSSStyleSource;
 import org.openstreetmap.josm.gui.util.CursorManager;
 import org.openstreetmap.josm.tools.Predicate;
 import org.openstreetmap.josm.tools.Utils;
+import org.openstreetmap.josm.data.Preferences.PreferenceChangeEvent;
+import org.openstreetmap.josm.data.Preferences.PreferenceChangedListener;
 
 /**
  * A component that can be navigated by a {@link MapMover}. Used as map view and for the
@@ -62,7 +66,7 @@ import org.openstreetmap.josm.tools.Utils;
  * @author imi
  * @since 41
  */
-public class NavigatableComponent extends JComponent implements Helpful {
+public class NavigatableComponent extends JComponent implements Helpful, PreferenceChangedListener {
 
     /**
      * Interface to notify listeners of the change of the zoom area.
@@ -89,6 +93,8 @@ public class NavigatableComponent extends JComponent implements Helpful {
     };
 
     public static final IntegerProperty PROP_SNAP_DISTANCE = new IntegerProperty("mappaint.node.snap-distance", 10);
+    public static final BooleanProperty PROP_SNAP_ZOOM = new BooleanProperty("zoom.snap-scale-to-mercator-zoom-levels", true);
+    public enum snapZoomMode { FLOOR, STEP, ROUND }
 
     public static final String PROPNAME_CENTER = "center";
     public static final String PROPNAME_SCALE  = "scale";
@@ -143,6 +149,8 @@ public class NavigatableComponent extends JComponent implements Helpful {
      */
     public NavigatableComponent() {
         setLayout(null);
+        Main.pref.addPreferenceChangeListener(this);
+        scale = getSnappedScale(scale, snapZoomMode.ROUND);
     }
 
     protected DataSet getCurrentDataSet() {
@@ -284,6 +292,17 @@ public class NavigatableComponent extends JComponent implements Helpful {
                 getProjection().latlon2eastNorth(b.getMax()));
     }
 
+    // maximum: world in 256 pixels
+    // getSnappedScale() and also MaxSlider uses this value
+    // as scale for zoom 0
+    public double getMaxScale() {
+        ProjectionBounds world = getMaxProjectionBounds();
+        return Math.max(
+            world.maxNorth-world.minNorth,
+            world.maxEast-world.minEast
+        )/256;
+    }
+
     /* FIXME: replace with better method - used by Main to reset Bounds when projection changes, don't use otherwise */
     public Bounds getRealBounds() {
         return new Bounds(
@@ -409,10 +428,7 @@ public class NavigatableComponent extends JComponent implements Helpful {
      * @param initial true if this call initializes the viewport.
      */
     public void zoomTo(EastNorth newCenter, double newScale, boolean initial) {
-        Bounds b = getProjection().getWorldBoundsLatLon();
         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();
@@ -423,30 +439,16 @@ public class NavigatableComponent extends JComponent implements Helpful {
         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 out too much
+        newScale = Math.min(newScale, getMaxScale());
 
         // 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() && ll2.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 (!Double.isInfinite(scaleMin) && newScale < scaleMin) {
-                newScale = scaleMin;
-            }
-        }
+        double d_m = getDist100Pixel();
+        double scaleMin = 0.01 * scale / d_m;
+        if (!Double.isInfinite(scaleMin) && newScale < scaleMin)
+            newScale = scaleMin;
 
+        newScale = getSnappedScale(newScale, snapZoomMode.FLOOR);
         if (!newCenter.equals(center) || !Utils.equalsEpsilon(scale, newScale)) {
             if (!initial) {
                 pushZoomUndo(center, scale);
@@ -455,6 +457,25 @@ public class NavigatableComponent extends JComponent implements Helpful {
         }
     }
 
+    public double getSnappedScale(double newScale) {
+        return getSnappedScale(newScale, snapZoomMode.STEP);
+    }
+
+    public double getSnappedScale(double newScale, snapZoomMode mode) {
+        if (!(PROP_SNAP_ZOOM.get() && "EPSG:3857".equals(Main.getProjection().toCode()))) return newScale;
+        double askedScale = newScale;
+        double tileSizeAtZeroZoom = getMaxScale();
+        double tmsZoom = Math.log(tileSizeAtZeroZoom/newScale)/Math.log(2);
+        int tmsZoomLevel = (int) (mode == snapZoomMode.FLOOR ? Math.floor(tmsZoom) : Math.round(tmsZoom));
+        newScale = tileSizeAtZeroZoom/Math.pow(2, tmsZoomLevel);
+        double diff = askedScale/this.scale-1;
+        if (mode == snapZoomMode.STEP && newScale == this.scale && Math.abs(diff) > 0.0001) {
+            if (diff > 0) newScale *= 2;
+            if (diff < 0) newScale /= 2;
+        }
+        return newScale;
+    }
+
     /**
      * Zoom to the given coordinate without adding to the zoom undo buffer.
      *
@@ -484,6 +505,10 @@ public class NavigatableComponent extends JComponent implements Helpful {
         }
     }
 
+    public void zoomTo(double newScale) {
+        zoomTo(center, newScale);
+    }
+
     public void zoomTo(EastNorth newCenter) {
         zoomTo(newCenter, scale);
     }
@@ -529,7 +554,7 @@ public class NavigatableComponent extends JComponent implements Helpful {
     }
 
     public void zoomToFactor(double x, double y, double factor) {
-        double newScale = scale*factor;
+        double newScale = getSnappedScale(scale*factor);
         // New center position so that point under the mouse pointer stays the same place as it was before zooming
         // You will get the formula by simplifying this expression: newCenter = oldCenter + mouseCoordinatesInNewZoom - mouseCoordinatesInOldZoom
         zoomTo(new EastNorth(
@@ -539,11 +564,11 @@ public class NavigatableComponent extends JComponent implements Helpful {
     }
 
     public void zoomToFactor(EastNorth newCenter, double factor) {
-        zoomTo(newCenter, scale*factor);
+        zoomTo(newCenter, getSnappedScale(scale*factor));
     }
 
     public void zoomToFactor(double factor) {
-        zoomTo(center, scale*factor);
+        zoomTo(center, getSnappedScale(scale*factor));
     }
 
     public void zoomTo(ProjectionBounds box) {
@@ -1516,4 +1541,11 @@ public class NavigatableComponent extends JComponent implements Helpful {
         }
         repaint();
     }
+
+    @Override
+    public void preferenceChanged(PreferenceChangeEvent e) {
+        if (e != null && e.getKey() == PROP_SNAP_ZOOM.getKey()) {
+            zoomTo(getSnappedScale(scale, snapZoomMode.ROUND));
+        }
+    }
 }
diff --git a/src/org/openstreetmap/josm/gui/layer/AbstractTileSourceLayer.java b/src/org/openstreetmap/josm/gui/layer/AbstractTileSourceLayer.java
index 926bf61..a17ca3f 100644
--- a/src/org/openstreetmap/josm/gui/layer/AbstractTileSourceLayer.java
+++ b/src/org/openstreetmap/josm/gui/layer/AbstractTileSourceLayer.java
@@ -84,6 +84,7 @@ import org.openstreetmap.josm.gui.dialogs.LayerListPopup;
 import org.openstreetmap.josm.gui.progress.ProgressMonitor;
 import org.openstreetmap.josm.io.WMSLayerImporter;
 import org.openstreetmap.josm.tools.GBC;
+import org.openstreetmap.josm.tools.Utils;
 
 /**
  * Base abstract class that supports displaying images provided by TileSource. It might be TMS source, WMS or WMTS
@@ -278,6 +279,7 @@ public abstract class AbstractTileSourceLayer extends ImageryLayer implements Im
 
         int screenPixels = mv.getWidth()*mv.getHeight();
         double tilePixels = Math.abs((t2.getY()-t1.getY())*(t2.getX()-t1.getX())*tileSource.getTileSize()*tileSource.getTileSize());
+        tilePixels = Utils.roundToSignificantDigits(tilePixels, 9);
         if (screenPixels == 0 || tilePixels == 0) return 1;
         return screenPixels/tilePixels;
     }
diff --git a/src/org/openstreetmap/josm/gui/preferences/display/LafPreference.java b/src/org/openstreetmap/josm/gui/preferences/display/LafPreference.java
index 026fbeb..80a1ec2 100644
--- a/src/org/openstreetmap/josm/gui/preferences/display/LafPreference.java
+++ b/src/org/openstreetmap/josm/gui/preferences/display/LafPreference.java
@@ -24,6 +24,7 @@ import javax.swing.UIManager.LookAndFeelInfo;
 import org.openstreetmap.josm.Main;
 import org.openstreetmap.josm.actions.ExpertToggleAction;
 import org.openstreetmap.josm.gui.dialogs.ToggleDialog;
+import org.openstreetmap.josm.gui.NavigatableComponent;
 import org.openstreetmap.josm.gui.preferences.PreferenceSetting;
 import org.openstreetmap.josm.gui.preferences.PreferenceSettingFactory;
 import org.openstreetmap.josm.gui.preferences.PreferenceTabbedPane;
@@ -61,6 +62,7 @@ public class LafPreference implements SubPreferenceSetting {
     private final JCheckBox dynamicButtons = new JCheckBox(tr("Dynamic buttons in side menus"));
     private final JCheckBox isoDates = new JCheckBox(tr("Display ISO dates"));
     private final JCheckBox nativeFileChoosers = new JCheckBox(tr("Use native file choosers (nicer, but do not support file filters)"));
+    private final JCheckBox snapToMercatorZoomLevels = new JCheckBox(tr("Snap zoom to Mercator zoom levels (imagery looks perfect, zoom steps are larger)"));
 
     @Override
     public void addGui(PreferenceTabbedPane gui) {
@@ -140,6 +142,11 @@ public class LafPreference implements SubPreferenceSetting {
         nativeFileChoosers.setSelected(FileChooserManager.PROP_USE_NATIVE_FILE_DIALOG.get());
         panel.add(nativeFileChoosers, GBC.eop().insets(20, 0, 0, 0));
 
+        snapToMercatorZoomLevels.setToolTipText(
+                tr("Displays tiles without resizing, similar to most websites. Zoom steps are twice larger than default. Ignored when using different projection."));
+        snapToMercatorZoomLevels.setSelected(NavigatableComponent.PROP_SNAP_ZOOM.get());
+        panel.add(snapToMercatorZoomLevels, GBC.eop().insets(20, 0, 0, 0));
+
         panel.add(Box.createVerticalGlue(), GBC.eol().insets(0, 20, 0, 0));
 
         panel.add(new JLabel(tr("Look and Feel")), GBC.std().insets(20, 0, 0, 0));
@@ -161,6 +168,7 @@ public class LafPreference implements SubPreferenceSetting {
         Main.pref.put(ToggleDialog.PROP_DYNAMIC_BUTTONS.getKey(), dynamicButtons.isSelected());
         Main.pref.put(DateUtils.PROP_ISO_DATES.getKey(), isoDates.isSelected());
         Main.pref.put(FileChooserManager.PROP_USE_NATIVE_FILE_DIALOG.getKey(), nativeFileChoosers.isSelected());
+        Main.pref.put(NavigatableComponent.PROP_SNAP_ZOOM.getKey(), snapToMercatorZoomLevels.isSelected());
         mod |= Main.pref.put("laf", ((LookAndFeelInfo) lafCombo.getSelectedItem()).getClassName());
         return mod;
     }
diff --git a/src/org/openstreetmap/josm/tools/Utils.java b/src/org/openstreetmap/josm/tools/Utils.java
index b08d5f9..8ab1f5e 100644
--- a/src/org/openstreetmap/josm/tools/Utils.java
+++ b/src/org/openstreetmap/josm/tools/Utils.java
@@ -1636,4 +1636,9 @@ public final class Utils {
         return gvs;
     }
 
+    public static double roundToSignificantDigits(double number, int digits) {
+        double scale = Math.pow(10, Math.floor(Math.log10(Math.abs(number))) + 1 - digits);
+        return scale * Math.round(number / scale);
+    }
+
 }
