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 >>>32))</li>
415 * <li>float -- code = Float.floatToIntBits(f);</li>
416 * <li>double -- long l = Double.doubleToLongBits(f); code = (int)(l ^ (l >>> 32))</li>
417 * <li>all Objects, (where equals( ) calls equals( ) for this field) -- code = f.hashCode( )</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 }