001    //$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/branches/2.3_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    }