    001    //$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/branches/2.4_testing/src/org/deegree/crs/projections/Projection.java $
    002    /*----------------------------------------------------------------------------
    003     This file is part of deegree, http://deegree.org/
    004     Copyright (C) 2001-2009 by:
    005     Department of Geography, University of Bonn
    006     and
    007     lat/lon GmbH
    008
    009     This library is free software; you can redistribute it and/or modify it under
    010     the terms of the GNU Lesser General Public License as published by the Free
    011     Software Foundation; either version 2.1 of the License, or (at your option)
    012     any later version.
    013     This library is distributed in the hope that it will be useful, but WITHOUT
    014     ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
    015     FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
    016     details.
    017     You should have received a copy of the GNU Lesser General Public License
    018     along with this library; if not, write to the Free Software Foundation, Inc.,
    019     59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
    020
    021     Contact information:
    022
    023     lat/lon GmbH
    024     Aennchenstr. 19, 53177 Bonn
    025     Germany
    026     http://lat-lon.de/
    027
    028     Department of Geography, University of Bonn
    029     Prof. Dr. Klaus Greve
    030     Postfach 1147, 53001 Bonn
    031     Germany
    032     http://www.geographie.uni-bonn.de/deegree/
    033
    034     e-mail: info@deegree.org
    035     ----------------------------------------------------------------------------*/
    036
    037    package org.deegree.crs.projections;
    038
    039    import static org.deegree.crs.projections.ProjectionUtils.EPS11;
    040    import static org.deegree.crs.projections.ProjectionUtils.normalizeLatitude;
    041    import static org.deegree.crs.projections.ProjectionUtils.normalizeLongitude;
    042
    043    import java.io.Serializable;
    044
    045    import javax.vecmath.Point2d;
    046
    047    import org.deegree.crs.Identifiable;
    048    import org.deegree.crs.components.Datum;
    049    import org.deegree.crs.components.Ellipsoid;
    050    import org.deegree.crs.components.PrimeMeridian;
    051    import org.deegree.crs.components.Unit;
    052    import org.deegree.crs.coordinatesystems.GeographicCRS;
    053    import org.deegree.crs.exceptions.ProjectionException;
    054
    055    /**
    056     * Map <code>conversion</code> is the process of changing the map grid coordinates (usually, but not always, Easting &
    057     * Northing) of a Projected Coordinate Reference System to its corresponding geographical coordinates (Latitude &
    058     * Longitude) or vice versa.
    059     * <p>
    060     * A projection is conformal if an infinitesimal small perfect circle on the earth's surface results in an infinitesimal
    061     * small projected perfect circle (an ellipsoid with no eccentricity). In other words, the relative local angles about
    062     * every point on the map are shown correctly.
    063     * </p>
    064     * <p>
    065     * An equal area projection can be best explained with a coin (Snyder), a coin (of any size) covers exactly the same
    066     * area of the actual earth as the same coin on any other part of the map. This can only be done by distorting shape,
    067     * scale and and angles of the original earth's layout.
    068     * </p>
    069     *
    070     *
    071     * @author <a href="mailto:bezema@lat-lon.de">Rutger Bezema</a>
    072     *
    073     * @author last edited by: $Author: rbezema $
    074     *
    075     * @version $Revision: 18222 $, $Date: 2009-06-22 14:42:54 +0200 (Mo, 22. Jun 2009) $
    076     *
    077     */
    078
    079    public abstract class Projection extends Identifiable implements Serializable {
    080
    081        private static final long serialVersionUID = 7369289115314441772L;
    082
    083        private final boolean conformal;
    084
    085        private boolean equalArea;
    086
    087        private double scale;
    088
    089        /**
    090         * the scale*semimajor-axis, often revered to as R*k_0 in Snyder.
    091         */
    092        private double scaleFactor;
    093
    094        private final double falseNorthing;
    095
    096        private double falseEasting;
    097
    098        private final Point2d naturalOrigin;
    099
    100        // Values gotten from the natural origin
    101        private double projectionLatitude;
    102
    103        private double projectionLongitude;
    104
    105        // the sin of the projection latitude
    106        private double sinphi0;
    107
    108        // the cos of the projection latitude
    109        private double cosphi0;
    110
    111        private final Unit units;
    112
    113        private final GeographicCRS geographicCRS;
    114
    115        private boolean isSpherical;
    116
    117        /**
    118         * Creates a Projection. <b>Caution</b>, the given natural origin should be given in radians rather then degrees.
    119         *
    120         * @param geographicCRS
    121         *            which this projection uses.
    122         * @param falseNorthing
    123         *            in given units
    124         * @param falseEasting
    125         *            in given units
    126         * @param naturalOrigin
    127         *            in radians longitude, latitude.
    128         * @param units
    129         *            of the map projection
    130         * @param scale
    131         *            at the prime meridian (e.g. 0.9996 for UTM)
    132         * @param conformal
    133         *            if the projection is conformal
    134         * @param equalArea
    135         *            if the projection result in an equal area map
    136         * @param id
    137         *            an identifiable instance containing information about this projection.
    138         */
    139        public Projection( GeographicCRS geographicCRS, double falseNorthing, double falseEasting, Point2d naturalOrigin,
    140                           Unit units, double scale, boolean conformal, boolean equalArea, Identifiable id ) {
    141            super( id );
    142            this.scale = scale;
    143            this.conformal = conformal;
    144            this.equalArea = equalArea;
    145            this.geographicCRS = geographicCRS;
    146            this.falseNorthing = falseNorthing;
    147            this.falseEasting = falseEasting;
    148            this.units = units;
    149
    150            checkForNullObject( geographicCRS, "Projection", "geographicCRS" );
    151            checkForNullObject( geographicCRS.getGeodeticDatum(), "Projection", "geographicCRS.datum" );
    152            checkForNullObject( geographicCRS.getGeodeticDatum().getEllipsoid(), "Projection",
    153                                "geographicCRS.datum.ellipsoid" );
    154            checkForNullObject( naturalOrigin, "Projection", "naturalOrigin" );
    155            checkForNullObject( units, "Projection", "units" );
    156
    157            this.scaleFactor = scale * getSemiMajorAxis();
    158
    159            this.naturalOrigin = new Point2d( normalizeLongitude( naturalOrigin.x ), normalizeLatitude( naturalOrigin.y ) );
    160
    161            // uses different library
    162            // this.projectionLongitude = this.naturalOrigin.getX();
    163            // this.projectionLatitude = this.naturalOrigin.getY();
    164            this.projectionLongitude = this.naturalOrigin.x;
    165            this.projectionLatitude = this.naturalOrigin.y;
    166
    167            sinphi0 = Math.sin( projectionLatitude );
    168            cosphi0 = Math.cos( projectionLatitude );
    169
    170            isSpherical = geographicCRS.getGeodeticDatum().getEllipsoid().getEccentricity() < 0.0000001;
    171        }
    172
    173        /**
    174         * The actual transform method doing a projection from geographic coordinates to map coordinates.
    175         *
    176         * @param lambda
    177         *            the longitude
    178         * @param phi
    179         *            the latitude
    180         * @return the projected Point or Point(Double.NAN, Double.NAN) if an error occurred.
    181         * @throws ProjectionException
    182         *             if the given lamba and phi coordinates could not be projected to x and y.
    183         */
    184        public abstract Point2d doProjection( double lambda, double phi )
    185                                throws ProjectionException;
    186
    187        /**
    188         * Do an inverse projection from projected (map) coordinates to geographic coordinates.
    189         *
    190         * @param x
    191         *            coordinate on the map
    192         * @param y
    193         *            coordinate on the map
    194         * @return the projected Point with x = lambda and y = phi;
    195         * @throws ProjectionException
    196         *             if the given x and y coordinates could not be inverted to lambda and phi.
    197         */
    198        public abstract Point2d doInverseProjection( double x, double y )
    199                                throws ProjectionException;
    200
    201        /**
    202         * @return A deegree specific name which will be used for the export of a projection.
    203         */
    204        public abstract String getImplementationName();
    205
    206        /**
    207         * @return true if the projection projects conformal.
    208         */
    209        public final boolean isConformal() {
    210            return conformal;
    211        }
    212
    213        /**
    214         * @return true if the projection is projects equal Area.
    215         */
    216        public final boolean isEqualArea() {
    217            return equalArea;
    218        }
    219
    220        /**
    221         * @return the scale.
    222         */
    223        public final double getScale() {
    224            return scale;
    225        }
    226
    227        /**
    228         * Sets the old scale to the given scale, also adjusts the scaleFactor.
    229         *
    230         * @param scale
    231         *            the new scale
    232         */
    233        public void setScale( double scale ) {
    234            this.scale = scale;
    235            this.scaleFactor = scale * getSemiMajorAxis();
    236        }
    237
    238        /**
    239         * @return the scale*semimajor-axis, often revered to as R*k_0 in Snyder.
    240         */
    241        public final double getScaleFactor() {
    242            return scaleFactor;
    243        }
    244
    245        /**
    246         * @return the datum.
    247         */
    248        public final Datum getDatum() {
    249            return geographicCRS.getGeodeticDatum();
    250        }
    251
    252        /**
    253         * @return the falseEasting.
    254         */
    255        public final double getFalseEasting() {
    256            return falseEasting;
    257        }
    258
    259        /**
    260         * sets the false easting to given value. (Used in for example transverse mercator, while setting the utm zone).
    261         *
    262         * @param newFalseEasting
    263         *            the new false easting parameter.
    264         */
    265        public void setFalseEasting( double newFalseEasting ) {
    266            this.falseEasting = newFalseEasting;
    267        }
    268
    269        /**
    270         * @return the falseNorthing.
    271         */
    272        public final double getFalseNorthing() {
    273            return falseNorthing;
    274        }
    275
    276        /**
    277         * @return the naturalOrigin.
    278         */
    279        public final Point2d getNaturalOrigin() {
    280            return naturalOrigin;
    281        }
    282
    283        /**
    284         * @return the units.
    285         */
    286        public final Unit getUnits() {
    287            return units;
    288        }
    289
    290        /**
    291         * @return the primeMeridian of the datum.
    292         */
    293        public final PrimeMeridian getPrimeMeridian() {
    294            return geographicCRS.getGeodeticDatum().getPrimeMeridian();
    295        }
    296
    297        /**
    298         * @return the ellipsoid of the datum.
    299         */
    300        public final Ellipsoid getEllipsoid() {
    301            return geographicCRS.getGeodeticDatum().getEllipsoid();
    302        }
    303
    304        /**
    305         * @return the eccentricity of the ellipsoid of the datum.
    306         */
    307        public final double getEccentricity() {
    308            return geographicCRS.getGeodeticDatum().getEllipsoid().getEccentricity();
    309        }
    310
    311        /**
    312         * @return the eccentricity of the ellipsoid of the datum.
    313         */
    314        public final double getSquaredEccentricity() {
    315            return geographicCRS.getGeodeticDatum().getEllipsoid().getSquaredEccentricity();
    316        }
    317
    318        /**
    319         * @return the semiMajorAxis (a) of the ellipsoid of the datum.
    320         */
    321        public final double getSemiMajorAxis() {
    322            return geographicCRS.getGeodeticDatum().getEllipsoid().getSemiMajorAxis();
    323        }
    324
    325        /**
    326         * @return the semiMinorAxis (a) of the ellipsoid of the datum.
    327         */
    328        public final double getSemiMinorAxis() {
    329            return geographicCRS.getGeodeticDatum().getEllipsoid().getSemiMinorAxis();
    330        }
    331
    332        /**
    333         * @return true if the ellipsoid of the datum is a sphere and not an ellipse.
    334         */
    335        public final boolean isSpherical() {
    336            return isSpherical;
    337        }
    338
    339        /**
    340         * @return the projectionLatitude also known as central-latitude or latitude-of-origin, in Snyder referenced as
    341         *         phi_1 for azimuthal, phi_0 for other projections.
    342         */
    343        public final double getProjectionLatitude() {
    344            return projectionLatitude;
    345        }
    346
    347        /**
    348         * @return the projectionLongitude also known as projection-meridian or central-meridian, in Snyder referenced as
    349         *         lambda_0
    350         */
    351        public final double getProjectionLongitude() {
    352            return projectionLongitude;
    353        }
    354
    355        /**
    356         * @return the sinphi0, the sine of the projection latitude
    357         */
    358        public final double getSinphi0() {
    359            return sinphi0;
    360        }
    361
    362        /**
    363         * @return the cosphi0, the cosine of the projection latitude
    364         */
    365        public final double getCosphi0() {
    366            return cosphi0;
    367        }
    368
    369        /**
    370         * @return the geographicCRS.
    371         */
    372        public final GeographicCRS getGeographicCRS() {
    373            return geographicCRS;
    374        }
    375
    376        @Override
    377        public boolean equals( Object other ) {
    378            if ( other != null && other instanceof Projection ) {
    379                final Projection that = (Projection) other;
    380                return this.units.equals( that.units )
    381                       && Math.abs( ( this.projectionLatitude - that.projectionLatitude ) ) < EPS11
    382                       && Math.abs( ( this.projectionLongitude - that.projectionLongitude ) ) < EPS11
    383                       && Math.abs( ( this.falseNorthing - that.falseNorthing ) ) < EPS11
    384                       && Math.abs( ( this.falseEasting - that.falseEasting ) ) < EPS11
    385                       && Math.abs( ( this.scale - that.scale ) ) < EPS11 && ( this.conformal == that.conformal )
    386                       && ( this.equalArea == that.equalArea ) && this.getGeographicCRS().equals( that.getGeographicCRS() );
    387            }
    388            return false;
    389        }
    390
    391        @Override
    392        public String toString() {
    393            StringBuilder sb = new StringBuilder( super.toString() );
    394            sb.append( "\n - underlying-geographic-CRS: " ).append( geographicCRS );
    395            sb.append( "\n - units: " ).append( units );
    396            sb.append( "\n - projection-longitude: " ).append( projectionLongitude );
    397            sb.append( "\n - projection-latitude: " ).append( projectionLatitude );
    398            sb.append( "\n - is-spherical: " ).append( isSpherical() );
    399            sb.append( "\n - is-conformal: " ).append( isConformal() );
    400            sb.append( "\n - natural-origin: " ).append( getNaturalOrigin() );
    401            sb.append( "\n - false-easting: " ).append( getFalseEasting() );
    402            sb.append( "\n - false-northing: " ).append( getFalseNorthing() );
    403            sb.append( "\n - scale: " ).append( getScale() );
    404            return sb.toString();
    405
    406        }
    407
    408        /**
    409         * Implementation as proposed by Joshua Block in Effective Java (Addison-Wesley 2001), which supplies an even
    410         * distribution and is relatively fast. It is created from field <b>f</b> as follows:
    411         * <ul>
    412         * <li>boolean -- code = (f ? 0 : 1)</li>
    413         * <li>byte, char, short, int -- code = (int)f</li>
    414         * <li>long -- code = (int)(f ^ (f &gt;&gt;&gt;32))</li>
    415         * <li>float -- code = Float.floatToIntBits(f);</li>
    416         * <li>double -- long l = Double.doubleToLongBits(f); code = (int)(l ^ (l &gt;&gt;&gt; 32))</li>
    417         * <li>all Objects, (where equals(&nbsp;) calls equals(&nbsp;) for this field) -- code = f.hashCode(&nbsp;)</li>
    418         * <li>Array -- Apply above rules to each element</li>
    419         * </ul>
    420         * <p>
    421         * Combining the hash code(s) computed above: result = 37 * result + code;
    422         * </p>
    423         *
    424         * @return (int) ( result >>> 32 ) ^ (int) result;
    425         *
    426         * @see java.lang.Object#hashCode()
    427         */
    428        @Override
    429        public int hashCode() {
    430            // the 2nd millionth prime, :-)
    431            long code = 32452843;
    432            if ( units != null ) {
    433                code = code * 37 + units.hashCode();
    434            }
    435
    436            long tmp = Double.doubleToLongBits( projectionLatitude );
    437            code = code * 37 + (int) ( tmp ^ ( tmp >>> 32 ) );
    438
    439            tmp = Double.doubleToLongBits( projectionLongitude );
    440            code = code * 37 + (int) ( tmp ^ ( tmp >>> 32 ) );
    441
    442            tmp = Double.doubleToLongBits( falseNorthing );
    443            code = code * 37 + (int) ( tmp ^ ( tmp >>> 32 ) );
    444
    445            tmp = Double.doubleToLongBits( falseEasting );
    446            code = code * 37 + (int) ( tmp ^ ( tmp >>> 32 ) );
    447
    448            tmp = Double.doubleToLongBits( scale );
    449            code = code * 37 + (int) ( tmp ^ ( tmp >>> 32 ) );
    450
    451            code = code * 37 + ( conformal ? 0 : 1 );
    452            code = code * 37 + ( equalArea ? 0 : 1 );
    453            if ( geographicCRS != null ) {
    454                code = code * 37 + geographicCRS.hashCode();
    455            }
    456
    457            return (int) ( code >>> 32 ) ^ (int) code;
    458        }
    459    }