001 //$HeadURL: svn+ssh://jwilden@svn.wald.intevation.org/deegree/base/branches/2.5_testing/src/org/deegree/crs/coordinatesystems/CoordinateSystem.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.coordinatesystems;
038
039 import java.io.Serializable;
040 import java.util.LinkedList;
041 import java.util.List;
042
043 import javax.vecmath.Point3d;
044
045 import org.deegree.crs.Identifiable;
046 import org.deegree.crs.components.Axis;
047 import org.deegree.crs.components.Datum;
048 import org.deegree.crs.components.GeodeticDatum;
049 import org.deegree.crs.components.Unit;
050 import org.deegree.crs.transformations.polynomial.PolynomialTransformation;
051
052 /**
053 * Three kinds of <code>CoordinateSystem</code>s (in this class abbreviated with CRS) are supported in this lib.
054 * <ul>
055 * <li>Geographic CRS: A position (on the ellipsoid) is given in Lattitude / Longitude (Polar Cooridnates) given in rad°
056 * min''sec. The order of the position's coordinates are to be contrued to the axis order of the CRS. These lat/lon
057 * coordinates are to be tranformed to x,y,z values to define their location on the underlying datum.</li>
058 * <li>GeoCentric CRS: A position (on the ellipsoid) is given in x, y, z (cartesian) coordinates with the same units
059 * defined as the ones in the underlying datum. The order of the position's coordinates are to be contrued to the axis
060 * order of the datum.</li>
061 * <li>Projected CRS: The position (on the map) is given in a 2D-tuple in pre-defined units. The Axis of the CRS are
062 * defined (through a transformation) for an underlying Datum, which can have it's own axis with their own units. The
063 * order of the position's coordinates are to be contrued to the axis order of the CRS</li>
064 * </ul>
065 *
066 * Summarizing it can be said, that each CRS has following features
067 * <ul>
068 * <li>A reference code (an casesensitive String identifying this CRS, for example 'EPGS:4326' or
069 * 'urn:ogc:def:crs:OGC:2:84' or 'luref')</li>
070 * <li>An optional version.</li>
071 * <li>A humanly readable name.</li>
072 * <li>An optional description.</li>
073 * <li>An optional area of use, describing where this CRS is used.</li>
074 * <li>The order in which the axis of ther crs are defined.</li>
075 * <li>The underlying Datum</li>
076 *
077 * @author <a href="mailto:bezema@lat-lon.de">Rutger Bezema</a>
078 *
079 * @author last edited by: $Author: lbuesching $
080 *
081 * @version $Revision: 23399 $, $Date: 2010-04-07 10:05:58 +0200 (Mi, 07 Apr 2010) $
082 *
083 */
084
085 public abstract class CoordinateSystem extends Identifiable implements Serializable {
086
087 private static final long serialVersionUID = -1817981955729114332L;
088
089 private Axis[] axisOrder;
090
091 private Datum usedDatum;
092
093 private final List<PolynomialTransformation> transformations;
094
095 /**
096 * Defines this CRS as a GeoCentric one.
097 */
098 public static final int GEOCENTRIC_CRS = 0;
099
100 /**
101 * Defines this CRS as a Geographic one.
102 */
103 public static final int GEOGRAPHIC_CRS = 1;
104
105 /**
106 * Defines this CRS as a Projected one.
107 */
108 public static final int PROJECTED_CRS = 2;
109
110 /**
111 * Defines this CRS as a Compound one.
112 */
113 public static final int COMPOUND_CRS = 3;
114
115 /**
116 * Defines this CRS as a Vertical one.
117 */
118 public static final int VERTICAL_CRS = 4;
119
120 /**
121 * @param datum
122 * of this coordinate system.
123 * @param axisOrder
124 * the axisorder of this coordinate system.
125 * @param identity
126 */
127 public CoordinateSystem( Datum datum, Axis[] axisOrder, Identifiable identity ) {
128 this( null, datum, axisOrder, identity );
129 }
130
131 /**
132 * @param datum
133 * of this coordinate system.
134 * @param axisOrder
135 * the axisorder of this coordinate system.
136 * @param identifiers
137 * of this coordinate system.
138 * @param names
139 * @param versions
140 * @param descriptions
141 * @param areasOfUse
142 */
143 public CoordinateSystem( Datum datum, Axis[] axisOrder, String[] identifiers, String[] names, String[] versions,
144 String[] descriptions, String[] areasOfUse ) {
145 super( identifiers, names, versions, descriptions, areasOfUse );
146 this.axisOrder = axisOrder;
147 this.usedDatum = datum;
148 this.transformations = new LinkedList<PolynomialTransformation>();
149 }
150
151 /**
152 * @param transformations
153 * to use instead of the helmert transformation(s).
154 * @param geodeticDatum
155 * of this crs
156 * @param axisOrder
157 * @param identity
158 */
159 public CoordinateSystem( List<PolynomialTransformation> transformations, Datum geodeticDatum, Axis[] axisOrder,
160 Identifiable identity ) {
161 super( identity );
162 this.axisOrder = axisOrder;
163 this.usedDatum = geodeticDatum;
164 if ( transformations == null ) {
165 transformations = new LinkedList<PolynomialTransformation>();
166 }
167 this.transformations = transformations;
168 }
169
170 /**
171 * @return (all) axis' in their defined order.
172 */
173 public Axis[] getAxis() {
174 return axisOrder;
175 }
176
177 /**
178 * @return the usedDatum or <code>null</code> if the datum was not a Geodetic one.
179 */
180 public final GeodeticDatum getGeodeticDatum() {
181 return ( usedDatum instanceof GeodeticDatum ) ? (GeodeticDatum) usedDatum : null;
182 }
183
184 /**
185 * @return the datum of this coordinate system.
186 */
187 public final Datum getDatum() {
188 return usedDatum;
189 }
190
191 /**
192 * @return the units of all axis of the coordinatesystem.
193 */
194 public Unit[] getUnits() {
195 Axis[] allAxis = getAxis();
196 Unit[] result = new Unit[allAxis.length];
197 for ( int i = 0; i < allAxis.length; ++i ) {
198 result[i] = allAxis[i].getUnits();
199 }
200 return result;
201 }
202
203 /**
204 * @return the dimension of this CRS.
205 */
206 public abstract int getDimension();
207
208 /**
209 * @return one of the *_CRS types defined in this class.
210 */
211 public abstract int getType();
212
213 /**
214 * @param targetCRS
215 * to get the alternative Transformation for.
216 * @return true if this crs has an alternative transformation for the given coordinatesystem, false otherwise.
217 */
218 public boolean hasDirectTransformation( CoordinateSystem targetCRS ) {
219 if ( targetCRS == null ) {
220 return false;
221 }
222 for ( PolynomialTransformation transformation : transformations ) {
223 if ( transformation != null && targetCRS.equals( transformation.getTargetCRS() ) ) {
224 return true;
225 }
226 }
227 return false;
228 }
229
230 /**
231 * @param targetCRS
232 * to get the alternative transformation for.
233 * @return the transformation associated with the given crs, <code>null</code> otherwise.
234 */
235 public PolynomialTransformation getDirectTransformation( CoordinateSystem targetCRS ) {
236 if ( targetCRS == null ) {
237 return null;
238 }
239 for ( PolynomialTransformation transformation : transformations ) {
240 if ( transformation.getTargetCRS().equals( targetCRS ) ) {
241 return transformation;
242 }
243 }
244 return null;
245 }
246
247 /**
248 * Converts the given coordinates in given to the unit of the respective axis.
249 *
250 * @param coordinates
251 * to convert to.
252 * @param units
253 * in which the coordinates were given.
254 * @param invert
255 * if the operation should be inverted, e.g. the coordinates are given in the axis units and should be
256 * converted to the given units.
257 * @return the converted coordinates.
258 */
259 public Point3d convertToAxis( Point3d coordinates, Unit[] units, boolean invert ) {
260 if ( units != null && units.length < getDimension() && units.length > 0 ) {
261 Unit[] axisUnits = getUnits();
262 for ( int i = 0; i < axisUnits.length; i++ ) {
263 Unit axisUnit = axisUnits[i];
264 double value = ( i == 0 ) ? coordinates.x : ( i == 1 ) ? coordinates.y : coordinates.z;
265 if ( i < units.length ) {
266 Unit coordinateUnit = units[i];
267 if ( invert ) {
268 value = axisUnit.convert( value, coordinateUnit );
269 } else {
270 value = coordinateUnit.convert( value, axisUnit );
271 }
272 }
273 if ( i == 0 ) {
274 coordinates.x = value;
275 } else if ( i == 1 ) {
276 coordinates.y = value;
277 } else {
278 coordinates.z = value;
279 }
280 }
281 }
282 return coordinates;
283 }
284
285 /**
286 * Helper function to get the typename as a String.
287 *
288 * @return either the type as a name or 'Unknown' if the type is not known.
289 */
290 protected String getTypeName() {
291 switch ( getType() ) {
292 case GEOCENTRIC_CRS:
293 return "Geocentric CRS";
294 case PROJECTED_CRS:
295 return "Projected CRS";
296 case GEOGRAPHIC_CRS:
297 return "Geographic CRS";
298 case COMPOUND_CRS:
299 return "Compound CRS";
300 default:
301 return "Unknown CRS";
302 }
303 }
304
305 /**
306 * Checks if the given axis match this.axisOrder[] in length and order.
307 *
308 * @param otherAxis
309 * the axis to check
310 * @return true if the given axis match this.axisOrder[] false otherwise.
311 */
312 private boolean matchAxis( Axis[] otherAxis ) {
313 Axis[] allAxis = getAxis();
314 if ( otherAxis.length != allAxis.length ) {
315 return false;
316 }
317 for ( int i = 0; i < allAxis.length; ++i ) {
318 Axis a = allAxis[i];
319 Axis b = otherAxis[i];
320 if ( !a.equals( b ) ) {
321 return false;
322 }
323 }
324 return true;
325 }
326
327 @Override
328 public boolean equals( Object other ) {
329 if ( other != null && other instanceof CoordinateSystem ) {
330 final CoordinateSystem that = (CoordinateSystem) other;
331 return that.getType() == this.getType() && that.getDimension() == this.getDimension()
332 && matchAxis( that.getAxis() ) && super.equals( that )
333 && that.getGeodeticDatum().equals( this.getGeodeticDatum() );
334 }
335 return false;
336 }
337
338 /**
339 * Implementation as proposed by Joshua Block in Effective Java (Addison-Wesley 2001), which supplies an even
340 * distribution and is relatively fast. It is created from field <b>f</b> as follows:
341 * <ul>
342 * <li>boolean -- code = (f ? 0 : 1)</li>
343 * <li>byte, char, short, int -- code = (int)f</li>
344 * <li>long -- code = (int)(f ^ (f >>>32))</li>
345 * <li>float -- code = Float.floatToIntBits(f);</li>
346 * <li>double -- long l = Double.doubleToLongBits(f); code = (int)(l ^ (l >>> 32))</li>
347 * <li>all Objects, (where equals( ) calls equals( ) for this field) -- code = f.hashCode( )</li>
348 * <li>Array -- Apply above rules to each element</li>
349 * </ul>
350 * <p>
351 * Combining the hash code(s) computed above: result = 37 * result + code;
352 * </p>
353 *
354 * @return (int) ( result >>> 32 ) ^ (int) result;
355 *
356 * @see java.lang.Object#hashCode()
357 */
358 @Override
359 public int hashCode() {
360 // the 2.nd million th. prime, :-)
361 long code = 32452843;
362 if ( getAxis() != null ) {
363 for ( Axis ax : getAxis() ) {
364 code = code * 37 + ax.hashCode();
365 }
366 }
367 if ( usedDatum != null ) {
368 code = code * 37 + usedDatum.hashCode();
369 }
370 code = code * 37 + getType();
371 code = code * 37 + getDimension();
372 return (int) ( code >>> 32 ) ^ (int) code;
373 }
374
375 @Override
376 public String toString() {
377 StringBuilder sb = new StringBuilder( super.toString() );
378 sb.append( "\n - type: " ).append( getTypeName() );
379 sb.append( "\n - datum: " ).append( usedDatum );
380 sb.append( "\n - dimension: " ).append( getDimension() );
381 for ( Axis a : getAxis() ) {
382 sb.append( "\n - axis: " ).append( a.toString() );
383 }
384 return sb.toString();
385
386 }
387
388 /**
389 * @return the polynomial transformations.
390 */
391 public final List<PolynomialTransformation> getTransformations() {
392 return transformations;
393 }
394
395
396 /**
397 * Return the axis index associated with an easting value, if the axis could not be determined {@link Axis#AO_OTHER}
398 * 0 will be returned.
399 *
400 * @return the index of the axis which represents the easting/westing component of a coordinate tuple.
401 */
402 public int getEasting() {
403 Axis[] axis = getAxis();
404 for ( int i = 0; i < axis.length; ++i ) {
405 Axis a = axis[i];
406 if ( a != null ) {
407 if ( a.getOrientation() == Axis.AO_EAST || a.getOrientation() == Axis.AO_WEST ) {
408 return i;
409 }
410 }
411 }
412 return 0;
413 }
414
415 /**
416 * Return the axis index associated with a northing value, if the axis could not be determined (e.g not is
417 * {@link Axis#AO_NORTH} {@link Axis#AO_SOUTH} or {@link Axis#AO_UP} or {@link Axis#AO_DOWN}) 1 will be returned.
418 *
419 * @return the index of the axis which represents the easting/westing component of a coordinate tuple.
420 */
421 public int getNorthing() {
422 Axis[] axis = getAxis();
423 for ( int i = 0; i < axis.length; ++i ) {
424 Axis a = axis[i];
425 if ( a != null ) {
426 if ( a.getOrientation() == Axis.AO_NORTH || a.getOrientation() == Axis.AO_SOUTH
427 || a.getOrientation() == Axis.AO_DOWN || a.getOrientation() == Axis.AO_UP ) {
428 return i;
429 }
430 }
431 }
432 return 1;
433 }
434 }