Ticket #13287: patch-mapview-chunked-render-area.patch

File patch-mapview-chunked-render-area.patch, 29.1 KB (added by michael2402, 6 years ago)
  • src/org/openstreetmap/josm/data/projection/AbstractProjection.java

    diff --git a/src/org/openstreetmap/josm/data/projection/AbstractProjection.java b/src/org/openstreetmap/josm/data/projection/AbstractProjection.java
    index c9a5ebf..8129cbc 100644
    a b  
    11// License: GPL. For details, see LICENSE file.
    22package org.openstreetmap.josm.data.projection;
    33
     4import java.util.Collections;
     5import java.util.HashMap;
     6import java.util.Map;
     7import java.util.function.DoubleUnaryOperator;
     8
    49import org.openstreetmap.josm.data.Bounds;
    510import org.openstreetmap.josm.data.ProjectionBounds;
    611import org.openstreetmap.josm.data.coor.EastNorth;
    712import org.openstreetmap.josm.data.coor.LatLon;
    813import org.openstreetmap.josm.data.projection.datum.Datum;
    914import org.openstreetmap.josm.data.projection.proj.Proj;
     15import org.openstreetmap.josm.tools.Utils;
    1016
    1117/**
    1218 * Implementation of the Projection interface that represents a coordinate reference system and delegates
    public abstract class AbstractProjection implements Projection { 
    115121
    116122    @Override
    117123    public LatLon eastNorth2latlon(EastNorth en) {
     124        return eastNorth2latlon(en, LatLon::normalizeLon);
     125    }
     126
     127    @Override
     128    public LatLon eastNorth2latlonClamped(EastNorth en) {
     129        LatLon ll = eastNorth2latlon(en, lon -> Utils.clamp(lon, -180, 180));
     130        Bounds bounds = getWorldBoundsLatLon();
     131        return new LatLon(Utils.clamp(ll.lat(), bounds.getMinLat(), bounds.getMaxLat()),
     132                Utils.clamp(ll.lon(), bounds.getMinLon(), bounds.getMaxLon()));
     133    }
     134
     135    private LatLon eastNorth2latlon(EastNorth en, DoubleUnaryOperator normalizeLon) {
    118136        double[] latlonRad = proj.invproject((en.east() * toMeter - x0) / ellps.a / k0, (en.north() * toMeter - y0) / ellps.a / k0);
    119         LatLon ll = new LatLon(Math.toDegrees(latlonRad[0]), LatLon.normalizeLon(Math.toDegrees(latlonRad[1]) + lon0 + pm));
     137        double lon = Math.toDegrees(latlonRad[1]) + lon0 + pm;
     138        LatLon ll = new LatLon(Math.toDegrees(latlonRad[0]), normalizeLon.applyAsDouble(lon));
    120139        return datum.toWGS84(ll);
    121140    }
    122141
    123142    @Override
     143    public Map<ProjectionBounds, Projecting> getProjectingsForArea(ProjectionBounds area) {
     144        if (proj.lonIsLinearToEast()) {
     145            //FIXME: Respect datum?
     146            // wrap the wrold around
     147            Bounds bounds = getWorldBoundsLatLon();
     148            double minEast = latlon2eastNorth(bounds.getMin()).east();
     149            double maxEast = latlon2eastNorth(bounds.getMax()).east();
     150            double dEast = maxEast - minEast;
     151            if ((area.minEast < minEast || area.maxEast > maxEast) && dEast > 0) {
     152                // We could handle the dEast < 0 case but we don't need it atm.
     153                int minChunk = (int) Math.floor((area.minEast - minEast) / dEast);
     154                int maxChunk = (int) Math.floor((area.maxEast - minEast) / dEast);
     155                HashMap<ProjectionBounds, Projecting> ret = new HashMap<>();
     156                for (int chunk = minChunk; chunk <= maxChunk; chunk++) {
     157                    ret.put(new ProjectionBounds(Math.max(area.minEast, minEast + chunk * dEast), area.minNorth,
     158                            Math.min(area.maxEast, maxEast + chunk * dEast), area.maxNorth),
     159                            new ShiftedProjecting(this, new EastNorth(-chunk * dEast, 0)));
     160                }
     161                return ret;
     162            }
     163        }
     164
     165        return Collections.singletonMap(area, this);
     166    }
     167
     168    @Override
    124169    public double getDefaultZoomInPPD() {
    125170        // this will set the map scaler to about 1000 m
    126171        return 10;
    public abstract class AbstractProjection implements Projection { 
    178223        }
    179224        return projectionBoundsBox;
    180225    }
     226
     227    @Override
     228    public Projection getBaseProjection() {
     229        return this;
     230    }
    181231}
  • new file src/org/openstreetmap/josm/data/projection/Projecting.java

    diff --git a/src/org/openstreetmap/josm/data/projection/Projecting.java b/src/org/openstreetmap/josm/data/projection/Projecting.java
    new file mode 100644
    index 0000000..e4e35cc
    - +  
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.data.projection;
     3
     4import java.util.Map;
     5
     6import org.openstreetmap.josm.data.ProjectionBounds;
     7import org.openstreetmap.josm.data.coor.EastNorth;
     8import org.openstreetmap.josm.data.coor.LatLon;
     9
     10/**
     11 * Classes implementing this are able to project between screen (east/north) and {@link LatLon} coordinates.
     12 * <p>
     13 * Each instance is backed by a base projection but may e.g. offset the resulting position.
     14 * @author Michael Zangl
     15 * @since xxx
     16 */
     17public interface Projecting {
     18
     19    /**
     20     * Convert from lat/lon to easting/northing.
     21     *
     22     * @param ll the geographical point to convert (in WGS84 lat/lon)
     23     * @return the corresponding east/north coordinates
     24     */
     25    EastNorth latlon2eastNorth(LatLon ll);
     26
     27    /**
     28     * Convert a east/north coordinate to the {@link LatLon} coordinate. This method clamps the lat/lon coordinate to the nearest point in the world bounds.
     29     * @param en east/north
     30     * @return The lat/lon coordinate.
     31     */
     32    LatLon eastNorth2latlonClamped(EastNorth en);
     33
     34    /**
     35     * Gets the base projection instance used.
     36     * @return The projection.
     37     */
     38    Projection getBaseProjection();
     39
     40    /**
     41     * Returns an map or (subarea, projecting) paris that contains projecting instances to convert the coordinates inside the given area.
     42     * This can be used by projections to support continuous projections.
     43     *
     44     * It is possible that the area covered by the map is bigger than the one given as area. There may be holes.
     45     * @param area The base area
     46     * @return a map of non-overlapping {@link ProjectionBounds} instances mapped to the {@link Projecting} object to use for that area.
     47     */
     48    Map<ProjectionBounds, Projecting> getProjectingsForArea(ProjectionBounds area);
     49}
  • src/org/openstreetmap/josm/data/projection/Projection.java

    diff --git a/src/org/openstreetmap/josm/data/projection/Projection.java b/src/org/openstreetmap/josm/data/projection/Projection.java
    index b2bd016..7477210 100644
    a b import org.openstreetmap.josm.data.coor.LatLon; 
    1313 * The conversion from east/north to the screen coordinates is simply a scale
    1414 * factor and x/y offset.
    1515 */
    16 public interface Projection {
     16public interface Projection extends Projecting {
    1717    /**
    1818     * The default scale factor in east/north units per pixel
    1919     * ({@link org.openstreetmap.josm.gui.NavigatableComponent#getState})).
    public interface Projection { 
    2323    double getDefaultZoomInPPD();
    2424
    2525    /**
    26      * Convert from lat/lon to easting/northing.
    27      *
    28      * @param ll the geographical point to convert (in WGS84 lat/lon)
    29      * @return the corresponding east/north coordinates
    30      */
    31     EastNorth latlon2eastNorth(LatLon ll);
    32 
    33     /**
    3426     * Convert from easting/norting to lat/lon.
    3527     *
    3628     * @param en the geographical point to convert (in projected coordinates)
    public interface Projection { 
    110102     * @return true if natural order of coordinates is North East, false if East North
    111103     */
    112104    boolean switchXY();
     105
    113106}
  • new file src/org/openstreetmap/josm/data/projection/ShiftedProjecting.java

    diff --git a/src/org/openstreetmap/josm/data/projection/ShiftedProjecting.java b/src/org/openstreetmap/josm/data/projection/ShiftedProjecting.java
    new file mode 100644
    index 0000000..0727c8a
    - +  
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.data.projection;
     3
     4import java.util.HashMap;
     5import java.util.Map;
     6
     7import org.openstreetmap.josm.data.ProjectionBounds;
     8import org.openstreetmap.josm.data.coor.EastNorth;
     9import org.openstreetmap.josm.data.coor.LatLon;
     10
     11/**
     12 * This is a projecting instance that shifts the projection by a given eastnorth offset.
     13 * @author Michael Zangl
     14 * @since xxx
     15 */
     16public class ShiftedProjecting implements Projecting {
     17    private final Projecting base;
     18    private final EastNorth offset;
     19
     20    /**
     21     * Create a new {@link ShiftedProjecting}
     22     * @param base The base to use
     23     * @param offset The offset to move base. Subtracted when converting lat/lon->east/north.
     24     */
     25    public ShiftedProjecting(Projecting base, EastNorth offset) {
     26        this.base = base;
     27        this.offset = offset;
     28    }
     29
     30    @Override
     31    public EastNorth latlon2eastNorth(LatLon ll) {
     32        return base.latlon2eastNorth(ll).add(offset);
     33    }
     34
     35    @Override
     36    public LatLon eastNorth2latlonClamped(EastNorth en) {
     37        return base.eastNorth2latlonClamped(en.subtract(offset));
     38    }
     39
     40    @Override
     41    public Projection getBaseProjection() {
     42        return base.getBaseProjection();
     43    }
     44
     45    @Override
     46    public Map<ProjectionBounds, Projecting> getProjectingsForArea(ProjectionBounds area) {
     47        Map<ProjectionBounds, Projecting> forArea = base
     48                .getProjectingsForArea(new ProjectionBounds(area.getMin().subtract(offset), area.getMax().subtract(offset)));
     49        HashMap<ProjectionBounds, Projecting> ret = new HashMap<>();
     50        forArea.forEach((pb, projecting) -> ret.put(
     51                new ProjectionBounds(pb.getMin().add(offset), pb.getMax().add(offset)),
     52                new ShiftedProjecting(projecting, offset)));
     53        return ret;
     54    }
     55}
  • src/org/openstreetmap/josm/data/projection/proj/Mercator.java

    diff --git a/src/org/openstreetmap/josm/data/projection/proj/Mercator.java b/src/org/openstreetmap/josm/data/projection/proj/Mercator.java
    index 10d61ca..592d9ef 100644
    a b public class Mercator extends AbstractProj implements IScaleFactorProvider { 
    119119    public double getScaleFactor() {
    120120        return scaleFactor;
    121121    }
     122
     123    @Override
     124    public boolean lonIsLinearToEast() {
     125        return true;
     126    }
    122127}
  • src/org/openstreetmap/josm/data/projection/proj/Proj.java

    diff --git a/src/org/openstreetmap/josm/data/projection/proj/Proj.java b/src/org/openstreetmap/josm/data/projection/proj/Proj.java
    index fa9b3fe..b2bf5a5 100644
    a b public interface Proj { 
    9090     * @return true, if it is geographic
    9191     */
    9292    boolean isGeographic();
     93
     94    /**
     95     * Checks wether the result of projecting a lon coordinate only has a linear relation to the east coordinate and
     96     * is not related to lat/north at all.
     97     * @return <code>true</code> if lon has a linear relationship to east only.
     98     * @since xxx
     99     */
     100    default boolean lonIsLinearToEast() {
     101        return false;
     102    }
    93103}
  • 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
    index f7dcd8d..cd7b292 100644
    a b import org.openstreetmap.josm.data.Bounds; 
    1616import org.openstreetmap.josm.data.ProjectionBounds;
    1717import org.openstreetmap.josm.data.coor.EastNorth;
    1818import org.openstreetmap.josm.data.coor.LatLon;
     19import org.openstreetmap.josm.data.projection.Projecting;
    1920import org.openstreetmap.josm.data.projection.Projection;
    2021import org.openstreetmap.josm.gui.download.DownloadDialog;
    2122import org.openstreetmap.josm.tools.bugreport.BugReport;
    import org.openstreetmap.josm.tools.bugreport.BugReport; 
    2728 */
    2829public final class MapViewState {
    2930
    30     private final Projection projection;
     31    private final Projecting projecting;
    3132
    3233    private final int viewWidth;
    3334    private final int viewHeight;
    public final class MapViewState { 
    5051     * @param scale The scale to use
    5152     * @param topLeft The top left corner in east/north space.
    5253     */
    53     private MapViewState(Projection projection, int viewWidth, int viewHeight, double scale, EastNorth topLeft) {
    54         this.projection = projection;
     54    private MapViewState(Projecting projection, int viewWidth, int viewHeight, double scale, EastNorth topLeft) {
     55        this.projecting = projection;
    5556        this.scale = scale;
    5657        this.topLeft = topLeft;
    5758
    public final class MapViewState { 
    6263    }
    6364
    6465    private MapViewState(EastNorth topLeft, MapViewState mapViewState) {
    65         this.projection = mapViewState.projection;
     66        this.projecting = mapViewState.projecting;
    6667        this.scale = mapViewState.scale;
    6768        this.topLeft = topLeft;
    6869
    public final class MapViewState { 
    7374    }
    7475
    7576    private MapViewState(double scale, MapViewState mapViewState) {
    76         this.projection = mapViewState.projection;
     77        this.projecting = mapViewState.projecting;
    7778        this.scale = scale;
    7879        this.topLeft = mapViewState.topLeft;
    7980
    public final class MapViewState { 
    8485    }
    8586
    8687    private MapViewState(JComponent position, MapViewState mapViewState) {
    87         this.projection = mapViewState.projection;
     88        this.projecting = mapViewState.projecting;
    8889        this.scale = mapViewState.scale;
    8990        this.topLeft = mapViewState.topLeft;
    9091
    public final class MapViewState { 
    105106        }
    106107    }
    107108
    108     private MapViewState(Projection projection, MapViewState mapViewState) {
    109         this.projection = projection;
     109    private MapViewState(Projecting projecting, MapViewState mapViewState) {
     110        this.projecting = projecting;
    110111        this.scale = mapViewState.scale;
    111112        this.topLeft = mapViewState.topLeft;
    112113
    public final class MapViewState { 
    200201     * @return The projection.
    201202     */
    202203    public Projection getProjection() {
    203         return projection;
     204        return projecting.getBaseProjection();
    204205    }
    205206
    206207    /**
    public final class MapViewState { 
    268269     * @since 10486
    269270     */
    270271    public MapViewState usingProjection(Projection projection) {
    271         if (projection.equals(this.projection)) {
     272        if (projection.equals(this.projecting)) {
    272273            return this;
    273274        } else {
    274275            return new MapViewState(projection, this);
    public final class MapViewState { 
    357358        /**
    358359         * Gets the current position in LatLon coordinates according to the current projection.
    359360         * @return The positon as LatLon.
     361         * @see #getLatLonClamped()
    360362         */
    361363        public LatLon getLatLon() {
    362             return projection.eastNorth2latlon(getEastNorth());
     364            return projecting.getBaseProjection().eastNorth2latlon(getEastNorth());
     365        }
     366
     367        /**
     368         * Gets the latlon coordinate clamped to the current world area.
     369         * @return The lat/lon coordinate
     370         * @since xxx
     371         */
     372        public LatLon getLatLonClamped() {
     373            return projecting.eastNorth2latlonClamped(getEastNorth());
    363374        }
    364375
    365376        /**
    public final class MapViewState { 
    473484         * @since 10458
    474485         */
    475486        public Bounds getLatLonBoundsBox() {
    476             return projection.getLatLonBoundsBox(getProjectionBounds());
     487            // TODO @michael2402: Use hillclimb.
     488            return projecting.getBaseProjection().getLatLonBoundsBox(getProjectionBounds());
    477489        }
    478490
    479491        /**
  • src/org/openstreetmap/josm/gui/layer/AbstractTileSourceLayer.java

    diff --git a/src/org/openstreetmap/josm/gui/layer/AbstractTileSourceLayer.java b/src/org/openstreetmap/josm/gui/layer/AbstractTileSourceLayer.java
    index 52dcbe9..2179312 100644
    a b implements ImageObserver, TileLoaderListener, ZoomChangeListener, FilterChangeLi 
    230230    }
    231231
    232232    protected void initTileSource(T tileSource) {
    233         coordinateConverter = new TileCoordinateConverter(Main.map.mapView, getDisplaySettings());
     233        coordinateConverter = new TileCoordinateConverter(Main.map.mapView, tileSource, getDisplaySettings());
    234234        attribution.initialize(tileSource);
    235235
    236236        currentZoomLevel = getBestZoom();
    implements ImageObserver, TileLoaderListener, ZoomChangeListener, FilterChangeLi 
    367367     * @return average number of screen pixels per tile pixel
    368368     */
    369369    private double getScaleFactor(int zoom) {
    370         if (!Main.isDisplayingMapView()) return 1;
    371         MapView mv = Main.map.mapView;
    372         LatLon topLeft = mv.getLatLon(0, 0);
    373         LatLon botRight = mv.getLatLon(mv.getWidth(), mv.getHeight());
    374         TileXY t1 = tileSource.latLonToTileXY(topLeft.toCoordinate(), zoom);
    375         TileXY t2 = tileSource.latLonToTileXY(botRight.toCoordinate(), zoom);
    376 
    377         int screenPixels = mv.getWidth()*mv.getHeight();
    378         double tilePixels = Math.abs((t2.getY()-t1.getY())*(t2.getX()-t1.getX())*tileSource.getTileSize()*tileSource.getTileSize());
    379         if (screenPixels == 0 || tilePixels == 0) return 1;
    380         return screenPixels/tilePixels;
     370        if (coordinateConverter != null) {
     371            return coordinateConverter.getScaleFactor(zoom);
     372        } else {
     373            return 1;
     374        }
    381375    }
    382376
    383377    protected int getBestZoom() {
    implements ImageObserver, TileLoaderListener, ZoomChangeListener, FilterChangeLi 
    12451239    }
    12461240
    12471241    private LatLon getShiftedLatLon(EastNorth en) {
    1248         return Main.getProjection().eastNorth2latlon(en.add(-getDisplaySettings().getDx(), -getDisplaySettings().getDy()));
     1242        return coordinateConverter.getProjecting().eastNorth2latlonClamped(en);
    12491243    }
    12501244
    12511245    private ICoordinate getShiftedCoord(EastNorth en) {
    implements ImageObserver, TileLoaderListener, ZoomChangeListener, FilterChangeLi 
    15161510
    15171511    @Override
    15181512    public void paint(Graphics2D g, MapView mv, Bounds bounds) {
    1519         ProjectionBounds pb = mv.getState().getViewArea().getProjectionBounds();
    1520 
    1521         needRedraw = false;
     1513        // old and unused.
     1514    }
    15221515
     1516    private void drawInViewArea(Graphics2D g, MapView mv, ProjectionBounds pb) {
    15231517        int zoom = currentZoomLevel;
    15241518        if (getDisplaySettings().isAutoZoom()) {
    15251519            zoom = getBestZoom();
    implements ImageObserver, TileLoaderListener, ZoomChangeListener, FilterChangeLi 
    18941888        public void paint(MapViewGraphics graphics) {
    18951889            allocateCacheMemory();
    18961890            if (memory != null) {
    1897                 super.paint(graphics);
     1891                doPaint(graphics);
    18981892            }
    18991893        }
    19001894
     1895        private void doPaint(MapViewGraphics graphics) {
     1896            ProjectionBounds pb = graphics.getClipBounds().getProjectionBounds();
     1897
     1898            needRedraw = false; // TEMPORARY
     1899
     1900            drawInViewArea(graphics.getDefaultGraphics(), graphics.getMapView(), pb);
     1901        }
     1902
    19011903        private void allocateCacheMemory() {
    19021904            if (memory == null) {
    19031905                MemoryManager manager = MemoryManager.getInstance();
  • src/org/openstreetmap/josm/gui/layer/imagery/TileCoordinateConverter.java

    diff --git a/src/org/openstreetmap/josm/gui/layer/imagery/TileCoordinateConverter.java b/src/org/openstreetmap/josm/gui/layer/imagery/TileCoordinateConverter.java
    index e8c925d..1489ff3 100644
    a b import java.awt.geom.Point2D; 
    55import java.awt.geom.Rectangle2D;
    66
    77import org.openstreetmap.gui.jmapviewer.Tile;
     8import org.openstreetmap.gui.jmapviewer.TileXY;
    89import org.openstreetmap.gui.jmapviewer.interfaces.ICoordinate;
     10import org.openstreetmap.gui.jmapviewer.interfaces.TileSource;
    911import org.openstreetmap.josm.data.coor.LatLon;
     12import org.openstreetmap.josm.data.projection.Projecting;
     13import org.openstreetmap.josm.data.projection.ShiftedProjecting;
    1014import org.openstreetmap.josm.gui.MapView;
    1115import org.openstreetmap.josm.gui.MapViewState.MapViewPoint;
    1216
    import org.openstreetmap.josm.gui.MapViewState.MapViewPoint; 
    1822public class TileCoordinateConverter {
    1923    private MapView mapView;
    2024    private TileSourceDisplaySettings settings;
     25    private TileSource tileSource;
    2126
    2227    /**
    2328     * Create a new coordinate converter for the map view.
    2429     * @param mapView The map view.
     30     * @param tileSource The tile source to use when converting coordinates.
    2531     * @param settings displacement settings.
    2632     */
    27     public TileCoordinateConverter(MapView mapView, TileSourceDisplaySettings settings) {
     33    public TileCoordinateConverter(MapView mapView, TileSource tileSource,  TileSourceDisplaySettings settings) {
    2834        this.mapView = mapView;
     35        this.tileSource = tileSource;
    2936        this.settings = settings;
    3037    }
    3138
    public class TileCoordinateConverter { 
    3441    }
    3542
    3643    /**
     44     * Gets the projecting instance to use to convert between latlon and eastnorth coordinates.
     45     * @return The {@link Projecting} instance.
     46     */
     47    public Projecting getProjecting() {
     48        return new ShiftedProjecting(mapView.getProjection(), settings.getDisplacement());
     49    }
     50
     51    /**
    3752     * Gets the top left position of the tile inside the map view.
    3853     * @param tile The tile
    3954     * @return The positon.
    public class TileCoordinateConverter { 
    5469
    5570        return pos(c1).rectTo(pos(c2)).getInView();
    5671    }
     72
     73    /**
     74     * Returns average number of screen pixels per tile pixel for current mapview
     75     * @param zoom zoom level
     76     * @return average number of screen pixels per tile pixel
     77     */
     78    public double getScaleFactor(int zoom) {
     79        LatLon topLeft = mapView.getLatLon(0, 0);
     80        LatLon botRight = mapView.getLatLon(mapView.getWidth(), mapView.getHeight());
     81        TileXY t1 = tileSource.latLonToTileXY(topLeft.toCoordinate(), zoom);
     82        TileXY t2 = tileSource.latLonToTileXY(botRight.toCoordinate(), zoom);
     83
     84        int screenPixels = mapView.getWidth()*mapView.getHeight();
     85        double tilePixels = Math.abs((t2.getY()-t1.getY())*(t2.getX()-t1.getX())*tileSource.getTileSize()*tileSource.getTileSize());
     86        if (screenPixels == 0 || tilePixels == 0) return 1;
     87        return screenPixels/tilePixels;
     88    }
    5789}
  • src/org/openstreetmap/josm/tools/Utils.java

    diff --git a/src/org/openstreetmap/josm/tools/Utils.java b/src/org/openstreetmap/josm/tools/Utils.java
    index 0b92733..84968ba 100644
    a b public final class Utils { 
    15491549            });
    15501550        }
    15511551    }
     1552
     1553    /**
     1554     * Clamp a value to the given range
     1555     * @param val The value
     1556     * @param min minimum value
     1557     * @param max maximum value
     1558     * @return the value
     1559     */
     1560    public static double clamp(double val, double min, double max) {
     1561        if (val < min) {
     1562            return min;
     1563        } else if (val > max) {
     1564            return max;
     1565        } else {
     1566            return val;
     1567        }
     1568    }
    15521569}
  • new file test/unit/org/openstreetmap/josm/data/projection/ShiftedProjectionTest.java

    diff --git a/test/unit/org/openstreetmap/josm/data/projection/ShiftedProjectionTest.java b/test/unit/org/openstreetmap/josm/data/projection/ShiftedProjectionTest.java
    new file mode 100644
    index 0000000..7748a44
    - +  
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.data.projection;
     3
     4import static org.junit.Assert.assertEquals;
     5
     6import java.util.Comparator;
     7import java.util.HashMap;
     8import java.util.List;
     9import java.util.Map;
     10import java.util.Map.Entry;
     11import java.util.stream.Collectors;
     12
     13import org.junit.Test;
     14import org.openstreetmap.josm.data.ProjectionBounds;
     15import org.openstreetmap.josm.data.coor.EastNorth;
     16import org.openstreetmap.josm.data.coor.LatLon;
     17
     18/**
     19 * Tests for {@link ShiftedProjecting}
     20 * @author Michael Zangl
     21 * @since xxx
     22 */
     23public class ShiftedProjectionTest {
     24    private static final class ProjectingBase implements Projecting {
     25        @Override
     26        public EastNorth latlon2eastNorth(LatLon ll) {
     27            return new EastNorth(ll.lat() * 2, ll.lon() * 3);
     28        }
     29
     30        @Override
     31        public Map<ProjectionBounds, Projecting> getProjectingsForArea(ProjectionBounds area) {
     32            HashMap<ProjectionBounds, Projecting> map = new HashMap<>();
     33            // split at east = 0
     34            if (area.minEast < 0) {
     35                map.put(new ProjectionBounds(area.minEast, area.minNorth, Math.min(area.maxEast, 0), area.maxNorth), this);
     36            }
     37            if (area.maxEast > 0) {
     38                map.put(new ProjectionBounds(Math.max(area.minEast, 0), area.minNorth, area.maxEast, area.maxNorth), this);
     39            }
     40
     41            return map;
     42        }
     43
     44        @Override
     45        public Projection getBaseProjection() {
     46            throw new AssertionError();
     47        }
     48
     49        @Override
     50        public LatLon eastNorth2latlonClamped(EastNorth en) {
     51            return new LatLon(en.east() / 2, en.north() / 3);
     52        }
     53    }
     54
     55    /**
     56     * Test {@link ShiftedProjecting#latlon2eastNorth(LatLon)}
     57     */
     58    @Test
     59    public void testLatlon2eastNorth() {
     60        Projecting base = new ProjectingBase();
     61
     62        ShiftedProjecting unshifted = new ShiftedProjecting(base, new EastNorth(0, 0));
     63        EastNorth unshift_00 = unshifted.latlon2eastNorth(new LatLon(0, 0));
     64        assertEquals(0, unshift_00.east(), 1e-10);
     65        assertEquals(0, unshift_00.north(), 1e-10);
     66        EastNorth unshift_12 = unshifted.latlon2eastNorth(new LatLon(1, 2));
     67        assertEquals(2, unshift_12.east(), 1e-10);
     68        assertEquals(6, unshift_12.north(), 1e-10);
     69
     70        ShiftedProjecting shifted = new ShiftedProjecting(base, new EastNorth(5, 7));
     71        EastNorth shift_00 = shifted.latlon2eastNorth(new LatLon(0, 0));
     72        assertEquals(5, shift_00.east(), 1e-10);
     73        assertEquals(7, shift_00.north(), 1e-10);
     74        EastNorth shift_12 = shifted.latlon2eastNorth(new LatLon(1, 2));
     75        assertEquals(2 + 5, shift_12.east(), 1e-10);
     76        assertEquals(6 + 7, shift_12.north(), 1e-10);
     77    }
     78
     79    /**
     80     * Test {@link ShiftedProjecting#eastNorth2latlonClamped(EastNorth)}
     81     */
     82    @Test
     83    public void testEastNorth2latlonClamped() {
     84        Projecting base = new ProjectingBase();
     85
     86        ShiftedProjecting unshifted = new ShiftedProjecting(base, new EastNorth(0, 0));
     87        LatLon unshift_00 = unshifted.eastNorth2latlonClamped(new EastNorth(0, 0));
     88        assertEquals(0, unshift_00.lat(), 1e-10);
     89        assertEquals(0, unshift_00.lon(), 1e-10);
     90        LatLon unshift_12 = unshifted.eastNorth2latlonClamped(new EastNorth(2, 6));
     91        assertEquals(1, unshift_12.lat(), 1e-10);
     92        assertEquals(2, unshift_12.lon(), 1e-10);
     93
     94        ShiftedProjecting shifted = new ShiftedProjecting(base, new EastNorth(5, 7));
     95        LatLon shift_00 = shifted.eastNorth2latlonClamped(new EastNorth(5, 7));
     96        assertEquals(0, shift_00.lat(), 1e-10);
     97        assertEquals(0, shift_00.lon(), 1e-10);
     98        LatLon shift_12 = shifted.eastNorth2latlonClamped(new EastNorth(2 + 5, 6 + 7));
     99        assertEquals(1, shift_12.lat(), 1e-10);
     100        assertEquals(2, shift_12.lon(), 1e-10);
     101    }
     102
     103    /**
     104     * Test {@link ShiftedProjecting#getProjectingsForArea(ProjectionBounds)}, single area case
     105     */
     106    @Test
     107    public void testGetProjectingsForArea() {
     108        Projecting base = new ProjectingBase();
     109        ShiftedProjecting shifted = new ShiftedProjecting(base, new EastNorth(5, 7));
     110
     111        ProjectionBounds area = new ProjectionBounds(10, 0, 20, 20);
     112
     113        Map<ProjectionBounds, Projecting> areas = shifted.getProjectingsForArea(area);
     114        assertEquals(1, areas.size());
     115        ProjectionBounds pb = areas.keySet().iterator().next();
     116        assertEquals(area.minEast, pb.minEast, 1e-7);
     117        assertEquals(area.maxEast, pb.maxEast, 1e-7);
     118        assertEquals(area.minNorth, pb.minNorth, 1e-7);
     119        assertEquals(area.maxNorth, pb.maxNorth, 1e-7);
     120    }
     121
     122    /**
     123     * Test {@link ShiftedProjecting#getProjectingsForArea(ProjectionBounds)}, multiple area case
     124     */
     125    @Test
     126    public void testGetProjectingsForAreaMultiple() {
     127        Projecting base = new ProjectingBase();
     128        ShiftedProjecting shifted = new ShiftedProjecting(base, new EastNorth(5, 7));
     129
     130        ProjectionBounds area = new ProjectionBounds(-10, 0, 20, 20);
     131
     132        // breach is at:
     133        EastNorth breachAt = shifted.latlon2eastNorth(base.eastNorth2latlonClamped(new EastNorth(0, 0)));
     134        assertEquals(5, breachAt.east(), 1e-7);
     135
     136        Map<ProjectionBounds, Projecting> areas = shifted.getProjectingsForArea(area);
     137        assertEquals(2, areas.size());
     138        List<Entry<ProjectionBounds, Projecting>> entries = areas.entrySet().stream().sorted(Comparator.comparingDouble(b -> b.getKey().minEast)).collect(Collectors.toList());
     139        assertEquals(area.minEast, entries.get(0).getKey().minEast, 1e-7);
     140        assertEquals(5, entries.get(0).getKey().maxEast, 1e-7);
     141        assertEquals(area.minNorth, entries.get(0).getKey().minNorth, 1e-7);
     142        assertEquals(area.maxNorth, entries.get(0).getKey().maxNorth, 1e-7);
     143        assertEquals(5, entries.get(1).getKey().minEast, 1e-7);
     144        assertEquals(area.maxEast, entries.get(1).getKey().maxEast, 1e-7);
     145        assertEquals(area.minNorth, entries.get(1).getKey().minNorth, 1e-7);
     146        assertEquals(area.maxNorth, entries.get(1).getKey().maxNorth, 1e-7);
     147    }
     148}