// License: GPL. For details, see LICENSE file.
package org.openstreetmap.josm.gui;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.geom.Point2D;
import java.util.Objects;
import javax.swing.JPanel;
import org.CustomMatchers;
import org.hamcrest.CustomTypeSafeMatcher;
import org.hamcrest.Matcher;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
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.gui.util.GuiHelper;
import org.openstreetmap.josm.testutils.JOSMTestRules;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
/**
* Some tests for the {@link NavigatableComponent} class.
* @author Michael Zangl
*
*/
public class NavigatableComponentTest {
private static final class NavigatableComponentMock extends NavigatableComponent {
@Override
public Point getLocationOnScreen() {
return new Point(30, 40);
}
@Override
protected boolean isVisibleOnScreen() {
return true;
}
}
private static final int HEIGHT = 200;
private static final int WIDTH = 300;
private NavigatableComponent component;
/**
* We need the projection for coordinate conversions.
*/
@Rule
@SuppressFBWarnings(value = "URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD")
public JOSMTestRules test = new JOSMTestRules().preferences().platform().projection();
/**
* Create a new, fresh {@link NavigatableComponent}
*/
@Before
public void setUp() {
component = new NavigatableComponentMock();
component.setBounds(new Rectangle(WIDTH, HEIGHT));
// wait for the event to be propagated.
GuiHelper.runInEDTAndWait(new Runnable() {
@Override
public void run() {
}
});
component.setVisible(true);
JPanel parent = new JPanel();
parent.add(component);
component.updateLocationState();
}
/**
* Test if the default scale was set correctly.
*/
@Test
public void testDefaultScale() {
assertEquals(Main.getProjection().getDefaultZoomInPPD(), component.getScale(), 0.00001);
}
/**
* Tests {@link NavigatableComponent#getPoint2D(EastNorth)}
*/
@Test
public void testPoint2DEastNorth() {
assertThat(component.getPoint2D((EastNorth) null), CustomMatchers.is(new Point2D.Double()));
Point2D shouldBeCenter = component.getPoint2D(component.getCenter());
assertThat(shouldBeCenter, CustomMatchers.is(new Point2D.Double(WIDTH / 2, HEIGHT / 2)));
EastNorth testPoint = component.getCenter().add(300 * component.getScale(), 200 * component.getScale());
Point2D testPointConverted = component.getPoint2D(testPoint);
assertThat(testPointConverted, CustomMatchers.is(new Point2D.Double(WIDTH / 2 + 300, HEIGHT / 2 - 200)));
}
/**
* TODO: Implement this test.
*/
@Test
public void testPoint2DLatLon() {
assertThat(component.getPoint2D((LatLon) null), CustomMatchers.is(new Point2D.Double()));
// TODO: Really test this.
}
/**
* Tests {@link NavigatableComponent#zoomTo(LatLon)}
*/
@Test
public void testZoomToLatLon() {
component.zoomTo(new LatLon(10, 10));
Point2D shouldBeCenter = component.getPoint2D(new LatLon(10, 10));
// 0.5 pixel tolerance, see isAfterZoom
assertEquals(shouldBeCenter.getX(), WIDTH / 2., 0.5);
assertEquals(shouldBeCenter.getY(), HEIGHT / 2., 0.5);
}
/**
* Tests {@link NavigatableComponent#zoomToFactor(double)} and {@link NavigatableComponent#zoomToFactor(EastNorth, double)}
*/
@Test
public void testZoomToFactor() {
EastNorth center = component.getCenter();
double initialScale = component.getScale();
// zoomToFactor(double)
component.zoomToFactor(0.5);
assertEquals(initialScale / 2, component.getScale(), 0.00000001);
assertThat(component.getCenter(), isAfterZoom(center, component.getScale()));
component.zoomToFactor(2);
assertEquals(initialScale, component.getScale(), 0.00000001);
assertThat(component.getCenter(), isAfterZoom(center, component.getScale()));
// zoomToFactor(EastNorth, double)
EastNorth newCenter = new EastNorth(10, 20);
component.zoomToFactor(newCenter, 0.5);
assertEquals(initialScale / 2, component.getScale(), 0.00000001);
assertThat(component.getCenter(), isAfterZoom(newCenter, component.getScale()));
component.zoomToFactor(newCenter, 2);
assertEquals(initialScale, component.getScale(), 0.00000001);
assertThat(component.getCenter(), isAfterZoom(newCenter, component.getScale()));
}
/**
* Tests {@link NavigatableComponent#getEastNorth(int, int)}
*/
@Test
public void testGetEastNorth() {
EastNorth center = component.getCenter();
assertThat(component.getEastNorth(WIDTH / 2, HEIGHT / 2), CustomMatchers.is(center));
EastNorth testPoint = component.getCenter().add(WIDTH * component.getScale(), HEIGHT * component.getScale());
assertThat(component.getEastNorth(3 * WIDTH / 2, -HEIGHT / 2), CustomMatchers.is(testPoint));
}
/**
* Tests {@link NavigatableComponent#zoomToFactor(double, double, double)}
*/
@Test
public void testZoomToFactorCenter() {
// zoomToFactor(double, double, double)
// assumes getEastNorth works as expected
EastNorth testPoint1 = component.getEastNorth(0, 0);
EastNorth testPoint2 = component.getEastNorth(200, 150);
double initialScale = component.getScale();
component.zoomToFactor(0, 0, 0.5);
assertEquals(initialScale / 2, component.getScale(), 0.00000001);
assertThat(component.getEastNorth(0, 0), isAfterZoom(testPoint1, component.getScale()));
component.zoomToFactor(0, 0, 2);
assertEquals(initialScale, component.getScale(), 0.00000001);
assertThat(component.getEastNorth(0, 0), isAfterZoom(testPoint1, component.getScale()));
component.zoomToFactor(200, 150, 0.5);
assertEquals(initialScale / 2, component.getScale(), 0.00000001);
assertThat(component.getEastNorth(200, 150), isAfterZoom(testPoint2, component.getScale()));
component.zoomToFactor(200, 150, 2);
assertEquals(initialScale, component.getScale(), 0.00000001);
assertThat(component.getEastNorth(200, 150), isAfterZoom(testPoint2, component.getScale()));
}
/**
* Tests {@link NavigatableComponent#getProjectionBounds()}
*/
@Test
public void testGetProjectionBounds() {
ProjectionBounds bounds = component.getProjectionBounds();
assertThat(bounds.getCenter(), CustomMatchers.is(component.getCenter()));
assertThat(bounds.getMin(), CustomMatchers.is(component.getEastNorth(0, HEIGHT)));
assertThat(bounds.getMax(), CustomMatchers.is(component.getEastNorth(WIDTH, 0)));
}
/**
* Tests {@link NavigatableComponent#getRealBounds()}
*/
@Test
public void testGetRealBounds() {
Bounds bounds = component.getRealBounds();
assertThat(bounds.getCenter(), CustomMatchers.is(component.getLatLon(WIDTH / 2, HEIGHT / 2)));
assertThat(bounds.getMin(), CustomMatchers.is(component.getLatLon(0, HEIGHT)));
assertThat(bounds.getMax(), CustomMatchers.is(component.getLatLon(WIDTH, 0)));
}
/**
* Check that EastNorth is the same as expected after zooming the NavigatableComponent.
*
* Adds tolerance of 0.5 pixel for pixel grid alignment, see
* {@link NavigatableComponent#zoomTo(EastNorth, double, boolean)}
* @param expected expected
* @param scale current scale
* @return Matcher object
*/
private Matcher<EastNorth> isAfterZoom(EastNorth expected, double scale) {
return new CustomTypeSafeMatcher<EastNorth>(Objects.toString(expected)) {
@Override
protected boolean matchesSafely(EastNorth actual) {
// compare pixels (east/north divided by scale)
return Math.abs((expected.getX() - actual.getX()) / scale) <= 0.5
&& Math.abs((expected.getY() - actual.getY()) / scale) <= 0.5;
}
};
}
}