001 //$HeadURL: svn+ssh://jwilden@svn.wald.intevation.org/deegree/base/branches/2.5_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 }