001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.data.projection;
003
004import org.openstreetmap.josm.data.Bounds;
005import org.openstreetmap.josm.data.ProjectionBounds;
006import org.openstreetmap.josm.data.coor.EastNorth;
007import org.openstreetmap.josm.data.coor.LatLon;
008import org.openstreetmap.josm.data.projection.datum.Datum;
009import org.openstreetmap.josm.data.projection.proj.Proj;
010
011/**
012 * Implementation of the Projection interface that represents a coordinate reference system and delegates
013 * the real projection and datum conversion to other classes.
014 *
015 * It handles false easting and northing, central meridian and general scale factor before calling the
016 * delegate projection.
017 *
018 * Forwards lat/lon values to the real projection in units of radians.
019 *
020 * The fields are named after Proj.4 parameters.
021 *
022 * Subclasses of AbstractProjection must set ellps and proj to a non-null value.
023 * In addition, either datum or nadgrid has to be initialized to some value.
024 */
025public abstract class AbstractProjection implements Projection {
026
027    protected Ellipsoid ellps;
028    protected Datum datum;
029    protected Proj proj;
030    protected double x0;       /* false easting (in meters) */
031    protected double y0;       /* false northing (in meters) */
032    protected double lon0;     /* central meridian */
033    protected double pm;       /* prime meridian */
034    protected double k0 = 1.0; /* general scale factor */
035
036    private volatile ProjectionBounds projectionBoundsBox;
037
038    public final Ellipsoid getEllipsoid() {
039        return ellps;
040    }
041
042    public final Datum getDatum() {
043        return datum;
044    }
045
046    /**
047     * Replies the projection (in the narrow sense)
048     * @return The projection object
049     */
050    public final Proj getProj() {
051        return proj;
052    }
053
054    public final double getFalseEasting() {
055        return x0;
056    }
057
058    public final double getFalseNorthing() {
059        return y0;
060    }
061
062    public final double getCentralMeridian() {
063        return lon0;
064    }
065
066    public final double getScaleFactor() {
067        return k0;
068    }
069
070    @Override
071    public EastNorth latlon2eastNorth(LatLon ll) {
072        ll = datum.fromWGS84(ll);
073        double[] en = proj.project(Math.toRadians(ll.lat()), Math.toRadians(ll.lon() - lon0 - pm));
074        return new EastNorth(ellps.a * k0 * en[0] + x0, ellps.a * k0 * en[1] + y0);
075    }
076
077    @Override
078    public LatLon eastNorth2latlon(EastNorth en) {
079        double[] latlon_rad = proj.invproject((en.east() - x0) / ellps.a / k0, (en.north() - y0) / ellps.a / k0);
080        LatLon ll = new LatLon(Math.toDegrees(latlon_rad[0]), Math.toDegrees(latlon_rad[1]) + lon0 + pm);
081        return datum.toWGS84(ll);
082    }
083
084    @Override
085    public double getDefaultZoomInPPD() {
086        // this will set the map scaler to about 1000 m
087        return 10;
088    }
089
090    /**
091     * @return The EPSG Code of this CRS, null if it doesn't have one.
092     */
093    public abstract Integer getEpsgCode();
094
095    /**
096     * Default implementation of toCode().
097     * Should be overridden, if there is no EPSG code for this CRS.
098     */
099    @Override
100    public String toCode() {
101        return "EPSG:" + getEpsgCode();
102    }
103
104    protected static final double convertMinuteSecond(double minute, double second) {
105        return (minute/60.0) + (second/3600.0);
106    }
107
108    protected static final double convertDegreeMinuteSecond(double degree, double minute, double second) {
109        return degree + (minute/60.0) + (second/3600.0);
110    }
111
112    @Override
113    public final ProjectionBounds getWorldBoundsBoxEastNorth() {
114        ProjectionBounds result = projectionBoundsBox;
115        if (result == null) {
116            synchronized (this) {
117                result = projectionBoundsBox;
118                if (result == null) {
119                    Bounds b = getWorldBoundsLatLon();
120                    // add 4 corners
121                    result = new ProjectionBounds(latlon2eastNorth(b.getMin()));
122                    result.extend(latlon2eastNorth(b.getMax()));
123                    result.extend(latlon2eastNorth(new LatLon(b.getMinLat(), b.getMaxLon())));
124                    result.extend(latlon2eastNorth(new LatLon(b.getMaxLat(), b.getMinLon())));
125                    // and trace along the outline
126                    double dLon = (b.getMaxLon() - b.getMinLon()) / 1000;
127                    double dLat = (b.getMaxLat() - b.getMinLat()) / 1000;
128                    for (double lon = b.getMinLon(); lon < b.getMaxLon(); lon += dLon) {
129                        result.extend(latlon2eastNorth(new LatLon(b.getMinLat(), lon)));
130                        result.extend(latlon2eastNorth(new LatLon(b.getMaxLat(), lon)));
131                    }
132                    for (double lat = b.getMinLat(); lat < b.getMaxLat(); lat += dLat) {
133                        result.extend(latlon2eastNorth(new LatLon(lat, b.getMinLon())));
134                        result.extend(latlon2eastNorth(new LatLon(lat, b.getMaxLon())));
135                    }
136                    projectionBoundsBox = result;
137                }
138            }
139        }
140        return projectionBoundsBox;
141    }
142}