001 //$HeadURL: http://svn.wald.intevation.org/svn/deegree/base/trunk/src/org/deegree/crs/transformations/coordinate/GeocentricTransform.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.transformations.coordinate;
038
039 import static org.deegree.crs.projections.ProjectionUtils.EPS11;
040 import static org.deegree.crs.projections.ProjectionUtils.length;
041
042 import java.util.ArrayList;
043 import java.util.List;
044
045 import javax.vecmath.Point3d;
046
047 import org.deegree.crs.Identifiable;
048 import org.deegree.crs.components.Ellipsoid;
049 import org.deegree.crs.components.Unit;
050 import org.deegree.crs.coordinatesystems.CompoundCRS;
051 import org.deegree.crs.coordinatesystems.CoordinateSystem;
052 import org.deegree.crs.coordinatesystems.GeocentricCRS;
053 import org.deegree.framework.log.ILogger;
054 import org.deegree.framework.log.LoggerFactory;
055
056 /**
057 * The <code>GeocentricTransform</code> class is used to create a transformation between a geocentric CRS (having
058 * lat-lon coordinates) and it's geodetic CRS (having x-y-z) coordinates and vice versa.
059 *
060 * @author <a href="mailto:bezema@lat-lon.de">Rutger Bezema</a>
061 *
062 * @author last edited by: $Author: rbezema $
063 *
064 * @version $Revision: 19653 $, $Date: 2009-09-15 14:56:30 +0200 (Tue, 15 Sep 2009) $
065 *
066 */
067
068 public class GeocentricTransform extends CRSTransformation {
069
070 private static final long serialVersionUID = -5306062355334449324L;
071
072 private static ILogger LOG = LoggerFactory.getLogger( GeocentricTransform.class );
073
074 /**
075 * Cosine of 67.5 degrees.
076 */
077 private static final double COS_67P5 = 0.38268343236508977;
078
079 /**
080 * Toms region 1 constant, which will allow for a difference between ellipsoid of 2000 Kilometers. <quote>Under this
081 * policy the maximum error is less than 42 centimeters for altitudes less then 10.000.000 Kilometers</quote>
082 */
083 private static final double AD_C = 1.0026;
084
085 /**
086 * Semi-major axis of ellipsoid in meters.
087 */
088 private double semiMajorAxis;
089
090 /**
091 * Semi-minor axis of ellipsoid in meters.
092 */
093 private double semiMinorAxis;
094
095 /**
096 * Square of semi-major axis {@link #semiMajorAxis}.
097 */
098 private double squaredSemiMajorAxis;
099
100 /**
101 * Square of semi-minor axis {@link #semiMinorAxis}.
102 */
103 private double squaredSemiMinorAxis;
104
105 /**
106 * Eccentricity squared.
107 */
108 private double squaredEccentricity;
109
110 /**
111 * 2nd eccentricity squared.
112 */
113 private double ep2;
114
115 /**
116 * true if the given points will use heights (e.g. have a z/height-value).
117 */
118 private boolean hasHeight;
119
120 private double defaultHeightValue;
121
122 /**
123 * @param source
124 * the geographic crs.
125 * @param target
126 * the geocentric crs.
127 * @param id
128 * an identifiable instance containing information about this transformation
129 */
130 public GeocentricTransform( CoordinateSystem source, GeocentricCRS target, Identifiable id ) {
131 super( source, target, id );
132 calcParams();
133 }
134
135 private void calcParams() {
136 this.hasHeight = ( getSourceCRS().getType() == CoordinateSystem.COMPOUND_CRS );
137 defaultHeightValue = ( hasHeight ) ? ( (CompoundCRS) getSourceCRS() ).getDefaultHeight() : 0;
138 Ellipsoid ellipsoid = getSourceCRS().getGeodeticDatum().getEllipsoid();
139 semiMajorAxis = Unit.METRE.convert( ellipsoid.getSemiMajorAxis(), ellipsoid.getUnits() );
140 semiMinorAxis = Unit.METRE.convert( ellipsoid.getSemiMinorAxis(), ellipsoid.getUnits() );
141 squaredSemiMajorAxis = semiMajorAxis * semiMajorAxis;
142 squaredSemiMinorAxis = semiMinorAxis * semiMinorAxis;
143 squaredEccentricity = ellipsoid.getSquaredEccentricity();
144 // e2 = ( a2 - b2 ) / a2;
145 ep2 = ( squaredSemiMajorAxis - squaredSemiMinorAxis ) / squaredSemiMinorAxis;
146 }
147
148 @Override
149 public void inverse() {
150 super.inverse();
151 calcParams();
152 }
153
154 /**
155 * @param source
156 * the geographic crs.
157 * @param target
158 * the geocentric crs.
159 */
160 public GeocentricTransform( CoordinateSystem source, GeocentricCRS target ) {
161 this( source, target, new Identifiable( createFromTo( source.getIdentifier(), target.getIdentifier() ) ) );
162 }
163
164 @Override
165 public List<Point3d> doTransform( List<Point3d> srcPts ) {
166 List<Point3d> result = new ArrayList<Point3d>( srcPts );
167 if ( LOG.isDebug() ) {
168 StringBuilder sb = new StringBuilder( isInverseTransform() ? "An inverse " : "A " );
169 sb.append( getImplementationName() );
170 sb.append( " with incoming points: " );
171 sb.append( srcPts );
172 LOG.logDebug( sb.toString() );
173 }
174 if ( isInverseTransform() ) {
175 toGeographic( result );
176 } else {
177 toGeoCentric( result );
178 }
179 return result;
180 }
181
182 /**
183 * Converts geocentric coordinates (x, y, z) to geodetic coordinates (longitude, latitude, height), according to the
184 * current ellipsoid parameters.
185 * <p>
186 * The method used here is derived from "An Improved Algorithm for Geocentric to Geodetic Coordinate Conversion", by
187 * Ralph Toms, Feb 1996 UCRL-JC-123138.
188 *
189 * @param srcPts
190 * the points which must be transformed.
191 */
192 protected void toGeographic( List<Point3d> srcPts ) {
193 for ( Point3d p : srcPts ) {
194 if ( LOG.isDebug() ) {
195 LOG.logDebug( "(incoming geocentric) x:" + p.x + ", y:" + p.y + ", z:" + p.z );
196 }
197 // Note: Variable names follow the notation used in Toms, Feb 1996
198
199 final double T0 = p.z * AD_C; // initial estimate of vertical component
200 final double W = length( p.x, p.y );// distance from Z axis
201 final double S0 = length( T0, W );// initial estimate of horizontal component
202
203 final double sin_B0 = T0 / S0; // sin(B0), B0 is estimate of Bowring variable
204 final double cos_B0 = W / S0; // cos(B0)
205 final double sin3_B0 = sin_B0 * sin_B0 * sin_B0; // cube of sin(B0)
206 final double T1 = p.z + semiMinorAxis * ep2 * sin3_B0; // corrected estimate of vertical component
207
208 // numerator of cos(phi1)
209 final double sum = W - semiMajorAxis * squaredEccentricity * ( cos_B0 * cos_B0 * cos_B0 );
210
211 // corrected estimate of horizontal component
212 final double S1 = length( T1, sum );// Math.sqrt( T1 * T1 + sum * sum );
213
214 // sin(phi), phi is estimated latitude
215 final double sinPhi = T1 / S1;
216 final double cosPhi = sum / S1; // cos(phi)
217
218 // Lambda in tom.
219 p.x = Math.atan2( p.y, p.x );// longitude;
220 p.y = Math.atan( sinPhi / cosPhi );// latitude;
221 if ( hasHeight ) {
222 double height = 1;
223 // rn = radius of curvature of the prime vertical, of the ellipsoid at location
224 final double rn = semiMajorAxis / Math.sqrt( 1 - squaredEccentricity * ( sinPhi * sinPhi ) );
225
226 if ( cosPhi >= +COS_67P5 ) {
227 height = W / +cosPhi - rn;
228 } else if ( cosPhi <= -COS_67P5 ) {
229 height = W / -cosPhi - rn;
230 } else {
231 height = p.z / sinPhi + rn * ( squaredEccentricity - 1.0 );
232 }
233 p.z = height;
234 } else {
235 p.z = Double.NaN;
236 }
237 if ( LOG.isDebug() ) {
238 LOG.logDebug( "(outgoing) lam:" + Math.toDegrees( p.x ) + ", phi:" + Math.toDegrees( p.y ) );
239 }
240 }
241 }
242
243 /**
244 * Converts geographic (longitude, latitude, height) to cartesian (x,y,z) coordinates.
245 *
246 * @param srcPts
247 * to convert.
248 */
249 protected void toGeoCentric( List<Point3d> srcPts ) {
250 for ( Point3d p : srcPts ) {
251 if ( LOG.isDebug() ) {
252 LOG.logDebug( "(incoming geographic) lam:" + Math.toDegrees( p.x ) + ", phi:" + Math.toDegrees( p.y ) );
253 }
254 final double lambda = p.x; // Longitude
255 final double phi = p.y; // Latitude
256 // first check the p.z value if it is defined, if not, use the defaultheight value, which will be
257 // initialized with 0 or the configured compound crs value.
258 if ( Double.isNaN( p.z ) || Math.abs( p.z ) < EPS11 ) {
259 p.z = defaultHeightValue;
260 }
261 final double h = hasHeight ? p.z : 0; // Height above the ellipsoid (metres).
262
263 final double cosPhi = Math.cos( phi );
264 final double sinPhi = Math.sin( phi );
265 final double rn = semiMajorAxis / Math.sqrt( 1 - squaredEccentricity * ( sinPhi * sinPhi ) );
266
267 p.x = ( rn + h ) * cosPhi * Math.cos( lambda );
268 p.y = ( rn + h ) * cosPhi * Math.sin( lambda );
269 p.z = ( rn * ( 1 - squaredEccentricity ) + h ) * sinPhi;
270 if ( LOG.isDebug() ) {
271 LOG.logDebug( "(outgoing) x:" + p.x + ", y:" + p.y + ", z:" + p.z );
272 }
273 }
274 }
275
276 @Override
277 public boolean isIdentity() {
278 return false;
279 }
280
281 /**
282 * @return the semiMajorAxis.
283 */
284 public final double getSemiMajorAxis() {
285 return semiMajorAxis;
286 }
287
288 /**
289 * @return the semiMinorAxis.
290 */
291 public final double getSemiMinorAxis() {
292 return semiMinorAxis;
293 }
294
295 @Override
296 public String getImplementationName() {
297 return "Geocentric-Transform";
298 }
299
300 }