Index: trunk/src/org/openstreetmap/josm/data/imagery/CachedTileLoaderFactory.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/imagery/CachedTileLoaderFactory.java	(revision 10650)
+++ trunk/src/org/openstreetmap/josm/data/imagery/CachedTileLoaderFactory.java	(revision 10651)
@@ -60,9 +60,4 @@
 
     @Override
-    public TileLoader makeTileLoader(TileLoaderListener listener) {
-        return makeTileLoader(listener, null);
-    }
-
-    @Override
     public TileLoader makeTileLoader(TileLoaderListener listener, Map<String, String> inputHeaders) {
         Map<String, String> headers = new ConcurrentHashMap<>();
Index: trunk/src/org/openstreetmap/josm/data/imagery/TileLoaderFactory.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/imagery/TileLoaderFactory.java	(revision 10650)
+++ trunk/src/org/openstreetmap/josm/data/imagery/TileLoaderFactory.java	(revision 10651)
@@ -13,15 +13,10 @@
  * @since 8526
  */
+@FunctionalInterface
 public interface TileLoaderFactory {
 
     /**
      * @param listener that will be notified, when tile has finished loading
-     * @return TileLoader that notifies specified listener
-     */
-    TileLoader makeTileLoader(TileLoaderListener listener);
-
-    /**
-     * @param listener that will be notified, when tile has finished loading
-     * @param headers that will be sent with requests to TileSource
+     * @param headers that will be sent with requests to TileSource. <code>null</code> indicates none
      * @return TileLoader that uses both of above
      */
Index: trunk/src/org/openstreetmap/josm/gui/MapViewState.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/MapViewState.java	(revision 10650)
+++ trunk/src/org/openstreetmap/josm/gui/MapViewState.java	(revision 10651)
@@ -8,4 +8,5 @@
 import java.awt.geom.Point2D;
 import java.awt.geom.Point2D.Double;
+import java.awt.geom.Rectangle2D;
 
 import javax.swing.JComponent;
@@ -144,4 +145,14 @@
 
     /**
+     * Gets the {@link MapViewPoint} for the given {@link LatLon} coordinate.
+     * @param latlon the position
+     * @return The point for that position.
+     * @since 10651
+     */
+    public MapViewPoint getPointFor(LatLon latlon) {
+        return getPointFor(getProjection().latlon2eastNorth(latlon));
+    }
+
+    /**
      * Gets a rectangle representing the whole view area.
      * @return The rectangle.
@@ -351,4 +362,14 @@
             return projection.eastNorth2latlon(getEastNorth());
         }
+
+        /**
+         * Add the given offset to this point
+         * @param en The offset in east/north space.
+         * @return The new point
+         * @since 10651
+         */
+        public MapViewPoint add(EastNorth en) {
+            return new MapViewEastNorthPoint(getEastNorth().add(en));
+        }
     }
 
@@ -455,4 +476,17 @@
             return projection.getLatLonBoundsBox(getProjectionBounds());
         }
+
+        /**
+         * Gets this rectangle on the screen.
+         * @return The rectangle.
+         * @since 10651
+         */
+        public Rectangle2D getInView() {
+            double x1 = p1.getInViewX();
+            double y1 = p1.getInViewY();
+            double x2 = p2.getInViewX();
+            double y2 = p2.getInViewY();
+            return new Rectangle2D.Double(Math.min(x1, x2), Math.min(y1, y2), Math.abs(x1 - x2), Math.abs(y1 - y2));
+        }
     }
 
Index: trunk/src/org/openstreetmap/josm/gui/layer/AbstractTileSourceLayer.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/layer/AbstractTileSourceLayer.java	(revision 10650)
+++ trunk/src/org/openstreetmap/josm/gui/layer/AbstractTileSourceLayer.java	(revision 10651)
@@ -13,9 +13,10 @@
 import java.awt.Image;
 import java.awt.Point;
-import java.awt.Rectangle;
 import java.awt.Toolkit;
 import java.awt.event.ActionEvent;
 import java.awt.event.MouseAdapter;
 import java.awt.event.MouseEvent;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
 import java.awt.image.BufferedImage;
 import java.awt.image.ImageObserver;
@@ -37,4 +38,5 @@
 import java.util.concurrent.ConcurrentSkipListSet;
 import java.util.concurrent.atomic.AtomicInteger;
+import java.util.stream.Stream;
 
 import javax.swing.AbstractAction;
@@ -68,4 +70,5 @@
 import org.openstreetmap.josm.actions.SaveActionBase;
 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;
@@ -78,4 +81,5 @@
 import org.openstreetmap.josm.gui.MapFrame;
 import org.openstreetmap.josm.gui.MapView;
+import org.openstreetmap.josm.gui.MapViewState.MapViewRectangle;
 import org.openstreetmap.josm.gui.NavigatableComponent.ZoomChangeListener;
 import org.openstreetmap.josm.gui.PleaseWaitRunnable;
@@ -83,4 +87,5 @@
 import org.openstreetmap.josm.gui.dialogs.LayerListPopup;
 import org.openstreetmap.josm.gui.layer.imagery.ImageryFilterSettings.FilterChangeListener;
+import org.openstreetmap.josm.gui.layer.imagery.TileCoordinateConverter;
 import org.openstreetmap.josm.gui.layer.imagery.TileSourceDisplaySettings;
 import org.openstreetmap.josm.gui.layer.imagery.TileSourceDisplaySettings.DisplaySettingsChangeEvent;
@@ -170,4 +175,6 @@
 
     private final ImageryAdjustAction adjustAction = new ImageryAdjustAction(this);
+    // prepared to be moved to the painter
+    private TileCoordinateConverter coordinateConverter;
 
     /**
@@ -224,4 +231,5 @@
 
     protected void initTileSource(T tileSource) {
+        coordinateConverter = new TileCoordinateConverter(Main.map.mapView, getDisplaySettings());
         attribution.initialize(tileSource);
 
@@ -421,5 +429,5 @@
                 ExtendedDialog ed = new ExtendedDialog(Main.parent, tr("Tile Info"), new String[]{tr("OK")});
                 JPanel panel = new JPanel(new GridBagLayout());
-                Rectangle displaySize = tileToRect(clickedTile);
+                Rectangle2D displaySize = coordinateConverter.getRectangleForTile(clickedTile);
                 String url = "";
                 try {
@@ -434,5 +442,7 @@
                         {"Tile url", url},
                         {"Tile size", getSizeString(clickedTile.getTileSource().getTileSize()) },
-                        {"Tile display size", new StringBuilder().append(displaySize.width).append('x').append(displaySize.height).toString()},
+                        {"Tile display size", new StringBuilder().append(displaySize.getWidth())
+                                                                 .append('x')
+                                                                 .append(displaySize.getHeight()).toString()},
                 };
 
@@ -1000,7 +1010,7 @@
     private TileSet getVisibleTileSet() {
         MapView mv = Main.map.mapView;
-        EastNorth topLeft = mv.getEastNorth(0, 0);
-        EastNorth botRight = mv.getEastNorth(mv.getWidth(), mv.getHeight());
-        return new MapWrappingTileSet(topLeft, botRight, currentZoomLevel);
+        MapViewRectangle area = mv.getState().getViewArea();
+        ProjectionBounds bounds = area.getProjectionBounds();
+        return getTileSet(bounds.getMin(), bounds.getMax(), currentZoomLevel);
     }
 
@@ -1057,16 +1067,4 @@
     }
 
-    private Rectangle tileToRect(Tile t1) {
-        /*
-         * We need to get a box in which to draw, so advance by one tile in
-         * each direction to find the other corner of the box.
-         * Note: this somewhat pollutes the tile cache
-         */
-        Tile t2 = tempCornerTile(t1);
-        Rectangle rect = new Rectangle(pixelPos(t1));
-        rect.add(pixelPos(t2));
-        return rect;
-    }
-
     // 'source' is the pixel coordinates for the area that
     // the img is capable of filling in.  However, we probably
@@ -1075,11 +1073,11 @@
     // 'border' is the screen cordinates that need to be drawn.
     //  We must not draw outside of it.
-    private void drawImageInside(Graphics g, Image sourceImg, Rectangle source, Rectangle border) {
-        Rectangle target = source;
+    private void drawImageInside(Graphics g, Image sourceImg, Rectangle2D source, Rectangle2D border) {
+        Rectangle2D target = source;
 
         // If a border is specified, only draw the intersection
         // if what we have combined with what we are supposed to draw.
         if (border != null) {
-            target = source.intersection(border);
+            target = source.createIntersection(border);
             if (Main.isDebugEnabled()) {
                 Main.debug("source: " + source + "\nborder: " + border + "\nintersection: " + target);
@@ -1098,6 +1096,6 @@
 
         // How many pixels into the 'source' rectangle are we drawing?
-        int screenXoffset = target.x - source.x;
-        int screenYoffset = target.y - source.y;
+        double screenXoffset = target.getX() - source.getX();
+        double screenYoffset = target.getY() - source.getY();
         // And how many pixels into the image itself does that correlate to?
         int imgXoffset = (int) (screenXoffset * imageXScaling + 0.5);
@@ -1112,6 +1110,6 @@
         }
         g.drawImage(sourceImg,
-                target.x, target.y,
-                target.x + target.width, target.y + target.height,
+                (int) target.getX(), (int) target.getY(),
+                (int) target.getMaxX(), (int) target.getMaxY(),
                 imgXoffset, imgYoffset,
                 imgXend, imgYend,
@@ -1120,6 +1118,5 @@
             // dimm by painting opaque rect...
             g.setColor(getFadeColorWithAlpha());
-            g.fillRect(target.x, target.y,
-                    target.width, target.height);
+            ((Graphics2D) g).fill(target);
         }
     }
@@ -1136,7 +1133,7 @@
     private List<Tile> paintTileImages(Graphics g, TileSet ts, int zoom, Tile border) {
         if (zoom <= 0) return Collections.emptyList();
-        Rectangle borderRect = null;
+        Rectangle2D borderRect = null;
         if (border != null) {
-            borderRect = tileToRect(border);
+            borderRect = coordinateConverter.getRectangleForTile(border);
         }
         List<Tile> missedTiles = new LinkedList<>();
@@ -1158,5 +1155,5 @@
             img = applyImageProcessors((BufferedImage) img);
 
-            Rectangle sourceRect = tileToRect(tile);
+            Rectangle2D sourceRect = coordinateConverter.getRectangleForTile(tile);
             if (borderRect != null && !sourceRect.intersects(borderRect)) {
                 continue;
@@ -1195,9 +1192,12 @@
 
     private void paintTileText(TileSet ts, Tile tile, Graphics g, MapView mv, int zoom, Tile t) {
+        if (tile == null) {
+            return;
+        }
+        Point2D p = coordinateConverter.getPixelForTile(t);
         int fontHeight = g.getFontMetrics().getHeight();
-        if (tile == null)
-            return;
-        Point p = pixelPos(t);
-        int texty = p.y + 2 + fontHeight;
+        int x = (int) p.getX();
+        int y = (int) p.getY();
+        int texty = y + 2 + fontHeight;
 
         /*if (PROP_DRAW_DEBUG.get()) {
@@ -1217,5 +1217,5 @@
 
         if (tile.hasError() && getDisplaySettings().isShowErrors()) {
-            myDrawString(g, tr("Error") + ": " + tr(tile.getErrorMessage()), p.x + 2, texty);
+            myDrawString(g, tr("Error") + ": " + tr(tile.getErrorMessage()), x + 2, texty);
             //texty += 1 + fontHeight;
         }
@@ -1226,7 +1226,7 @@
             if (yCursor < t.getYtile()) {
                 if (t.getYtile() % 32 == 31) {
-                    g.fillRect(0, p.y - 1, mv.getWidth(), 3);
+                    g.fillRect(0, y - 1, mv.getWidth(), 3);
                 } else {
-                    g.drawLine(0, p.y, mv.getWidth(), p.y);
+                    g.drawLine(0, y, mv.getWidth(), y);
                 }
                 //yCursor = t.getYtile();
@@ -1236,20 +1236,11 @@
                 if (t.getXtile() % 32 == 0) {
                     // level 7 tile boundary
-                    g.fillRect(p.x - 1, 0, 3, mv.getHeight());
+                    g.fillRect(x - 1, 0, 3, mv.getHeight());
                 } else {
-                    g.drawLine(p.x, 0, p.x, mv.getHeight());
+                    g.drawLine(x, 0, x, mv.getHeight());
                 }
                 //xCursor = t.getXtile();
             }
         }
-    }
-
-    private Point pixelPos(LatLon ll) {
-        return Main.map.mapView.getPoint(Main.getProjection().latlon2eastNorth(ll).add(getDx(), getDy()));
-    }
-
-    private Point pixelPos(Tile t) {
-        ICoordinate coord = tileSource.tileXYToLatLon(t);
-        return pixelPos(new LatLon(coord));
     }
 
@@ -1269,57 +1260,38 @@
     private final TileSet nullTileSet = new TileSet();
 
-    private final class MapWrappingTileSet extends TileSet {
-        private MapWrappingTileSet(EastNorth topLeft, EastNorth botRight, int zoom) {
-            this(getShiftedLatLon(topLeft), getShiftedLatLon(botRight), zoom);
-        }
-
-        private MapWrappingTileSet(LatLon topLeft, LatLon botRight, int zoom) {
-            super(topLeft, botRight, zoom);
-            double centerLon = getShiftedLatLon(Main.map.mapView.getCenter()).lon();
-
-            if (topLeft.lon() > centerLon) {
-                x0 = tileSource.getTileXMin(zoom);
-            }
-            if (botRight.lon() < centerLon) {
-                x1 = tileSource.getTileXMax(zoom);
-            }
+    /**
+     * This is a rectangular range of tiles.
+     */
+    private static class TileRange {
+        int minX, maxX, minY, maxY;
+        int zoom;
+
+        private TileRange() {
+        }
+
+        protected TileRange(TileXY t1, TileXY t2, int zoom) {
+            minX = (int) Math.floor(Math.min(t1.getX(), t2.getX()));
+            minY = (int) Math.floor(Math.min(t1.getY(), t2.getY()));
+            maxX = (int) Math.ceil(Math.max(t1.getX(), t2.getX()));
+            maxY = (int) Math.ceil(Math.max(t1.getY(), t2.getY()));
+            this.zoom = zoom;
+        }
+
+        protected double tilesSpanned() {
+            return Math.sqrt(1.0 * this.size());
+        }
+
+        protected int size() {
+            int xSpan = maxX - minX + 1;
+            int ySpan = maxY - minY + 1;
+            return xSpan * ySpan;
+        }
+    }
+
+    private class TileSet extends TileRange {
+
+        protected TileSet(TileXY t1, TileXY t2, int zoom) {
+            super(t1, t2, zoom);
             sanitize();
-        }
-    }
-
-    private class TileSet {
-        int x0, x1, y0, y1;
-        int zoom;
-
-        /**
-         * Create a TileSet by EastNorth bbox taking a layer shift in account
-         * @param topLeft top-left lat/lon
-         * @param botRight bottom-right lat/lon
-         * @param zoom zoom level
-         */
-        protected TileSet(EastNorth topLeft, EastNorth botRight, int zoom) {
-            this(getShiftedLatLon(topLeft), getShiftedLatLon(botRight), zoom);
-        }
-
-        /**
-         * Create a TileSet by known LatLon bbox without layer shift correction
-         * @param topLeft top-left lat/lon
-         * @param botRight bottom-right lat/lon
-         * @param zoom zoom level
-         */
-        protected TileSet(LatLon topLeft, LatLon botRight, int zoom) {
-            this.zoom = zoom;
-            if (zoom == 0)
-                return;
-
-            TileXY t1 = tileSource.latLonToTileXY(topLeft.toCoordinate(), zoom);
-            TileXY t2 = tileSource.latLonToTileXY(botRight.toCoordinate(), zoom);
-
-            x0 = (int) Math.floor(t1.getX());
-            y0 = (int) Math.floor(t1.getY());
-            x1 = (int) Math.ceil(t2.getX());
-            y1 = (int) Math.ceil(t2.getY());
-            sanitize();
-
         }
 
@@ -1332,26 +1304,15 @@
 
         protected void sanitize() {
-            if (x0 > x1) {
-                int tmp = x0;
-                x0 = x1;
-                x1 = tmp;
-            }
-            if (y0 > y1) {
-                int tmp = y0;
-                y0 = y1;
-                y1 = tmp;
-            }
-
-            if (x0 < tileSource.getTileXMin(zoom)) {
-                x0 = tileSource.getTileXMin(zoom);
-            }
-            if (y0 < tileSource.getTileYMin(zoom)) {
-                y0 = tileSource.getTileYMin(zoom);
-            }
-            if (x1 > tileSource.getTileXMax(zoom)) {
-                x1 = tileSource.getTileXMax(zoom);
-            }
-            if (y1 > tileSource.getTileYMax(zoom)) {
-                y1 = tileSource.getTileYMax(zoom);
+            if (minX < tileSource.getTileXMin(zoom)) {
+                minX = tileSource.getTileXMin(zoom);
+            }
+            if (minY < tileSource.getTileYMin(zoom)) {
+                minY = tileSource.getTileYMin(zoom);
+            }
+            if (maxX > tileSource.getTileXMax(zoom)) {
+                maxX = tileSource.getTileXMax(zoom);
+            }
+            if (maxY > tileSource.getTileYMax(zoom)) {
+                maxY = tileSource.getTileYMax(zoom);
             }
         }
@@ -1367,14 +1328,4 @@
         private boolean insane() {
             return tileCache == null || size() > tileCache.getCacheSize();
-        }
-
-        private double tilesSpanned() {
-            return Math.sqrt(1.0 * this.size());
-        }
-
-        private int size() {
-            int xSpan = x1 - x0 + 1;
-            int ySpan = y1 - y0 + 1;
-            return xSpan * ySpan;
         }
 
@@ -1396,6 +1347,6 @@
                 return Collections.emptyList();
             List<Tile> ret = new ArrayList<>();
-            for (int x = x0; x <= x1; x++) {
-                for (int y = y0; y <= y1; y++) {
+            for (int x = minX; x <= maxX; x++) {
+                for (int y = minY; y <= maxY; y++) {
                     Tile t;
                     if (create) {
@@ -1425,6 +1376,6 @@
          */
         private Comparator<Tile> getTileDistanceComparator() {
-            final int centerX = (int) Math.ceil((x0 + x1) / 2d);
-            final int centerY = (int) Math.ceil((y0 + y1) / 2d);
+            final int centerX = (int) Math.ceil((minX + maxX) / 2d);
+            final int centerY = (int) Math.ceil((minY + maxY) / 2d);
             return new Comparator<Tile>() {
                 private int getDistance(Tile t) {
@@ -1463,6 +1414,35 @@
         @Override
         public String toString() {
-            return getClass().getName() + ": zoom: " + zoom + " X(" + x0 + ", " + x1 + ") Y(" + y0 + ", " + y1 + ") size: " + size();
-        }
+            return getClass().getName() + ": zoom: " + zoom + " X(" + minX + ", " + maxX + ") Y(" + minY + ", " + maxY + ") size: " + size();
+        }
+    }
+
+    /**
+     * Create a TileSet by EastNorth bbox taking a layer shift in account
+     * @param topLeft top-left lat/lon
+     * @param botRight bottom-right lat/lon
+     * @param zoom zoom level
+     * @return the tile set
+     * @since 10651
+     */
+    protected TileSet getTileSet(EastNorth topLeft, EastNorth botRight, int zoom) {
+        return getTileSet(getShiftedLatLon(topLeft), getShiftedLatLon(botRight), zoom);
+    }
+
+    /**
+     * Create a TileSet by known LatLon bbox without layer shift correction
+     * @param topLeft top-left lat/lon
+     * @param botRight bottom-right lat/lon
+     * @param zoom zoom level
+     * @return the tile set
+     * @since 10651
+     */
+    protected TileSet getTileSet(LatLon topLeft, LatLon botRight, int zoom) {
+        if (zoom == 0)
+            return new TileSet();
+
+        TileXY t1 = tileSource.latLonToTileXY(topLeft.toCoordinate(), zoom);
+        TileXY t2 = tileSource.latLonToTileXY(botRight.toCoordinate(), zoom);
+        return new TileSet(t1, t2, zoom);
     }
 
@@ -1494,5 +1474,5 @@
 
     private class DeepTileSet {
-        private final EastNorth topLeft, botRight;
+        private final ProjectionBounds bounds;
         private final int minZoom, maxZoom;
         private final TileSet[] tileSets;
@@ -1500,7 +1480,6 @@
 
         @SuppressWarnings("unchecked")
-        DeepTileSet(EastNorth topLeft, EastNorth botRight, int minZoom, int maxZoom) {
-            this.topLeft = topLeft;
-            this.botRight = botRight;
+        DeepTileSet(ProjectionBounds bounds, int minZoom, int maxZoom) {
+            this.bounds = bounds;
             this.minZoom = minZoom;
             this.maxZoom = maxZoom;
@@ -1515,5 +1494,5 @@
                 TileSet ts = tileSets[zoom-minZoom];
                 if (ts == null) {
-                    ts = new MapWrappingTileSet(topLeft, botRight, zoom);
+                    ts = AbstractTileSourceLayer.this.getTileSet(bounds.getMin(), bounds.getMax(), zoom);
                     tileSets[zoom-minZoom] = ts;
                 }
@@ -1538,12 +1517,5 @@
     @Override
     public void paint(Graphics2D g, MapView mv, Bounds bounds) {
-        EastNorth topLeft = mv.getEastNorth(0, 0);
-        EastNorth botRight = mv.getEastNorth(mv.getWidth(), mv.getHeight());
-
-        if (botRight.east() == 0 || botRight.north() == 0) {
-            /*Main.debug("still initializing??");*/
-            // probably still initializing
-            return;
-        }
+        ProjectionBounds pb = mv.getState().getViewArea().getProjectionBounds();
 
         needRedraw = false;
@@ -1554,5 +1526,5 @@
         }
 
-        DeepTileSet dts = new DeepTileSet(topLeft, botRight, getMinZoomLvl(), zoom);
+        DeepTileSet dts = new DeepTileSet(pb, getMinZoomLvl(), zoom);
         TileSet ts = dts.getTileSet(zoom);
 
@@ -1633,5 +1605,5 @@
                 }
                 Tile t2 = tempCornerTile(missed);
-                TileSet ts2 = new TileSet(
+                TileSet ts2 = getTileSet(
                         getShiftedLatLon(tileSource.tileXYToLatLon(missed)),
                         getShiftedLatLon(tileSource.tileXYToLatLon(t2)),
@@ -1661,5 +1633,7 @@
         }
 
-        attribution.paintAttribution(g, mv.getWidth(), mv.getHeight(), getShiftedCoord(topLeft), getShiftedCoord(botRight),
+        EastNorth min = pb.getMin();
+        EastNorth max = pb.getMax();
+        attribution.paintAttribution(g, mv.getWidth(), mv.getHeight(), getShiftedCoord(min), getShiftedCoord(max),
                 displayZoomLevel, this);
 
@@ -1711,30 +1685,16 @@
         EastNorth botRight = mv.getEastNorth(mv.getWidth(), mv.getHeight());
         int z = currentZoomLevel;
-        TileSet ts = new TileSet(topLeft, botRight, z);
+        TileSet ts = getTileSet(topLeft, botRight, z);
 
         if (!ts.tooLarge()) {
             ts.loadAllTiles(false); // make sure there are tile objects for all tiles
         }
-        Tile clickedTile = null;
-        for (Tile t1 : ts.allExistingTiles()) {
-            Tile t2 = tempCornerTile(t1);
-            Rectangle r = new Rectangle(pixelPos(t1));
-            r.add(pixelPos(t2));
-            if (Main.isDebugEnabled()) {
-                Main.debug("r: " + r + " clicked: " + clicked);
-            }
-            if (!r.contains(clicked)) {
-                continue;
-            }
-            clickedTile = t1;
-            break;
-        }
-        if (clickedTile == null)
-            return null;
+        Stream<Tile> clickedTiles = ts.allExistingTiles().stream()
+                .filter(t -> coordinateConverter.getRectangleForTile(t).contains(clicked));
         if (Main.isTraceEnabled()) {
-            Main.trace("Clicked on tile: " + clickedTile.getXtile() + ' ' + clickedTile.getYtile() +
-                " currentZoomLevel: " + currentZoomLevel);
-        }
-        return clickedTile;
+            clickedTiles = clickedTiles.peek(t -> Main.trace("Clicked on tile: " + t.getXtile() + ' ' + t.getYtile() +
+                    " currentZoomLevel: " + currentZoomLevel));
+        }
+        return clickedTiles.findAny().orElse(null);
     }
 
Index: trunk/src/org/openstreetmap/josm/gui/layer/imagery/TileCoordinateConverter.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/layer/imagery/TileCoordinateConverter.java	(revision 10651)
+++ trunk/src/org/openstreetmap/josm/gui/layer/imagery/TileCoordinateConverter.java	(revision 10651)
@@ -0,0 +1,57 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.layer.imagery;
+
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+
+import org.openstreetmap.gui.jmapviewer.Tile;
+import org.openstreetmap.gui.jmapviewer.interfaces.ICoordinate;
+import org.openstreetmap.josm.data.coor.LatLon;
+import org.openstreetmap.josm.gui.MapView;
+import org.openstreetmap.josm.gui.MapViewState.MapViewPoint;
+
+/**
+ * This class handles tile coordinate management and computes their position in the map view.
+ * @author Michael Zangl
+ * @since 10651
+ */
+public class TileCoordinateConverter {
+    private MapView mapView;
+    private TileSourceDisplaySettings settings;
+
+    /**
+     * Create a new coordinate converter for the map view.
+     * @param mapView The map view.
+     * @param settings displacement settings.
+     */
+    public TileCoordinateConverter(MapView mapView, TileSourceDisplaySettings settings) {
+        this.mapView = mapView;
+        this.settings = settings;
+    }
+
+    private MapViewPoint pos(ICoordinate ll) {
+        return mapView.getState().getPointFor(new LatLon(ll)).add(settings.getDisplacement());
+    }
+
+    /**
+     * Gets the top left position of the tile inside the map view.
+     * @param tile The tile
+     * @return The positon.
+     */
+    public Point2D getPixelForTile(Tile tile) {
+        ICoordinate coord = tile.getTileSource().tileXYToLatLon(tile);
+        return pos(coord).getInView();
+    }
+
+    /**
+     * Gets the position of the tile inside the map view.
+     * @param tile The tile
+     * @return The positon.
+     */
+    public Rectangle2D getRectangleForTile(Tile tile) {
+        ICoordinate c1 = tile.getTileSource().tileXYToLatLon(tile);
+        ICoordinate c2 = tile.getTileSource().tileXYToLatLon(tile.getXtile() + 1, tile.getYtile() + 1, tile.getZoom());
+
+        return pos(c1).rectTo(pos(c2)).getInView();
+    }
+}
