diff --git a/src/org/openstreetmap/josm/gui/MapViewState.java b/src/org/openstreetmap/josm/gui/MapViewState.java
new file mode 100644
index 0000000..6397120
--- /dev/null
+++ b/src/org/openstreetmap/josm/gui/MapViewState.java
@@ -0,0 +1,276 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui;
+
+import java.awt.Point;
+import java.awt.geom.Point2D;
+import java.awt.geom.Point2D.Double;
+
+import javax.swing.SwingUtilities;
+
+import org.openstreetmap.josm.Main;
+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;
+import org.openstreetmap.josm.data.projection.Projection;
+
+/**
+ * This class represents a state of the {@link MapView}.
+ * @author Michael Zangl
+ * @since xxx
+ */
+public class MapViewState {
+
+    private final Projection projection = Main.getProjection();
+
+    private final int viewWidth;
+    private final int viewHeight;
+
+    private final double scale;
+
+    /**
+     * Top left {@link EastNorth} coordinate of the view.
+     */
+    private final EastNorth topLeft;
+
+    private final NavigatableComponent navigatableComponent;
+
+    /**
+     * Create a new {@link MapViewState} object for the given map view.
+     * @param navigatableComponent The view.
+     */
+    public MapViewState(NavigatableComponent navigatableComponent) {
+        this.navigatableComponent = navigatableComponent;
+        viewWidth = navigatableComponent.getWidth();
+        viewHeight = navigatableComponent.getHeight();
+
+        scale = navigatableComponent.getScale();
+        EastNorth center = navigatableComponent.getCenter();
+        topLeft = new EastNorth(center.east() - viewWidth / 2.0 * scale, center.north() + viewHeight / 2.0 * scale);
+    }
+
+    /**
+     * Gets the MapViewPoint representation for a position in view coordinates.
+     * @param x The x coordinate inside the view.
+     * @param y The y coordinate inside the view.
+     * @return The MapViewPoint.
+     */
+    public MapViewPoint getForView(double x, double y) {
+        return new MapViewViewPoint(x, y);
+    }
+
+    /**
+     * Gets the {@link MapViewPoint} for the given {@link EastNorth} coordinate.
+     * @param eastNorth the position.
+     * @return The point for that position.
+     */
+    public MapViewPoint getPointFor(EastNorth eastNorth) {
+        return new MapViewEastNorthPoint(eastNorth);
+    }
+
+    /**
+     * Gets a rectangle representing the whole view area.
+     * @return The rectangle.
+     */
+    public MapViewRectangle getViewArea() {
+        return getForView(0, 0).rectTo(getForView(viewWidth, viewHeight));
+    }
+
+    /**
+     * Gets the center of the view.
+     * @return The center position.
+     */
+    public MapViewPoint getCenter() {
+        return getForView(viewWidth / 2.0, viewHeight / 2.0);
+    }
+
+    /**
+     * Gets the width of the view on the Screen;
+     * @return The width of the view component in screen pixel.
+     */
+    public double getViewWidth() {
+        return viewWidth;
+    }
+
+    /**
+     * Gets the height of the view on the Screen;
+     * @return The height of the view component in screen pixel.
+     */
+    public double getViewHeight() {
+        return viewHeight;
+    }
+
+    /**
+     * Gets the current projection used for the MapView.
+     * @return The projection.
+     */
+    public Projection getProjection() {
+        return projection;
+    }
+
+    /**
+     * A class representing a point in the map view. It allws to convert between the different coordinate systems.
+     * @author Michael Zangl
+     * @since xxx
+     */
+    public abstract class MapViewPoint {
+
+        /**
+         * Get this point in view coordinates.
+         * @return The point in view coordinates.
+         */
+        public Point2D getInView() {
+            return new Point2D.Double(getInViewX(), getInViewY());
+        }
+
+        protected abstract double getInViewX();
+
+        protected abstract double getInViewY();
+
+        /**
+         * Convert this point to window coordinates.
+         * @return The point in window coordinates.
+         */
+        public Point2D getInWindow() {
+            Point corner = SwingUtilities.convertPoint(navigatableComponent, new Point(0, 0), null);
+            return getUsingCorner(corner);
+        }
+
+        /**
+         * Convert this point to screen coordinates.
+         * @return The point in screen coordinates.
+         */
+        public Point2D getOnScreen() {
+            Point corner = new Point(0, 0);
+            SwingUtilities.convertPointToScreen(corner, navigatableComponent);
+            return getUsingCorner(corner);
+        }
+
+        private Double getUsingCorner(Point corner) {
+            return new Point2D.Double(corner.getX() + getInViewX(), corner.getY() + getInViewY());
+        }
+
+        /**
+         * Gets the {@link EastNorth} coordinate of this point.
+         * @return The east/north coordinate.
+         */
+        public EastNorth getEastNorth() {
+            return new EastNorth(topLeft.east() + getInViewX() * scale, topLeft.north() - getInViewY() * scale);
+        }
+
+        /**
+         * Create a rectangle from this to the other point.
+         * @param other The other point. Needs to be of the same {@link MapViewState}
+         * @return A rectangle.
+         */
+        public MapViewRectangle rectTo(MapViewPoint other) {
+            return new MapViewRectangle(this, other);
+        }
+
+        /**
+         * Gets the current position in LatLon coordinates according to the current projection.
+         * @return The positon as LatLon.
+         */
+        public LatLon getLatLon() {
+            return projection.eastNorth2latlon(getEastNorth());
+        }
+
+    }
+
+    private class MapViewViewPoint extends MapViewPoint {
+        private final double x;
+        private final double y;
+
+        MapViewViewPoint(double x, double y) {
+            this.x = x;
+            this.y = y;
+        }
+
+        @Override
+        protected double getInViewX() {
+            return x;
+        }
+
+        @Override
+        protected double getInViewY() {
+            return y;
+        }
+
+        @Override
+        public String toString() {
+            return "MapViewViewPoint [x=" + x + ", y=" + y + "]";
+        }
+    }
+
+    private class MapViewEastNorthPoint extends MapViewPoint {
+
+        private final EastNorth eastNorth;
+
+        MapViewEastNorthPoint(EastNorth eastNorth) {
+            this.eastNorth = eastNorth;
+        }
+
+        @Override
+        protected double getInViewX() {
+            return (eastNorth.east() - topLeft.east()) / scale;
+        }
+
+        @Override
+        protected double getInViewY() {
+            return (topLeft.north() - eastNorth.north()) / scale;
+        }
+
+        @Override
+        public EastNorth getEastNorth() {
+            return eastNorth;
+        }
+
+        @Override
+        public String toString() {
+            return "MapViewEastNorthPoint [eastNorth=" + eastNorth + "]";
+        }
+
+    }
+
+    /**
+     * A rectangle on the MapView. It is rectangular in screen / EastNorth space.
+     * @author Michael Zangl
+     * @since xxx
+     */
+    public class MapViewRectangle {
+        private final MapViewPoint p1;
+        private final MapViewPoint p2;
+
+        /**
+         * Create a new MapViewRectangle
+         * @param p1 The first point to use
+         * @param p2 The second point to use.
+         */
+        MapViewRectangle(MapViewPoint p1, MapViewPoint p2) {
+            super();
+            this.p1 = p1;
+            this.p2 = p2;
+        }
+
+        /**
+         * Gets the projection bounds for this rectangle.
+         * @return The projection bounds.
+         */
+        public ProjectionBounds getProjectionBounds() {
+            ProjectionBounds b = new ProjectionBounds(p1.getEastNorth());
+            b.extend(p2.getEastNorth());
+            return b;
+        }
+
+        /**
+         * Gets a rough estimate of the bounds by assuming lat/lon are parallel to x/y.
+         * @return The bounds computed by converting the corners of this rectangle.
+         */
+        public Bounds getCornerBounds() {
+            Bounds b = new Bounds(p1.getLatLon());
+            b.extend(p2.getLatLon());
+            return b;
+        }
+    }
+
+}
diff --git a/src/org/openstreetmap/josm/gui/NavigatableComponent.java b/src/org/openstreetmap/josm/gui/NavigatableComponent.java
index 9334562..ecf66b4 100644
--- a/src/org/openstreetmap/josm/gui/NavigatableComponent.java
+++ b/src/org/openstreetmap/josm/gui/NavigatableComponent.java
@@ -49,6 +49,7 @@ import org.openstreetmap.josm.data.preferences.DoubleProperty;
 import org.openstreetmap.josm.data.preferences.IntegerProperty;
 import org.openstreetmap.josm.data.projection.Projection;
 import org.openstreetmap.josm.data.projection.Projections;
+import org.openstreetmap.josm.gui.MapViewState.MapViewPoint;
 import org.openstreetmap.josm.gui.download.DownloadDialog;
 import org.openstreetmap.josm.gui.help.Helpful;
 import org.openstreetmap.josm.gui.layer.NativeScaleLayer;
@@ -393,13 +394,7 @@ public class NavigatableComponent extends JComponent implements Helpful {
     }
 
     public ProjectionBounds getProjectionBounds() {
-        return new ProjectionBounds(
-                new EastNorth(
-                        center.east() - getWidth()/2.0*scale,
-                        center.north() - getHeight()/2.0*scale),
-                        new EastNorth(
-                                center.east() + getWidth()/2.0*scale,
-                                center.north() + getHeight()/2.0*scale));
+        return new MapViewState(this).getViewArea().getProjectionBounds();
     }
 
     /* FIXME: replace with better method - used by MapSlider */
@@ -411,13 +406,7 @@ public class NavigatableComponent extends JComponent implements Helpful {
 
     /* FIXME: replace with better method - used by Main to reset Bounds when projection changes, don't use otherwise */
     public Bounds getRealBounds() {
-        return new Bounds(
-                getProjection().eastNorth2latlon(new EastNorth(
-                        center.east() - getWidth()/2.0*scale,
-                        center.north() - getHeight()/2.0*scale)),
-                        getProjection().eastNorth2latlon(new EastNorth(
-                                center.east() + getWidth()/2.0*scale,
-                                center.north() + getHeight()/2.0*scale)));
+        return new MapViewState(this).getViewArea().getCornerBounds();
     }
 
     /**
@@ -436,11 +425,10 @@ public class NavigatableComponent extends JComponent implements Helpful {
     }
 
     public ProjectionBounds getProjectionBounds(Rectangle r) {
-        EastNorth p1 = getEastNorth(r.x, r.y);
-        EastNorth p2 = getEastNorth(r.x + r.width, r.y + r.height);
-        ProjectionBounds pb = new ProjectionBounds(p1);
-        pb.extend(p2);
-        return pb;
+        MapViewState state = new MapViewState(this);
+        MapViewPoint p1 = state.getForView(r.getMinX(), r.getMinY());
+        MapViewPoint p2 = state.getForView(r.getMaxX(), r.getMaxY());
+        return p1.rectTo(p2).getProjectionBounds();
     }
 
     /**
diff --git a/test/unit/org/openstreetmap/josm/gui/MapViewStateTest.java b/test/unit/org/openstreetmap/josm/gui/MapViewStateTest.java
new file mode 100644
index 0000000..021a2b5
--- /dev/null
+++ b/test/unit/org/openstreetmap/josm/gui/MapViewStateTest.java
@@ -0,0 +1,131 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui;
+
+import static org.junit.Assert.assertEquals;
+
+import java.awt.Rectangle;
+
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.openstreetmap.josm.JOSMFixture;
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.data.coor.EastNorth;
+import org.openstreetmap.josm.data.coor.LatLon;
+import org.openstreetmap.josm.gui.MapViewState.MapViewPoint;
+import org.openstreetmap.josm.gui.util.GuiHelper;
+
+/**
+ * Test {@link MapViewState}
+ * @author Michael Zangl
+ * @since xxx
+ */
+public class MapViewStateTest {
+
+    private static final int HEIGHT = 200;
+    private static final int WIDTH = 300;
+    private NavigatableComponent component;
+    private MapViewState state;
+
+    /**
+     * Setup test.
+     */
+    @BeforeClass
+    public static void setUpBeforeClass() {
+        JOSMFixture.createUnitTestFixture().init();
+    }
+
+    /**
+     * Create a new, fresh {@link NavigatableComponent}
+     */
+    @Before
+    public void setUp() {
+        component = new NavigatableComponent();
+        component.setBounds(new Rectangle(WIDTH, HEIGHT));
+        // wait for the event to be propagated.
+        GuiHelper.runInEDTAndWait(new Runnable() {
+            @Override
+            public void run() {
+            }
+        });
+        state = new MapViewState(component);
+    }
+
+    /**
+     * Test {@link MapViewState#getCenter()} returns map view center.
+     */
+    @Test
+    public void testGetCenter() {
+        MapViewPoint center = state.getCenter();
+        assertHasViewCoords(WIDTH / 2, HEIGHT / 2, center);
+
+        component.zoomTo(new LatLon(3, 4));
+
+        // state should not change, but new state should.
+        center = state.getCenter();
+        assertHasViewCoords(WIDTH / 2, HEIGHT / 2, center);
+
+        center = new MapViewState(component).getCenter();
+        assertEquals("x", 3, center.getLatLon().lat(), 0.01);
+        assertEquals("y", 4, center.getLatLon().lon(), 0.01);
+    }
+
+    private void assertHasViewCoords(double x, double y, MapViewPoint center) {
+        assertEquals("x", x, center.getInViewX(), 0.01);
+        assertEquals("y", y, center.getInViewY(), 0.01);
+        assertEquals("x", x, center.getInView().getX(), 0.01);
+        assertEquals("y", y, center.getInView().getY(), 0.01);
+    }
+
+    /**
+     * Test {@link MapViewState#getForView(double, double)}
+     */
+    @Test
+    public void testGetForView() {
+        MapViewPoint corner = state.getForView(0, 0);
+        assertHasViewCoords(0, 0, corner);
+
+        MapViewPoint middle = state.getForView(120, 130);
+        assertHasViewCoords(120, 130, middle);
+
+        MapViewPoint fraction = state.getForView(0.12, 0.7);
+        assertHasViewCoords(0.12, 0.7, fraction);
+
+        MapViewPoint negative = state.getForView(-17, -30);
+        assertHasViewCoords(-17, -30, negative);
+    }
+
+    /**
+     * Test {@link MapViewState#getViewWidth()} and {@link MapViewState#getViewHeight()}
+     */
+    @Test
+    public void testGetViewSize() {
+        assertEquals(WIDTH, state.getViewWidth(), 0.01);
+        assertEquals(HEIGHT, state.getViewHeight(), 0.01);
+    }
+
+    /**
+     * Tests that all coordinate conversions for the point work.
+     */
+    @Test
+    public void testPointConversions() {
+        MapViewPoint p = state.getForView(50, 70);
+        assertHasViewCoords(50, 70, p);
+
+        EastNorth eastnorth = p.getEastNorth();
+        EastNorth shouldEastNorth = component.getEastNorth(50, 70);
+        assertEquals("east", shouldEastNorth.east(), eastnorth.east(), 0.01);
+        assertEquals("north", shouldEastNorth.north(), eastnorth.north(), 0.01);
+        MapViewPoint reversed = state.getPointFor(shouldEastNorth);
+        assertHasViewCoords(50, 70, reversed);
+
+        LatLon latlon = p.getLatLon();
+        LatLon shouldLatLon = Main.getProjection().eastNorth2latlon(shouldEastNorth);
+        assertEquals("lat", shouldLatLon.lat(), latlon.lat(), 0.01);
+        assertEquals("lon", shouldLatLon.lon(), latlon.lon(), 0.01);
+
+        MapViewPoint p2 = state.getPointFor(new EastNorth(2, 3));
+        assertEquals("east", 2, p2.getEastNorth().east(), 0.01);
+        assertEquals("north", 3, p2.getEastNorth().north(), 0.01);
+    }
+}
