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    protected double toMeter = 1.0; /* switch from meters to east/north coordinate units */
036
037    private volatile ProjectionBounds projectionBoundsBox;
038
039    public final Ellipsoid getEllipsoid() {
040        return ellps;
041    }
042
043    public final Datum getDatum() {
044        return datum;
045    }
046
047    /**
048     * Replies the projection (in the narrow sense)
049     * @return The projection object
050     */
051    public final Proj getProj() {
052        return proj;
053    }
054
055    public final double getFalseEasting() {
056        return x0;
057    }
058
059    public final double getFalseNorthing() {
060        return y0;
061    }
062
063    public final double getCentralMeridian() {
064        return lon0;
065    }
066
067    public final double getScaleFactor() {
068        return k0;
069    }
070
071    /**
072     * Get the factor that converts meters to intended units of east/north coordinates.
073     *
074     * For projected coordinate systems, the semi-major axis of the ellipsoid is
075     * always given in meters, which means the preliminary projection result will
076     * be in meters as well. This factor is used to convert to the intended units
077     * of east/north coordinates (e.g. feet in the US).
078     * 
079     * For geographic coordinate systems, the preliminary "projection" result will
080     * be in degrees, so there is no reason to convert anything and this factor
081     * will by 1 by default.
082     *
083     * @return factor that converts meters to intended units of east/north coordinates
084     */
085    public final double getToMeter() {
086        return toMeter;
087    }
088
089    @Override
090    public EastNorth latlon2eastNorth(LatLon ll) {
091        ll = datum.fromWGS84(ll);
092        double[] en = proj.project(Math.toRadians(ll.lat()), Math.toRadians(LatLon.normalizeLon(ll.lon() - lon0 - pm)));
093        return new EastNorth((ellps.a * k0 * en[0] + x0) / toMeter, (ellps.a * k0 * en[1] + y0) / toMeter);
094    }
095
096    @Override
097    public LatLon eastNorth2latlon(EastNorth en) {
098        double[] latlonRad = proj.invproject((en.east() * toMeter - x0) / ellps.a / k0, (en.north() * toMeter - y0) / ellps.a / k0);
099        LatLon ll = new LatLon(Math.toDegrees(latlonRad[0]), LatLon.normalizeLon(Math.toDegrees(latlonRad[1]) + lon0 + pm));
100        return datum.toWGS84(ll);
101    }
102
103    @Override
104    public double getDefaultZoomInPPD() {
105        // this will set the map scaler to about 1000 m
106        return 10;
107    }
108
109    /**
110     * @return The EPSG Code of this CRS, null if it doesn't have one.
111     */
112    public abstract Integer getEpsgCode();
113
114    /**
115     * Default implementation of toCode().
116     * Should be overridden, if there is no EPSG code for this CRS.
117     */
118    @Override
119    public String toCode() {
120        return "EPSG:" + getEpsgCode();
121    }
122
123    protected static final double convertMinuteSecond(double minute, double second) {
124        return (minute/60.0) + (second/3600.0);
125    }
126
127    protected static final double convertDegreeMinuteSecond(double degree, double minute, double second) {
128        return degree + (minute/60.0) + (second/3600.0);
129    }
130
131    @Override
132    public final ProjectionBounds getWorldBoundsBoxEastNorth() {
133        ProjectionBounds result = projectionBoundsBox;
134        if (result == null) {
135            synchronized (this) {
136                result = projectionBoundsBox;
137                if (result == null) {
138                    Bounds b = getWorldBoundsLatLon();
139                    // add 4 corners
140                    result = new ProjectionBounds(latlon2eastNorth(b.getMin()));
141                    result.extend(latlon2eastNorth(b.getMax()));
142                    result.extend(latlon2eastNorth(new LatLon(b.getMinLat(), b.getMaxLon())));
143                    result.extend(latlon2eastNorth(new LatLon(b.getMaxLat(), b.getMinLon())));
144                    // and trace along the outline
145                    double dLon = (b.getMaxLon() - b.getMinLon()) / 1000;
146                    double dLat = (b.getMaxLat() - b.getMinLat()) / 1000;
147                    for (double lon = b.getMinLon(); lon < b.getMaxLon(); lon += dLon) {
148                        result.extend(latlon2eastNorth(new LatLon(b.getMinLat(), lon)));
149                        result.extend(latlon2eastNorth(new LatLon(b.getMaxLat(), lon)));
150                    }
151                    for (double lat = b.getMinLat(); lat < b.getMaxLat(); lat += dLat) {
152                        result.extend(latlon2eastNorth(new LatLon(lat, b.getMinLon())));
153                        result.extend(latlon2eastNorth(new LatLon(lat, b.getMaxLon())));
154                    }
155                    projectionBoundsBox = result;
156                }
157            }
158        }
159        return projectionBoundsBox;
160    }
161}