Index: trunk/src/org/openstreetmap/josm/data/projection/AbstractProjection.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/projection/AbstractProjection.java	(revision 10804)
+++ trunk/src/org/openstreetmap/josm/data/projection/AbstractProjection.java	(revision 10805)
@@ -1,4 +1,9 @@
 // License: GPL. For details, see LICENSE file.
 package org.openstreetmap.josm.data.projection;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.DoubleUnaryOperator;
 
 import org.openstreetmap.josm.data.Bounds;
@@ -8,4 +13,5 @@
 import org.openstreetmap.josm.data.projection.datum.Datum;
 import org.openstreetmap.josm.data.projection.proj.Proj;
+import org.openstreetmap.josm.tools.Utils;
 
 /**
@@ -116,7 +122,46 @@
     @Override
     public LatLon eastNorth2latlon(EastNorth en) {
+        return eastNorth2latlon(en, LatLon::normalizeLon);
+    }
+
+    @Override
+    public LatLon eastNorth2latlonClamped(EastNorth en) {
+        LatLon ll = eastNorth2latlon(en, lon -> Utils.clamp(lon, -180, 180));
+        Bounds bounds = getWorldBoundsLatLon();
+        return new LatLon(Utils.clamp(ll.lat(), bounds.getMinLat(), bounds.getMaxLat()),
+                Utils.clamp(ll.lon(), bounds.getMinLon(), bounds.getMaxLon()));
+    }
+
+    private LatLon eastNorth2latlon(EastNorth en, DoubleUnaryOperator normalizeLon) {
         double[] latlonRad = proj.invproject((en.east() * toMeter - x0) / ellps.a / k0, (en.north() * toMeter - y0) / ellps.a / k0);
-        LatLon ll = new LatLon(Math.toDegrees(latlonRad[0]), LatLon.normalizeLon(Math.toDegrees(latlonRad[1]) + lon0 + pm));
+        double lon = Math.toDegrees(latlonRad[1]) + lon0 + pm;
+        LatLon ll = new LatLon(Math.toDegrees(latlonRad[0]), normalizeLon.applyAsDouble(lon));
         return datum.toWGS84(ll);
+    }
+
+    @Override
+    public Map<ProjectionBounds, Projecting> getProjectingsForArea(ProjectionBounds area) {
+        if (proj.lonIsLinearToEast()) {
+            //FIXME: Respect datum?
+            // wrap the wrold around
+            Bounds bounds = getWorldBoundsLatLon();
+            double minEast = latlon2eastNorth(bounds.getMin()).east();
+            double maxEast = latlon2eastNorth(bounds.getMax()).east();
+            double dEast = maxEast - minEast;
+            if ((area.minEast < minEast || area.maxEast > maxEast) && dEast > 0) {
+                // We could handle the dEast < 0 case but we don't need it atm.
+                int minChunk = (int) Math.floor((area.minEast - minEast) / dEast);
+                int maxChunk = (int) Math.floor((area.maxEast - minEast) / dEast);
+                HashMap<ProjectionBounds, Projecting> ret = new HashMap<>();
+                for (int chunk = minChunk; chunk <= maxChunk; chunk++) {
+                    ret.put(new ProjectionBounds(Math.max(area.minEast, minEast + chunk * dEast), area.minNorth,
+                            Math.min(area.maxEast, maxEast + chunk * dEast), area.maxNorth),
+                            new ShiftedProjecting(this, new EastNorth(-chunk * dEast, 0)));
+                }
+                return ret;
+            }
+        }
+
+        return Collections.singletonMap(area, this);
     }
 
@@ -179,3 +224,8 @@
         return projectionBoundsBox;
     }
+
+    @Override
+    public Projection getBaseProjection() {
+        return this;
+    }
 }
Index: trunk/src/org/openstreetmap/josm/data/projection/Projecting.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/projection/Projecting.java	(revision 10805)
+++ trunk/src/org/openstreetmap/josm/data/projection/Projecting.java	(revision 10805)
@@ -0,0 +1,50 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.data.projection;
+
+import java.util.Map;
+
+import org.openstreetmap.josm.data.ProjectionBounds;
+import org.openstreetmap.josm.data.coor.EastNorth;
+import org.openstreetmap.josm.data.coor.LatLon;
+
+/**
+ * Classes implementing this are able to project between screen (east/north) and {@link LatLon} coordinates.
+ * <p>
+ * Each instance is backed by a base projection but may e.g. offset the resulting position.
+ * @author Michael Zangl
+ * @since 10805
+ */
+public interface Projecting {
+
+    /**
+     * Convert from lat/lon to easting/northing.
+     *
+     * @param ll the geographical point to convert (in WGS84 lat/lon)
+     * @return the corresponding east/north coordinates
+     */
+    EastNorth latlon2eastNorth(LatLon ll);
+
+    /**
+     * Convert a east/north coordinate to the {@link LatLon} coordinate.
+     * This method clamps the lat/lon coordinate to the nearest point in the world bounds.
+     * @param en east/north
+     * @return The lat/lon coordinate.
+     */
+    LatLon eastNorth2latlonClamped(EastNorth en);
+
+    /**
+     * Gets the base projection instance used.
+     * @return The projection.
+     */
+    Projection getBaseProjection();
+
+    /**
+     * Returns an map or (subarea, projecting) paris that contains projecting instances to convert the coordinates inside the given area.
+     * This can be used by projections to support continuous projections.
+     *
+     * It is possible that the area covered by the map is bigger than the one given as area. There may be holes.
+     * @param area The base area
+     * @return a map of non-overlapping {@link ProjectionBounds} instances mapped to the {@link Projecting} object to use for that area.
+     */
+    Map<ProjectionBounds, Projecting> getProjectingsForArea(ProjectionBounds area);
+}
Index: trunk/src/org/openstreetmap/josm/data/projection/Projection.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/projection/Projection.java	(revision 10804)
+++ trunk/src/org/openstreetmap/josm/data/projection/Projection.java	(revision 10805)
@@ -14,5 +14,5 @@
  * factor and x/y offset.
  */
-public interface Projection {
+public interface Projection extends Projecting {
     /**
      * The default scale factor in east/north units per pixel
@@ -22,12 +22,4 @@
      */
     double getDefaultZoomInPPD();
-
-    /**
-     * Convert from lat/lon to easting/northing.
-     *
-     * @param ll the geographical point to convert (in WGS84 lat/lon)
-     * @return the corresponding east/north coordinates
-     */
-    EastNorth latlon2eastNorth(LatLon ll);
 
     /**
Index: trunk/src/org/openstreetmap/josm/data/projection/ShiftedProjecting.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/projection/ShiftedProjecting.java	(revision 10805)
+++ trunk/src/org/openstreetmap/josm/data/projection/ShiftedProjecting.java	(revision 10805)
@@ -0,0 +1,55 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.data.projection;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.openstreetmap.josm.data.ProjectionBounds;
+import org.openstreetmap.josm.data.coor.EastNorth;
+import org.openstreetmap.josm.data.coor.LatLon;
+
+/**
+ * This is a projecting instance that shifts the projection by a given eastnorth offset.
+ * @author Michael Zangl
+ * @since 10805
+ */
+public class ShiftedProjecting implements Projecting {
+    private final Projecting base;
+    private final EastNorth offset;
+
+    /**
+     * Create a new {@link ShiftedProjecting}
+     * @param base The base to use
+     * @param offset The offset to move base. Subtracted when converting lat/lon->east/north.
+     */
+    public ShiftedProjecting(Projecting base, EastNorth offset) {
+        this.base = base;
+        this.offset = offset;
+    }
+
+    @Override
+    public EastNorth latlon2eastNorth(LatLon ll) {
+        return base.latlon2eastNorth(ll).add(offset);
+    }
+
+    @Override
+    public LatLon eastNorth2latlonClamped(EastNorth en) {
+        return base.eastNorth2latlonClamped(en.subtract(offset));
+    }
+
+    @Override
+    public Projection getBaseProjection() {
+        return base.getBaseProjection();
+    }
+
+    @Override
+    public Map<ProjectionBounds, Projecting> getProjectingsForArea(ProjectionBounds area) {
+        Map<ProjectionBounds, Projecting> forArea = base
+                .getProjectingsForArea(new ProjectionBounds(area.getMin().subtract(offset), area.getMax().subtract(offset)));
+        HashMap<ProjectionBounds, Projecting> ret = new HashMap<>();
+        forArea.forEach((pb, projecting) -> ret.put(
+                new ProjectionBounds(pb.getMin().add(offset), pb.getMax().add(offset)),
+                new ShiftedProjecting(projecting, offset)));
+        return ret;
+    }
+}
Index: trunk/src/org/openstreetmap/josm/data/projection/proj/Mercator.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/projection/proj/Mercator.java	(revision 10804)
+++ trunk/src/org/openstreetmap/josm/data/projection/proj/Mercator.java	(revision 10805)
@@ -120,3 +120,8 @@
         return scaleFactor;
     }
+
+    @Override
+    public boolean lonIsLinearToEast() {
+        return true;
+    }
 }
Index: trunk/src/org/openstreetmap/josm/data/projection/proj/Proj.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/projection/proj/Proj.java	(revision 10804)
+++ trunk/src/org/openstreetmap/josm/data/projection/proj/Proj.java	(revision 10805)
@@ -91,3 +91,13 @@
      */
     boolean isGeographic();
+
+    /**
+     * Checks wether the result of projecting a lon coordinate only has a linear relation to the east coordinate and
+     * is not related to lat/north at all.
+     * @return <code>true</code> if lon has a linear relationship to east only.
+     * @since 10805
+     */
+    default boolean lonIsLinearToEast() {
+        return false;
+    }
 }
Index: trunk/src/org/openstreetmap/josm/gui/MapViewState.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/MapViewState.java	(revision 10804)
+++ trunk/src/org/openstreetmap/josm/gui/MapViewState.java	(revision 10805)
@@ -17,4 +17,5 @@
 import org.openstreetmap.josm.data.coor.EastNorth;
 import org.openstreetmap.josm.data.coor.LatLon;
+import org.openstreetmap.josm.data.projection.Projecting;
 import org.openstreetmap.josm.data.projection.Projection;
 import org.openstreetmap.josm.gui.download.DownloadDialog;
@@ -28,5 +29,5 @@
 public final class MapViewState {
 
-    private final Projection projection;
+    private final Projecting projecting;
 
     private final int viewWidth;
@@ -51,6 +52,6 @@
      * @param topLeft The top left corner in east/north space.
      */
-    private MapViewState(Projection projection, int viewWidth, int viewHeight, double scale, EastNorth topLeft) {
-        this.projection = projection;
+    private MapViewState(Projecting projection, int viewWidth, int viewHeight, double scale, EastNorth topLeft) {
+        this.projecting = projection;
         this.scale = scale;
         this.topLeft = topLeft;
@@ -63,5 +64,5 @@
 
     private MapViewState(EastNorth topLeft, MapViewState mapViewState) {
-        this.projection = mapViewState.projection;
+        this.projecting = mapViewState.projecting;
         this.scale = mapViewState.scale;
         this.topLeft = topLeft;
@@ -74,5 +75,5 @@
 
     private MapViewState(double scale, MapViewState mapViewState) {
-        this.projection = mapViewState.projection;
+        this.projecting = mapViewState.projecting;
         this.scale = scale;
         this.topLeft = mapViewState.topLeft;
@@ -85,5 +86,5 @@
 
     private MapViewState(JComponent position, MapViewState mapViewState) {
-        this.projection = mapViewState.projection;
+        this.projecting = mapViewState.projecting;
         this.scale = mapViewState.scale;
         this.topLeft = mapViewState.topLeft;
@@ -106,6 +107,6 @@
     }
 
-    private MapViewState(Projection projection, MapViewState mapViewState) {
-        this.projection = projection;
+    private MapViewState(Projecting projecting, MapViewState mapViewState) {
+        this.projecting = projecting;
         this.scale = mapViewState.scale;
         this.topLeft = mapViewState.topLeft;
@@ -201,5 +202,5 @@
      */
     public Projection getProjection() {
-        return projection;
+        return projecting.getBaseProjection();
     }
 
@@ -269,5 +270,5 @@
      */
     public MapViewState usingProjection(Projection projection) {
-        if (projection.equals(this.projection)) {
+        if (projection.equals(this.projecting)) {
             return this;
         } else {
@@ -358,7 +359,17 @@
          * Gets the current position in LatLon coordinates according to the current projection.
          * @return The positon as LatLon.
+         * @see #getLatLonClamped()
          */
         public LatLon getLatLon() {
-            return projection.eastNorth2latlon(getEastNorth());
+            return projecting.getBaseProjection().eastNorth2latlon(getEastNorth());
+        }
+
+        /**
+         * Gets the latlon coordinate clamped to the current world area.
+         * @return The lat/lon coordinate
+         * @since 10805
+         */
+        public LatLon getLatLonClamped() {
+            return projecting.eastNorth2latlonClamped(getEastNorth());
         }
 
@@ -474,5 +485,6 @@
          */
         public Bounds getLatLonBoundsBox() {
-            return projection.getLatLonBoundsBox(getProjectionBounds());
+            // TODO @michael2402: Use hillclimb.
+            return projecting.getBaseProjection().getLatLonBoundsBox(getProjectionBounds());
         }
 
Index: trunk/src/org/openstreetmap/josm/gui/layer/AbstractTileSourceLayer.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/layer/AbstractTileSourceLayer.java	(revision 10804)
+++ trunk/src/org/openstreetmap/josm/gui/layer/AbstractTileSourceLayer.java	(revision 10805)
@@ -231,5 +231,5 @@
 
     protected void initTileSource(T tileSource) {
-        coordinateConverter = new TileCoordinateConverter(Main.map.mapView, getDisplaySettings());
+        coordinateConverter = new TileCoordinateConverter(Main.map.mapView, tileSource, getDisplaySettings());
         attribution.initialize(tileSource);
 
@@ -368,15 +368,9 @@
      */
     private double getScaleFactor(int zoom) {
-        if (!Main.isDisplayingMapView()) return 1;
-        MapView mv = Main.map.mapView;
-        LatLon topLeft = mv.getLatLon(0, 0);
-        LatLon botRight = mv.getLatLon(mv.getWidth(), mv.getHeight());
-        TileXY t1 = tileSource.latLonToTileXY(topLeft.toCoordinate(), zoom);
-        TileXY t2 = tileSource.latLonToTileXY(botRight.toCoordinate(), zoom);
-
-        int screenPixels = mv.getWidth()*mv.getHeight();
-        double tilePixels = Math.abs((t2.getY()-t1.getY())*(t2.getX()-t1.getX())*tileSource.getTileSize()*tileSource.getTileSize());
-        if (screenPixels == 0 || tilePixels == 0) return 1;
-        return screenPixels/tilePixels;
+        if (coordinateConverter != null) {
+            return coordinateConverter.getScaleFactor(zoom);
+        } else {
+            return 1;
+        }
     }
 
@@ -1246,5 +1240,5 @@
 
     private LatLon getShiftedLatLon(EastNorth en) {
-        return Main.getProjection().eastNorth2latlon(en.add(-getDisplaySettings().getDx(), -getDisplaySettings().getDy()));
+        return coordinateConverter.getProjecting().eastNorth2latlonClamped(en);
     }
 
@@ -1520,8 +1514,8 @@
     @Override
     public void paint(Graphics2D g, MapView mv, Bounds bounds) {
-        ProjectionBounds pb = mv.getState().getViewArea().getProjectionBounds();
-
-        needRedraw = false;
-
+        // old and unused.
+    }
+
+    private void drawInViewArea(Graphics2D g, MapView mv, ProjectionBounds pb) {
         int zoom = currentZoomLevel;
         if (getDisplaySettings().isAutoZoom()) {
@@ -1898,6 +1892,14 @@
             allocateCacheMemory();
             if (memory != null) {
-                super.paint(graphics);
-            }
+                doPaint(graphics);
+            }
+        }
+
+        private void doPaint(MapViewGraphics graphics) {
+            ProjectionBounds pb = graphics.getClipBounds().getProjectionBounds();
+
+            needRedraw = false; // TEMPORARY
+
+            drawInViewArea(graphics.getDefaultGraphics(), graphics.getMapView(), pb);
         }
 
Index: trunk/src/org/openstreetmap/josm/gui/layer/imagery/TileCoordinateConverter.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/layer/imagery/TileCoordinateConverter.java	(revision 10804)
+++ trunk/src/org/openstreetmap/josm/gui/layer/imagery/TileCoordinateConverter.java	(revision 10805)
@@ -6,6 +6,10 @@
 
 import org.openstreetmap.gui.jmapviewer.Tile;
+import org.openstreetmap.gui.jmapviewer.TileXY;
 import org.openstreetmap.gui.jmapviewer.interfaces.ICoordinate;
+import org.openstreetmap.gui.jmapviewer.interfaces.TileSource;
 import org.openstreetmap.josm.data.coor.LatLon;
+import org.openstreetmap.josm.data.projection.Projecting;
+import org.openstreetmap.josm.data.projection.ShiftedProjecting;
 import org.openstreetmap.josm.gui.MapView;
 import org.openstreetmap.josm.gui.MapViewState.MapViewPoint;
@@ -19,12 +23,15 @@
     private MapView mapView;
     private TileSourceDisplaySettings settings;
+    private TileSource tileSource;
 
     /**
      * Create a new coordinate converter for the map view.
      * @param mapView The map view.
+     * @param tileSource The tile source to use when converting coordinates.
      * @param settings displacement settings.
      */
-    public TileCoordinateConverter(MapView mapView, TileSourceDisplaySettings settings) {
+    public TileCoordinateConverter(MapView mapView, TileSource tileSource, TileSourceDisplaySettings settings) {
         this.mapView = mapView;
+        this.tileSource = tileSource;
         this.settings = settings;
     }
@@ -32,4 +39,12 @@
     private MapViewPoint pos(ICoordinate ll) {
         return mapView.getState().getPointFor(new LatLon(ll)).add(settings.getDisplacement());
+    }
+
+    /**
+     * Gets the projecting instance to use to convert between latlon and eastnorth coordinates.
+     * @return The {@link Projecting} instance.
+     */
+    public Projecting getProjecting() {
+        return new ShiftedProjecting(mapView.getProjection(), settings.getDisplacement());
     }
 
@@ -55,3 +70,20 @@
         return pos(c1).rectTo(pos(c2)).getInView();
     }
+
+    /**
+     * Returns average number of screen pixels per tile pixel for current mapview
+     * @param zoom zoom level
+     * @return average number of screen pixels per tile pixel
+     */
+    public double getScaleFactor(int zoom) {
+        LatLon topLeft = mapView.getLatLon(0, 0);
+        LatLon botRight = mapView.getLatLon(mapView.getWidth(), mapView.getHeight());
+        TileXY t1 = tileSource.latLonToTileXY(topLeft.toCoordinate(), zoom);
+        TileXY t2 = tileSource.latLonToTileXY(botRight.toCoordinate(), zoom);
+
+        int screenPixels = mapView.getWidth()*mapView.getHeight();
+        double tilePixels = Math.abs((t2.getY()-t1.getY())*(t2.getX()-t1.getX())*tileSource.getTileSize()*tileSource.getTileSize());
+        if (screenPixels == 0 || tilePixels == 0) return 1;
+        return screenPixels/tilePixels;
+    }
 }
Index: trunk/src/org/openstreetmap/josm/tools/Utils.java
===================================================================
--- trunk/src/org/openstreetmap/josm/tools/Utils.java	(revision 10804)
+++ trunk/src/org/openstreetmap/josm/tools/Utils.java	(revision 10805)
@@ -1552,3 +1552,21 @@
         }
     }
+
+    /**
+     * Clamp a value to the given range
+     * @param val The value
+     * @param min minimum value
+     * @param max maximum value
+     * @return the value
+     * @since 10805
+     */
+    public static double clamp(double val, double min, double max) {
+        if (val < min) {
+            return min;
+        } else if (val > max) {
+            return max;
+        } else {
+            return val;
+        }
+    }
 }
