Ticket #12934: patch-mapview-add-state-class.patch

File patch-mapview-add-state-class.patch, 15.8 KB (added by michael2402, 10 years ago)
  • new file src/org/openstreetmap/josm/gui/MapViewState.java

    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
    - +  
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.gui;
     3
     4import java.awt.Point;
     5import java.awt.geom.Point2D;
     6import java.awt.geom.Point2D.Double;
     7
     8import javax.swing.SwingUtilities;
     9
     10import org.openstreetmap.josm.Main;
     11import org.openstreetmap.josm.data.Bounds;
     12import org.openstreetmap.josm.data.ProjectionBounds;
     13import org.openstreetmap.josm.data.coor.EastNorth;
     14import org.openstreetmap.josm.data.coor.LatLon;
     15import org.openstreetmap.josm.data.projection.Projection;
     16
     17/**
     18 * This class represents a state of the {@link MapView}.
     19 * @author Michael Zangl
     20 * @since xxx
     21 */
     22public class MapViewState {
     23
     24    private final Projection projection = Main.getProjection();
     25
     26    private final int viewWidth;
     27    private final int viewHeight;
     28
     29    private final double scale;
     30
     31    /**
     32     * Top left {@link EastNorth} coordinate of the view.
     33     */
     34    private final EastNorth topLeft;
     35
     36    private final NavigatableComponent navigatableComponent;
     37
     38    /**
     39     * Create a new {@link MapViewState} object for the given map view.
     40     * @param navigatableComponent The view.
     41     */
     42    public MapViewState(NavigatableComponent navigatableComponent) {
     43        this.navigatableComponent = navigatableComponent;
     44        viewWidth = navigatableComponent.getWidth();
     45        viewHeight = navigatableComponent.getHeight();
     46
     47        scale = navigatableComponent.getScale();
     48        EastNorth center = navigatableComponent.getCenter();
     49        topLeft = new EastNorth(center.east() - viewWidth / 2.0 * scale, center.north() + viewHeight / 2.0 * scale);
     50    }
     51
     52    /**
     53     * Gets the MapViewPoint representation for a position in view coordinates.
     54     * @param x The x coordinate inside the view.
     55     * @param y The y coordinate inside the view.
     56     * @return The MapViewPoint.
     57     */
     58    public MapViewPoint getForView(double x, double y) {
     59        return new MapViewViewPoint(x, y);
     60    }
     61
     62    /**
     63     * Gets the {@link MapViewPoint} for the given {@link EastNorth} coordinate.
     64     * @param eastNorth the position.
     65     * @return The point for that position.
     66     */
     67    public MapViewPoint getPointFor(EastNorth eastNorth) {
     68        return new MapViewEastNorthPoint(eastNorth);
     69    }
     70
     71    /**
     72     * Gets a rectangle representing the whole view area.
     73     * @return The rectangle.
     74     */
     75    public MapViewRectangle getViewArea() {
     76        return getForView(0, 0).rectTo(getForView(viewWidth, viewHeight));
     77    }
     78
     79    /**
     80     * Gets the center of the view.
     81     * @return The center position.
     82     */
     83    public MapViewPoint getCenter() {
     84        return getForView(viewWidth / 2.0, viewHeight / 2.0);
     85    }
     86
     87    /**
     88     * Gets the width of the view on the Screen;
     89     * @return The width of the view component in screen pixel.
     90     */
     91    public double getViewWidth() {
     92        return viewWidth;
     93    }
     94
     95    /**
     96     * Gets the height of the view on the Screen;
     97     * @return The height of the view component in screen pixel.
     98     */
     99    public double getViewHeight() {
     100        return viewHeight;
     101    }
     102
     103    /**
     104     * Gets the current projection used for the MapView.
     105     * @return The projection.
     106     */
     107    public Projection getProjection() {
     108        return projection;
     109    }
     110
     111    /**
     112     * A class representing a point in the map view. It allws to convert between the different coordinate systems.
     113     * @author Michael Zangl
     114     * @since xxx
     115     */
     116    public abstract class MapViewPoint {
     117
     118        /**
     119         * Get this point in view coordinates.
     120         * @return The point in view coordinates.
     121         */
     122        public Point2D getInView() {
     123            return new Point2D.Double(getInViewX(), getInViewY());
     124        }
     125
     126        protected abstract double getInViewX();
     127
     128        protected abstract double getInViewY();
     129
     130        /**
     131         * Convert this point to window coordinates.
     132         * @return The point in window coordinates.
     133         */
     134        public Point2D getInWindow() {
     135            Point corner = SwingUtilities.convertPoint(navigatableComponent, new Point(0, 0), null);
     136            return getUsingCorner(corner);
     137        }
     138
     139        /**
     140         * Convert this point to screen coordinates.
     141         * @return The point in screen coordinates.
     142         */
     143        public Point2D getOnScreen() {
     144            Point corner = new Point(0, 0);
     145            SwingUtilities.convertPointToScreen(corner, navigatableComponent);
     146            return getUsingCorner(corner);
     147        }
     148
     149        private Double getUsingCorner(Point corner) {
     150            return new Point2D.Double(corner.getX() + getInViewX(), corner.getY() + getInViewY());
     151        }
     152
     153        /**
     154         * Gets the {@link EastNorth} coordinate of this point.
     155         * @return The east/north coordinate.
     156         */
     157        public EastNorth getEastNorth() {
     158            return new EastNorth(topLeft.east() + getInViewX() * scale, topLeft.north() - getInViewY() * scale);
     159        }
     160
     161        /**
     162         * Create a rectangle from this to the other point.
     163         * @param other The other point. Needs to be of the same {@link MapViewState}
     164         * @return A rectangle.
     165         */
     166        public MapViewRectangle rectTo(MapViewPoint other) {
     167            return new MapViewRectangle(this, other);
     168        }
     169
     170        /**
     171         * Gets the current position in LatLon coordinates according to the current projection.
     172         * @return The positon as LatLon.
     173         */
     174        public LatLon getLatLon() {
     175            return projection.eastNorth2latlon(getEastNorth());
     176        }
     177
     178    }
     179
     180    private class MapViewViewPoint extends MapViewPoint {
     181        private final double x;
     182        private final double y;
     183
     184        MapViewViewPoint(double x, double y) {
     185            this.x = x;
     186            this.y = y;
     187        }
     188
     189        @Override
     190        protected double getInViewX() {
     191            return x;
     192        }
     193
     194        @Override
     195        protected double getInViewY() {
     196            return y;
     197        }
     198
     199        @Override
     200        public String toString() {
     201            return "MapViewViewPoint [x=" + x + ", y=" + y + "]";
     202        }
     203    }
     204
     205    private class MapViewEastNorthPoint extends MapViewPoint {
     206
     207        private final EastNorth eastNorth;
     208
     209        MapViewEastNorthPoint(EastNorth eastNorth) {
     210            this.eastNorth = eastNorth;
     211        }
     212
     213        @Override
     214        protected double getInViewX() {
     215            return (eastNorth.east() - topLeft.east()) / scale;
     216        }
     217
     218        @Override
     219        protected double getInViewY() {
     220            return (topLeft.north() - eastNorth.north()) / scale;
     221        }
     222
     223        @Override
     224        public EastNorth getEastNorth() {
     225            return eastNorth;
     226        }
     227
     228        @Override
     229        public String toString() {
     230            return "MapViewEastNorthPoint [eastNorth=" + eastNorth + "]";
     231        }
     232
     233    }
     234
     235    /**
     236     * A rectangle on the MapView. It is rectangular in screen / EastNorth space.
     237     * @author Michael Zangl
     238     * @since xxx
     239     */
     240    public class MapViewRectangle {
     241        private final MapViewPoint p1;
     242        private final MapViewPoint p2;
     243
     244        /**
     245         * Create a new MapViewRectangle
     246         * @param p1 The first point to use
     247         * @param p2 The second point to use.
     248         */
     249        MapViewRectangle(MapViewPoint p1, MapViewPoint p2) {
     250            super();
     251            this.p1 = p1;
     252            this.p2 = p2;
     253        }
     254
     255        /**
     256         * Gets the projection bounds for this rectangle.
     257         * @return The projection bounds.
     258         */
     259        public ProjectionBounds getProjectionBounds() {
     260            ProjectionBounds b = new ProjectionBounds(p1.getEastNorth());
     261            b.extend(p2.getEastNorth());
     262            return b;
     263        }
     264
     265        /**
     266         * Gets a rough estimate of the bounds by assuming lat/lon are parallel to x/y.
     267         * @return The bounds computed by converting the corners of this rectangle.
     268         */
     269        public Bounds getCornerBounds() {
     270            Bounds b = new Bounds(p1.getLatLon());
     271            b.extend(p2.getLatLon());
     272            return b;
     273        }
     274    }
     275
     276}
  • src/org/openstreetmap/josm/gui/NavigatableComponent.java

    diff --git a/src/org/openstreetmap/josm/gui/NavigatableComponent.java b/src/org/openstreetmap/josm/gui/NavigatableComponent.java
    index 9334562..ecf66b4 100644
    a b import org.openstreetmap.josm.data.preferences.DoubleProperty;  
    4949import org.openstreetmap.josm.data.preferences.IntegerProperty;
    5050import org.openstreetmap.josm.data.projection.Projection;
    5151import org.openstreetmap.josm.data.projection.Projections;
     52import org.openstreetmap.josm.gui.MapViewState.MapViewPoint;
    5253import org.openstreetmap.josm.gui.download.DownloadDialog;
    5354import org.openstreetmap.josm.gui.help.Helpful;
    5455import org.openstreetmap.josm.gui.layer.NativeScaleLayer;
    public class NavigatableComponent extends JComponent implements Helpful {  
    393394    }
    394395
    395396    public ProjectionBounds getProjectionBounds() {
    396         return new ProjectionBounds(
    397                 new EastNorth(
    398                         center.east() - getWidth()/2.0*scale,
    399                         center.north() - getHeight()/2.0*scale),
    400                         new EastNorth(
    401                                 center.east() + getWidth()/2.0*scale,
    402                                 center.north() + getHeight()/2.0*scale));
     397        return new MapViewState(this).getViewArea().getProjectionBounds();
    403398    }
    404399
    405400    /* FIXME: replace with better method - used by MapSlider */
    public class NavigatableComponent extends JComponent implements Helpful {  
    411406
    412407    /* FIXME: replace with better method - used by Main to reset Bounds when projection changes, don't use otherwise */
    413408    public Bounds getRealBounds() {
    414         return new Bounds(
    415                 getProjection().eastNorth2latlon(new EastNorth(
    416                         center.east() - getWidth()/2.0*scale,
    417                         center.north() - getHeight()/2.0*scale)),
    418                         getProjection().eastNorth2latlon(new EastNorth(
    419                                 center.east() + getWidth()/2.0*scale,
    420                                 center.north() + getHeight()/2.0*scale)));
     409        return new MapViewState(this).getViewArea().getCornerBounds();
    421410    }
    422411
    423412    /**
    public class NavigatableComponent extends JComponent implements Helpful {  
    436425    }
    437426
    438427    public ProjectionBounds getProjectionBounds(Rectangle r) {
    439         EastNorth p1 = getEastNorth(r.x, r.y);
    440         EastNorth p2 = getEastNorth(r.x + r.width, r.y + r.height);
    441         ProjectionBounds pb = new ProjectionBounds(p1);
    442         pb.extend(p2);
    443         return pb;
     428        MapViewState state = new MapViewState(this);
     429        MapViewPoint p1 = state.getForView(r.getMinX(), r.getMinY());
     430        MapViewPoint p2 = state.getForView(r.getMaxX(), r.getMaxY());
     431        return p1.rectTo(p2).getProjectionBounds();
    444432    }
    445433
    446434    /**
  • new file test/unit/org/openstreetmap/josm/gui/MapViewStateTest.java

    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
    - +  
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.gui;
     3
     4import static org.junit.Assert.assertEquals;
     5
     6import java.awt.Rectangle;
     7
     8import org.junit.Before;
     9import org.junit.BeforeClass;
     10import org.junit.Test;
     11import org.openstreetmap.josm.JOSMFixture;
     12import org.openstreetmap.josm.Main;
     13import org.openstreetmap.josm.data.coor.EastNorth;
     14import org.openstreetmap.josm.data.coor.LatLon;
     15import org.openstreetmap.josm.gui.MapViewState.MapViewPoint;
     16import org.openstreetmap.josm.gui.util.GuiHelper;
     17
     18/**
     19 * Test {@link MapViewState}
     20 * @author Michael Zangl
     21 * @since xxx
     22 */
     23public class MapViewStateTest {
     24
     25    private static final int HEIGHT = 200;
     26    private static final int WIDTH = 300;
     27    private NavigatableComponent component;
     28    private MapViewState state;
     29
     30    /**
     31     * Setup test.
     32     */
     33    @BeforeClass
     34    public static void setUpBeforeClass() {
     35        JOSMFixture.createUnitTestFixture().init();
     36    }
     37
     38    /**
     39     * Create a new, fresh {@link NavigatableComponent}
     40     */
     41    @Before
     42    public void setUp() {
     43        component = new NavigatableComponent();
     44        component.setBounds(new Rectangle(WIDTH, HEIGHT));
     45        // wait for the event to be propagated.
     46        GuiHelper.runInEDTAndWait(new Runnable() {
     47            @Override
     48            public void run() {
     49            }
     50        });
     51        state = new MapViewState(component);
     52    }
     53
     54    /**
     55     * Test {@link MapViewState#getCenter()} returns map view center.
     56     */
     57    @Test
     58    public void testGetCenter() {
     59        MapViewPoint center = state.getCenter();
     60        assertHasViewCoords(WIDTH / 2, HEIGHT / 2, center);
     61
     62        component.zoomTo(new LatLon(3, 4));
     63
     64        // state should not change, but new state should.
     65        center = state.getCenter();
     66        assertHasViewCoords(WIDTH / 2, HEIGHT / 2, center);
     67
     68        center = new MapViewState(component).getCenter();
     69        assertEquals("x", 3, center.getLatLon().lat(), 0.01);
     70        assertEquals("y", 4, center.getLatLon().lon(), 0.01);
     71    }
     72
     73    private void assertHasViewCoords(double x, double y, MapViewPoint center) {
     74        assertEquals("x", x, center.getInViewX(), 0.01);
     75        assertEquals("y", y, center.getInViewY(), 0.01);
     76        assertEquals("x", x, center.getInView().getX(), 0.01);
     77        assertEquals("y", y, center.getInView().getY(), 0.01);
     78    }
     79
     80    /**
     81     * Test {@link MapViewState#getForView(double, double)}
     82     */
     83    @Test
     84    public void testGetForView() {
     85        MapViewPoint corner = state.getForView(0, 0);
     86        assertHasViewCoords(0, 0, corner);
     87
     88        MapViewPoint middle = state.getForView(120, 130);
     89        assertHasViewCoords(120, 130, middle);
     90
     91        MapViewPoint fraction = state.getForView(0.12, 0.7);
     92        assertHasViewCoords(0.12, 0.7, fraction);
     93
     94        MapViewPoint negative = state.getForView(-17, -30);
     95        assertHasViewCoords(-17, -30, negative);
     96    }
     97
     98    /**
     99     * Test {@link MapViewState#getViewWidth()} and {@link MapViewState#getViewHeight()}
     100     */
     101    @Test
     102    public void testGetViewSize() {
     103        assertEquals(WIDTH, state.getViewWidth(), 0.01);
     104        assertEquals(HEIGHT, state.getViewHeight(), 0.01);
     105    }
     106
     107    /**
     108     * Tests that all coordinate conversions for the point work.
     109     */
     110    @Test
     111    public void testPointConversions() {
     112        MapViewPoint p = state.getForView(50, 70);
     113        assertHasViewCoords(50, 70, p);
     114
     115        EastNorth eastnorth = p.getEastNorth();
     116        EastNorth shouldEastNorth = component.getEastNorth(50, 70);
     117        assertEquals("east", shouldEastNorth.east(), eastnorth.east(), 0.01);
     118        assertEquals("north", shouldEastNorth.north(), eastnorth.north(), 0.01);
     119        MapViewPoint reversed = state.getPointFor(shouldEastNorth);
     120        assertHasViewCoords(50, 70, reversed);
     121
     122        LatLon latlon = p.getLatLon();
     123        LatLon shouldLatLon = Main.getProjection().eastNorth2latlon(shouldEastNorth);
     124        assertEquals("lat", shouldLatLon.lat(), latlon.lat(), 0.01);
     125        assertEquals("lon", shouldLatLon.lon(), latlon.lon(), 0.01);
     126
     127        MapViewPoint p2 = state.getPointFor(new EastNorth(2, 3));
     128        assertEquals("east", 2, p2.getEastNorth().east(), 0.01);
     129        assertEquals("north", 3, p2.getEastNorth().north(), 0.01);
     130    }
     131}