Index: trunk/src/org/openstreetmap/josm/data/coor/EastNorth.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/coor/EastNorth.java	(revision 11829)
+++ trunk/src/org/openstreetmap/josm/data/coor/EastNorth.java	(revision 11830)
@@ -1,4 +1,6 @@
 // License: GPL. For details, see LICENSE file.
 package org.openstreetmap.josm.data.coor;
+
+import org.openstreetmap.gui.jmapviewer.interfaces.IProjected;
 
 /**
@@ -20,4 +22,12 @@
     public EastNorth(double east, double north) {
         super(east, north);
+    }
+
+    /**
+     * Constructs a new {@code EastNorth} from {@link IProjected}.
+     * @param p projected coordinates
+     */
+    public EastNorth(IProjected p) {
+        super(p.getEast(), p.getNorth());
     }
 
Index: trunk/src/org/openstreetmap/josm/data/imagery/AbstractWMSTileSource.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/imagery/AbstractWMSTileSource.java	(revision 11829)
+++ trunk/src/org/openstreetmap/josm/data/imagery/AbstractWMSTileSource.java	(revision 11830)
@@ -4,7 +4,9 @@
 import java.awt.Point;
 
+import org.openstreetmap.gui.jmapviewer.Projected;
 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.IProjected;
 import org.openstreetmap.gui.jmapviewer.tilesources.TMSTileSource;
 import org.openstreetmap.gui.jmapviewer.tilesources.TileSourceInfo;
@@ -182,3 +184,18 @@
     }
 
+    @Override
+    public IProjected tileXYtoProjected(int x, int y, int zoom) {
+        EastNorth en = getTileEastNorth(x, y, zoom);
+        return new Projected(en.east(), en.north());
+    }
+
+    @Override
+    public TileXY projectedToTileXY(IProjected p, int zoom) {
+        return eastNorthToTileXY(new EastNorth(p.getEast(), p.getNorth()), zoom);
+    }
+
+    @Override
+    public String getServerCRS() {
+        return Main.getProjection().toCode();
+    }
 }
Index: trunk/src/org/openstreetmap/josm/data/imagery/WMTSTileSource.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/imagery/WMTSTileSource.java	(revision 11829)
+++ trunk/src/org/openstreetmap/josm/data/imagery/WMTSTileSource.java	(revision 11830)
@@ -40,10 +40,14 @@
 
 import org.openstreetmap.gui.jmapviewer.Coordinate;
+import org.openstreetmap.gui.jmapviewer.Projected;
 import org.openstreetmap.gui.jmapviewer.Tile;
+import org.openstreetmap.gui.jmapviewer.TileRange;
 import org.openstreetmap.gui.jmapviewer.TileXY;
 import org.openstreetmap.gui.jmapviewer.interfaces.ICoordinate;
+import org.openstreetmap.gui.jmapviewer.interfaces.IProjected;
 import org.openstreetmap.gui.jmapviewer.interfaces.TemplatedTileSource;
 import org.openstreetmap.gui.jmapviewer.tilesources.AbstractTMSTileSource;
 import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.data.ProjectionBounds;
 import org.openstreetmap.josm.data.coor.EastNorth;
 import org.openstreetmap.josm.data.coor.LatLon;
@@ -908,3 +912,71 @@
     }
 
+    @Override
+    public IProjected tileXYtoProjected(int x, int y, int zoom) {
+        TileMatrix matrix = getTileMatrix(zoom);
+        if (matrix == null) {
+            return new Projected(0, 0);
+        }
+        double scale = matrix.scaleDenominator * this.crsScale;
+        return new Projected(
+                matrix.topLeftCorner.east() + x * scale,
+                matrix.topLeftCorner.north() - y * scale);
+    }
+
+    @Override
+    public TileXY projectedToTileXY(IProjected projected, int zoom) {
+        TileMatrix matrix = getTileMatrix(zoom);
+        if (matrix == null) {
+            return new TileXY(0, 0);
+        }
+        double scale = matrix.scaleDenominator * this.crsScale;
+        return new TileXY(
+                (projected.getEast() - matrix.topLeftCorner.east()) / scale,
+                -(projected.getNorth() - matrix.topLeftCorner.north()) / scale);
+    }
+
+    private ProjectionBounds getTileProjectionBounds(Tile tile) {
+        ProjectionBounds pb = new ProjectionBounds(new EastNorth(
+                this.tileXYtoProjected(tile.getXtile(), tile.getYtile(), tile.getZoom())));
+        pb.extend(new EastNorth(this.tileXYtoProjected(tile.getXtile() + 1, tile.getYtile() + 1, tile.getZoom())));
+        return pb;
+    }
+
+    @Override
+    public boolean isInside(Tile inner, Tile outer) {
+        ProjectionBounds pbInner = getTileProjectionBounds(inner);
+        ProjectionBounds pbOuter = getTileProjectionBounds(outer);
+        // a little tolerance, for when inner tile touches the border of the
+        // outer tile
+        double epsilon = 1e-7 * (pbOuter.maxEast - pbOuter.minEast);
+        return pbOuter.minEast <= pbInner.minEast + epsilon &&
+                pbOuter.minNorth <= pbInner.minNorth + epsilon &&
+                pbOuter.maxEast >= pbInner.maxEast - epsilon &&
+                pbOuter.maxNorth >= pbInner.maxNorth - epsilon;
+    }
+
+    @Override
+    public TileRange getCoveringTileRange(Tile tile, int newZoom) {
+        TileMatrix matrixNew = getTileMatrix(newZoom);
+        if (matrixNew == null) {
+            return new TileRange(new TileXY(0, 0), new TileXY(0, 0), newZoom);
+        }
+        IProjected p0 = tileXYtoProjected(tile.getXtile(), tile.getYtile(), tile.getZoom());
+        IProjected p1 = tileXYtoProjected(tile.getXtile() + 1, tile.getYtile() + 1, tile.getZoom());
+        TileXY tMin = projectedToTileXY(p0, newZoom);
+        TileXY tMax = projectedToTileXY(p1, newZoom);
+        // shrink the target tile a little, so we don't get neighboring tiles, that
+        // share an edge, but don't actually cover the target tile
+        double epsilon = 1e-7 * (tMax.getX() - tMin.getX());
+        int minX = (int) Math.floor(tMin.getX() + epsilon);
+        int minY = (int) Math.floor(tMin.getY() + epsilon);
+        int maxX = (int) Math.ceil(tMax.getX() - epsilon) - 1;
+        int maxY = (int) Math.ceil(tMax.getY() - epsilon) - 1;
+        return new TileRange(new TileXY(minX, minY), new TileXY(maxX, maxY), newZoom);
+    }
+
+    @Override
+    public String getServerCRS() {
+        return Main.getProjection().toCode();
+    }
 }
