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