001: /*
002: * GeoTools - OpenSource mapping toolkit
003: * http://geotools.org
004: * (C) 2004-2006, GeoTools Project Managment Committee (PMC)
005: * (C) 2004, Institut de Recherche pour le Développement
006: *
007: * This library is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU Lesser General Public
009: * License as published by the Free Software Foundation;
010: * version 2.1 of the License.
011: *
012: * This library is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: *
017: * This package contains documentation from OpenGIS specifications.
018: * OpenGIS consortium's work is fully acknowledged here.
019: */
020: package org.geotools.referencing.cs;
021:
022: // J2SE dependencies and extensions
023: import java.util.Map;
024: import java.util.Arrays;
025: import java.util.HashMap;
026: import java.util.Collections;
027: import javax.units.SI;
028: import javax.units.NonSI;
029: import javax.units.Unit;
030: import javax.units.Converter;
031: import javax.units.ConversionException;
032:
033: // OpenGIS dependencies
034: import org.opengis.referencing.cs.AxisDirection;
035: import org.opengis.referencing.cs.CoordinateSystem;
036: import org.opengis.referencing.cs.CoordinateSystemAxis;
037: import org.opengis.referencing.operation.Matrix;
038: import org.opengis.geometry.MismatchedDimensionException;
039: import org.opengis.util.InternationalString;
040:
041: // Geotools dependencies
042: import org.geotools.measure.Measure;
043: import org.geotools.referencing.AbstractIdentifiedObject;
044: import org.geotools.referencing.operation.matrix.GeneralMatrix;
045: import org.geotools.referencing.wkt.Formatter;
046: import org.geotools.resources.Utilities;
047: import org.geotools.resources.i18n.Errors;
048: import org.geotools.resources.i18n.ErrorKeys;
049: import org.geotools.resources.i18n.Vocabulary;
050:
051: /**
052: * The set of coordinate system axes that spans a given coordinate space. A coordinate system (CS)
053: * is derived from a set of (mathematical) rules for specifying how coordinates in a given space
054: * are to be assigned to points. The coordinate values in a coordinate tuple shall be recorded in
055: * the order in which the coordinate system axes are recorded, whenever those
056: * coordinates use a coordinate reference system that uses this coordinate system.
057: * <p>
058: * This class is conceptually <cite>abstract</cite>, even if it is technically possible to
059: * instantiate it. Typical applications should create instances of the most specific subclass with
060: * {@code Default} prefix instead. An exception to this rule may occurs when it is not possible to
061: * identify the exact type. For example it is not possible to infer the exact coordinate system from
062: * <A HREF="http://geoapi.sourceforge.net/snapshot/javadoc/org/opengis/referencing/doc-files/WKT.html"><cite>Well
063: * Known Text</cite></A> is some cases (e.g. in a {@code LOCAL_CS} element). In such exceptional
064: * situation, a plain {@code AbstractCS} object may be instantiated.
065: *
066: * @since 2.1
067: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/library/referencing/src/main/java/org/geotools/referencing/cs/AbstractCS.java $
068: * @version $Id: AbstractCS.java 25485 2007-05-11 19:12:35Z desruisseaux $
069: * @author Martin Desruisseaux
070: *
071: * @see DefaultCoordinateSystemAxis
072: * @see javax.units.Unit
073: * @see org.geotools.referencing.datum.AbstractDatum
074: * @see org.geotools.referencing.crs.AbstractCRS
075: */
076: public class AbstractCS extends AbstractIdentifiedObject implements
077: CoordinateSystem {
078: /**
079: * Serial number for interoperability with different versions.
080: */
081: private static final long serialVersionUID = 6757665252533744744L;
082:
083: /**
084: * Base axis to use for checking directions. This is used in order to trap
085: * inconsistency like an axis named "Northing" with South direction.
086: */
087: private static final DefaultCoordinateSystemAxis[] DIRECTION_CHECKS = {
088: DefaultCoordinateSystemAxis.NORTHING,
089: DefaultCoordinateSystemAxis.EASTING,
090: DefaultCoordinateSystemAxis.SOUTHING,
091: DefaultCoordinateSystemAxis.WESTING };
092:
093: /**
094: * The axis for this coordinate system at the specified dimension.
095: */
096: private final CoordinateSystemAxis[] axis;
097:
098: /**
099: * The unit for measuring distance in this coordinate system, or {@code null} if none.
100: * Will be computed only when first needed.
101: */
102: private transient Unit distanceUnit;
103:
104: /**
105: * Constructs a new coordinate system with the same values than the specified one.
106: * This copy constructor provides a way to wrap an arbitrary implementation into a
107: * Geotools one or a user-defined one (as a subclass), usually in order to leverage
108: * some implementation-specific API. This constructor performs a shallow copy,
109: * i.e. the properties are not cloned.
110: *
111: * @since 2.2
112: */
113: public AbstractCS(final CoordinateSystem cs) {
114: super (cs);
115: if (cs instanceof AbstractCS) {
116: axis = ((AbstractCS) cs).axis;
117: } else {
118: axis = new CoordinateSystemAxis[cs.getDimension()];
119: for (int i = 0; i < axis.length; i++) {
120: axis[i] = cs.getAxis(i);
121: }
122: }
123: }
124:
125: /**
126: * Constructs a coordinate system from a name.
127: *
128: * @param name The coordinate system name.
129: * @param axis The set of axis.
130: */
131: public AbstractCS(final String name,
132: final CoordinateSystemAxis[] axis) {
133: this (Collections.singletonMap(NAME_KEY, name), axis);
134: }
135:
136: /**
137: * Constructs a coordinate system from a set of properties. The properties map is given
138: * unchanged to the {@linkplain AbstractIdentifiedObject#AbstractIdentifiedObject(Map)
139: * super-class constructor}.
140: *
141: * @param properties Set of properties. Should contains at least <code>"name"</code>.
142: * @param axis The set of axis.
143: */
144: public AbstractCS(final Map properties,
145: final CoordinateSystemAxis[] axis) {
146: super (properties);
147: ensureNonNull("axis", axis);
148: this .axis = (CoordinateSystemAxis[]) axis.clone();
149: for (int i = 0; i < axis.length; i++) {
150: ensureNonNull("axis", axis, i);
151: final AxisDirection direction = axis[i].getDirection();
152: ensureNonNull("direction", direction);
153: /*
154: * Ensures that axis direction and units are compatible with the
155: * coordinate system to be created. For example CartesianCS will
156: * accepts only linear or dimensionless units.
157: */
158: if (!isCompatibleDirection(direction)) {
159: // TOOD: localize name()
160: throw new IllegalArgumentException(Errors.format(
161: ErrorKeys.ILLEGAL_AXIS_ORIENTATION_$2,
162: direction.name(), Utilities
163: .getShortClassName(this )));
164: }
165: final Unit unit = axis[i].getUnit();
166: ensureNonNull("unit", unit);
167: if (!isCompatibleUnit(direction, unit)) {
168: throw new IllegalArgumentException(Errors.format(
169: ErrorKeys.INCOMPATIBLE_UNIT_$1, unit));
170: }
171: /*
172: * Ensures there is no axis along the same direction
173: * (e.g. two North axis, or an East and a West axis).
174: */
175: final AxisDirection check = direction.absolute();
176: if (!check.equals(AxisDirection.OTHER)) {
177: for (int j = i; --j >= 0;) {
178: if (check.equals(axis[j].getDirection().absolute())) {
179: // TODO: localize name()
180: final String nameI = axis[i].getDirection()
181: .name();
182: final String nameJ = axis[j].getDirection()
183: .name();
184: throw new IllegalArgumentException(Errors
185: .format(ErrorKeys.COLINEAR_AXIS_$2,
186: nameI, nameJ));
187: }
188: }
189: }
190: /*
191: * Checks for some inconsistency in naming and direction. For example if the axis
192: * is named "Northing", then the direction must be North. Exceptions to this rule
193: * are the directions along a meridian from a pole. For example a "Northing" axis
194: * may have a "South along 180 deg" direction.
195: */
196: final String name = axis[i].getName().getCode();
197: for (int j = 0; j < DIRECTION_CHECKS.length; j++) {
198: final DefaultCoordinateSystemAxis candidate = DIRECTION_CHECKS[j];
199: if (candidate.nameMatches(name)) {
200: final AxisDirection expected = candidate
201: .getDirection();
202: if (!direction.equals(expected)) {
203: DirectionAlongMeridian m = DirectionAlongMeridian
204: .parse(direction);
205: /*
206: * Note: for the check below, maybe it would have be nice to use:
207: *
208: * if (m == null || m.baseDirection.equals(expected.opposite())
209: *
210: * but the EPSG database contains many axis named "Northing" with
211: * direction like "South along 180 deg", so it doesn't seem to be
212: * considered as a contradiction...
213: */
214: if (m == null) {
215: throw new IllegalArgumentException(
216: Errors
217: .format(
218: ErrorKeys.INCONSISTENT_AXIS_ORIENTATION_$2,
219: name, direction
220: .name()));
221: }
222: }
223: }
224: }
225: }
226: }
227:
228: /**
229: * Creates a name for the predefined constants in subclasses. The name is an unlocalized String
230: * object. However, since this method is used for creation of convenience objects only (not for
231: * objects created from an "official" database), the "unlocalized" name is actually choosen
232: * according the user's locale at class initialization time. The same name is also added in
233: * a localizable form as an alias. Since the {@link #nameMatches} convenience method checks
234: * the alias, it still possible to consider two objects are equivalent even if their names
235: * were formatted in different locales.
236: */
237: static Map name(final int key) {
238: final Map properties = new HashMap(4);
239: final InternationalString name = Vocabulary
240: .formatInternational(key);
241: properties.put(NAME_KEY, name.toString());
242: properties.put(ALIAS_KEY, name);
243: return properties;
244: }
245:
246: /**
247: * Returns {@code true} if the specified axis direction is allowed for this coordinate
248: * system. This method is invoked at construction time for checking argument validity.
249: * The default implementation returns {@code true} for all axis directions. Subclasses
250: * will overrides this method in order to put more restrictions on allowed axis directions.
251: */
252: protected boolean isCompatibleDirection(
253: final AxisDirection direction) {
254: return true;
255: }
256:
257: /**
258: * Returns {@code true} is the specified unit is legal for the specified axis direction.
259: * This method is invoked at construction time for checking units compatibility. The default
260: * implementation returns {@code true} in all cases. Subclasses can override this method and
261: * check for compatibility with {@linkplain SI#METER meter} or
262: * {@linkplain NonSI#DEGREE_ANGLE degree} units.
263: *
264: * @since 2.2
265: */
266: protected boolean isCompatibleUnit(final AxisDirection direction,
267: final Unit unit) {
268: return true;
269: }
270:
271: /**
272: * Returns the dimension of the coordinate system.
273: * This is the number of axis.
274: */
275: public int getDimension() {
276: return axis.length;
277: }
278:
279: /**
280: * Returns the axis for this coordinate system at the specified dimension.
281: *
282: * @param dimension The zero based index of axis.
283: * @return The axis at the specified dimension.
284: * @throws IndexOutOfBoundsException if {@code dimension} is out of bounds.
285: */
286: public CoordinateSystemAxis getAxis(final int dimension)
287: throws IndexOutOfBoundsException {
288: return axis[dimension];
289: }
290:
291: /**
292: * Returns the axis direction for the specified coordinate system.
293: *
294: * @param cs The coordinate system.
295: * @return The axis directions for the specified coordinate system.
296: */
297: private static AxisDirection[] getAxisDirections(
298: final CoordinateSystem cs) {
299: final AxisDirection[] axis = new AxisDirection[cs
300: .getDimension()];
301: for (int i = 0; i < axis.length; i++) {
302: axis[i] = cs.getAxis(i).getDirection();
303: }
304: return axis;
305: }
306:
307: /**
308: * Returns an affine transform between two coordinate systems. Only units and
309: * axis order (e.g. transforming from
310: * ({@linkplain AxisDirection#NORTH NORTH},{@linkplain AxisDirection#WEST WEST}) to
311: * ({@linkplain AxisDirection#EAST EAST},{@linkplain AxisDirection#NORTH NORTH}
312: * are taken in account.
313: *
314: * <P><STRONG>Example:</STRONG> If coordinates in {@code sourceCS} are
315: * (<var>x</var>,<var>y</var>) pairs in metres and coordinates in {@code targetCS}
316: * are (-<var>y</var>,<var>x</var>) pairs in centimetres, then the transformation
317: * can be performed as below:</P>
318: *
319: * <pre><blockquote>
320: * [-y(cm)] [ 0 -100 0 ] [x(m)]
321: * [ x(cm)] = [ 100 0 0 ] [y(m)]
322: * [ 1 ] [ 0 0 1 ] [1 ]
323: * </blockquote></pre>
324: *
325: * @param sourceCS The source coordinate system.
326: * @param targetCS The target coordinate system.
327: * @return The conversion from {@code sourceCS} to {@code targetCS} as
328: * an affine transform. Only axis direction and units are taken in account.
329: * @throws IllegalArgumentException if axis doesn't matches, or the CS doesn't have the
330: * same geometry.
331: * @throws ConversionException if the unit conversion is non-linear.
332: */
333: public static Matrix swapAndScaleAxis(
334: final CoordinateSystem sourceCS,
335: final CoordinateSystem targetCS)
336: throws IllegalArgumentException, ConversionException {
337: if (!Utilities.sameInterfaces(sourceCS.getClass(), targetCS
338: .getClass(), CoordinateSystem.class)) {
339: throw new IllegalArgumentException(
340: Errors
341: .format(ErrorKeys.INCOMPATIBLE_COORDINATE_SYSTEM_TYPE));
342: }
343: final AxisDirection[] sourceAxis = getAxisDirections(sourceCS);
344: final AxisDirection[] targetAxis = getAxisDirections(targetCS);
345: final GeneralMatrix matrix = new GeneralMatrix(sourceAxis,
346: targetAxis);
347: assert Arrays.equals(sourceAxis, targetAxis) == matrix
348: .isIdentity() : matrix;
349: /*
350: * The previous code computed a matrix for swapping axis. Usually, this
351: * matrix contains only 0 and 1 values with only one "1" value by row.
352: * For example, the matrix operation for swapping x and y axis is:
353: *
354: * [y] [ 0 1 0 ] [x]
355: * [x] = [ 1 0 0 ] [y]
356: * [1] [ 0 0 1 ] [1]
357: *
358: * Now, take in account units conversions. Each matrix's element (j,i)
359: * is multiplied by the conversion factor from sourceCS.getUnit(i) to
360: * targetCS.getUnit(j). This is an element-by-element multiplication,
361: * not a matrix multiplication. The last column is processed in a special
362: * way, since it contains the offset values.
363: */
364: final int sourceDim = matrix.getNumCol() - 1;
365: final int targetDim = matrix.getNumRow() - 1;
366: assert sourceDim == sourceCS.getDimension() : sourceCS;
367: assert targetDim == targetCS.getDimension() : targetCS;
368: for (int j = 0; j < targetDim; j++) {
369: final Unit targetUnit = targetCS.getAxis(j).getUnit();
370: for (int i = 0; i < sourceDim; i++) {
371: final double element = matrix.getElement(j, i);
372: if (element == 0) {
373: // There is no dependency between source[i] and target[j]
374: // (i.e. axis are orthogonal).
375: continue;
376: }
377: final Unit sourceUnit = sourceCS.getAxis(i).getUnit();
378: if (Utilities.equals(sourceUnit, targetUnit)) {
379: // There is no units conversion to apply
380: // between source[i] and target[j].
381: continue;
382: }
383: final Converter converter = sourceUnit
384: .getConverterTo(targetUnit);
385: if (!converter.isLinear()) {
386: throw new ConversionException(Errors.format(
387: ErrorKeys.NON_LINEAR_UNIT_CONVERSION_$2,
388: sourceUnit, targetUnit));
389: }
390: final double offset = converter.convert(0);
391: final double scale = converter.derivative(0);
392: matrix.setElement(j, i, element * scale);
393: matrix.setElement(j, sourceDim, matrix.getElement(j,
394: sourceDim)
395: + element * offset);
396: }
397: }
398: return matrix;
399: }
400:
401: /**
402: * Returns a coordinate system with "standard" axis order and units.
403: * Most of the time, this method returns one of the predefined constants with axis in
404: * (<var>longitude</var>,<var>latitude</var>) or (<var>X</var>,<var>Y</var>) order,
405: * and units in degrees or metres. In some particular cases like
406: * {@linkplain org.opengis.referencing.cs.CartesianCS Cartesian CS}, this method may
407: * create a new instance on the fly. In every cases this method attempts to return a
408: * <A HREF="http://en.wikipedia.org/wiki/Right_hand_rule">right-handed</A> coordinate
409: * system, but this is not garanteed.
410: * <p>
411: * This method is typically used together with {@link #swapAndScaleAxis swapAndScaleAxis}
412: * for the creation of a transformation step before some
413: * {@linkplain org.opengis.referencing.operation.MathTransform math transform}.
414: * Example:
415: *
416: * <blockquote><pre>
417: * Matrix step1 = swapAndScaleAxis(sourceCS, standard(sourceCS));
418: * Matrix step2 = ... some transform operating on standard axis ...
419: * Matrix step3 = swapAndScaleAxis(standard(targetCS), targetCS);
420: * </pre></blockquote>
421: *
422: * A rational for standard axis order and units is explained in the <cite>Axis units and
423: * direction</cite> section in the {@linkplain org.geotools.referencing.operation.projection
424: * description of map projection package}.
425: *
426: * @param cs The coordinate system.
427: * @return A constant similar to the specified {@code cs} with "standard" axis.
428: * @throws IllegalArgumentException if the specified coordinate system is unknow to this method.
429: *
430: * @since 2.2
431: */
432: public static CoordinateSystem standard(final CoordinateSystem cs)
433: throws IllegalArgumentException {
434: return PredefinedCS.standard(cs);
435: }
436:
437: /**
438: * Suggests an unit for measuring distances in this coordinate system. The default
439: * implementation scans all {@linkplain CoordinateSystemAxis#getUnit axis units},
440: * ignoring angular ones (this also implies ignoring {@linkplain Unit#ONE dimensionless} ones).
441: * If more than one non-angular unit is found, the default implementation returns the "largest"
442: * one (e.g. kilometers instead of meters).
443: *
444: * @return Suggested distance unit.
445: * @throws ConversionException if some non-angular units are incompatibles.
446: */
447: final Unit getDistanceUnit() throws ConversionException {
448: Unit unit = distanceUnit; // Avoid the need for synchronization.
449: if (unit == null) {
450: for (int i = 0; i < axis.length; i++) {
451: final Unit candidate = axis[i].getUnit();
452: if (candidate != null
453: && !candidate.isCompatible(SI.RADIAN)) {
454: // TODO: checks the unit scale type (keeps RATIO only).
455: if (unit != null) {
456: final Converter converter = candidate
457: .getConverterTo(unit);
458: if (!converter.isLinear()) {
459: // TODO: use the localization provided in 'swapAxis'. We could also
460: // do a more intelligent work by checking the unit scale type.
461: throw new ConversionException(
462: "Unit conversion is non-linear");
463: }
464: if (Math.abs(converter.derivative(0)) <= 1) {
465: // The candidate is a "smaller" unit than the current one
466: // (e.g. "m" instead of "km"). Keeps the "largest" unit.
467: continue;
468: }
469: }
470: unit = candidate;
471: }
472: }
473: distanceUnit = unit;
474: }
475: return unit;
476: }
477:
478: /**
479: * Convenience method for checking object dimension validity.
480: *
481: * @param name The name of the argument to check.
482: * @param coordinates The coordinate array to check.
483: * @throws MismatchedDimensionException if the coordinate doesn't have the expected dimension.
484: */
485: final void ensureDimensionMatch(final String name,
486: final double[] coordinates)
487: throws MismatchedDimensionException {
488: if (coordinates.length != axis.length) {
489: throw new MismatchedDimensionException(Errors.format(
490: ErrorKeys.MISMATCHED_DIMENSION_$3, name,
491: new Integer(coordinates.length), new Integer(
492: axis.length)));
493: }
494: }
495:
496: /**
497: * Computes the distance between two points. This method is not available for all coordinate
498: * systems. For example, {@linkplain DefaultEllipsoidalCS ellipsoidal CS} doesn't have
499: * suffisient information.
500: *
501: * @param coord1 Coordinates of the first point.
502: * @param coord2 Coordinates of the second point.
503: * @return The distance between {@code coord1} and {@code coord2}.
504: * @throws UnsupportedOperationException if this coordinate system can't compute distances.
505: * @throws MismatchedDimensionException if a coordinate doesn't have the expected dimension.
506: *
507: * @todo Provides a localized message in the exception.
508: */
509: public Measure distance(final double[] coord1, final double[] coord2)
510: throws UnsupportedOperationException,
511: MismatchedDimensionException {
512: throw new UnsupportedOperationException();
513: }
514:
515: /**
516: * Returns all axis in the specified unit. This method is used for implementation of
517: * {@code usingUnit} methods in subclasses.
518: *
519: * @param unit The unit for the new axis.
520: * @return New axis using the specified unit, or {@code null} if current axis fits.
521: * @throws IllegalArgumentException If the specified unit is incompatible with the expected one.
522: *
523: * @see DefaultCartesianCS#usingUnit
524: * @see DefaultEllipsoidalCS#usingUnit
525: */
526: final CoordinateSystemAxis[] axisUsingUnit(final Unit unit)
527: throws IllegalArgumentException {
528: CoordinateSystemAxis[] newAxis = null;
529: for (int i = 0; i < axis.length; i++) {
530: CoordinateSystemAxis a = axis[i];
531: if (!unit.equals(a.getUnit())) {
532: DefaultCoordinateSystemAxis converted;
533: if (a instanceof DefaultCoordinateSystemAxis) {
534: converted = (DefaultCoordinateSystemAxis) a;
535: } else {
536: converted = new DefaultCoordinateSystemAxis(a);
537: a = converted; // For detecting changes.
538: }
539: converted = converted.usingUnit(unit);
540: if (converted != a) {
541: if (newAxis == null) {
542: newAxis = new CoordinateSystemAxis[axis.length];
543: System.arraycopy(axis, 0, newAxis, 0, i);
544: }
545: newAxis[i] = converted;
546: }
547: }
548: }
549: return newAxis;
550: }
551:
552: /**
553: * Returns every axis from the specified coordinate system as instance of
554: * {@link DefaultCoordinateSystemAxis}. This allow usage of some methods
555: * specific to that implementation.
556: */
557: private static DefaultCoordinateSystemAxis[] getDefaultAxis(
558: final CoordinateSystem cs) {
559: final DefaultCoordinateSystemAxis[] axis = new DefaultCoordinateSystemAxis[cs
560: .getDimension()];
561: for (int i = 0; i < axis.length; i++) {
562: final CoordinateSystemAxis a = cs.getAxis(i);
563: DefaultCoordinateSystemAxis c = DefaultCoordinateSystemAxis
564: .getPredefined(a);
565: if (c == null) {
566: if (a instanceof DefaultCoordinateSystemAxis) {
567: c = (DefaultCoordinateSystemAxis) a;
568: } else {
569: c = new DefaultCoordinateSystemAxis(a);
570: }
571: }
572: axis[i] = c;
573: }
574: return axis;
575: }
576:
577: /**
578: * Returns {@code true} if every axis in the specified {@code userCS} are colinear with axis
579: * in this coordinate system. The comparaison is insensitive to axis order and units. What
580: * matter is axis names (because they are fixed by ISO 19111 specification) and directions.
581: * <p>
582: * If this method returns {@code true}, then there is good chances that this CS can be used
583: * together with {@code userCS} as arguments to {@link #swapAndScaleAxis swapAndScaleAxis}.
584: * <p>
585: * This method should not be public because current implementation is not fully consistent
586: * for every pair of CS. It tries to check the opposite direction in addition of the usual
587: * one, but only a few pre-defined axis declare their opposite. This method should be okay
588: * when invoked on pre-defined CS declared in this package. {@link PredefinedCS} uses this
589: * method only that way.
590: */
591: final boolean axisColinearWith(final CoordinateSystem userCS) {
592: if (userCS.getDimension() != getDimension()) {
593: return false;
594: }
595: final DefaultCoordinateSystemAxis[] axis0 = getDefaultAxis(this );
596: final DefaultCoordinateSystemAxis[] axis1 = getDefaultAxis(userCS);
597: next: for (int i = 0; i < axis0.length; i++) {
598: final DefaultCoordinateSystemAxis direct = axis0[i];
599: final DefaultCoordinateSystemAxis opposite = direct
600: .getOpposite();
601: for (int j = 0; j < axis1.length; j++) {
602: final DefaultCoordinateSystemAxis candidate = axis1[j];
603: if (candidate != null) {
604: if (candidate.equals(direct, false, false)
605: || (opposite != null && candidate.equals(
606: opposite, false, false))) {
607: axis1[j] = null; // Flags as already compared.
608: continue next;
609: }
610: }
611: }
612: return false;
613: }
614: assert directionColinearWith(userCS);
615: return true;
616: }
617:
618: /**
619: * Compares directions only, without consideration for the axis name.
620: */
621: final boolean directionColinearWith(final CoordinateSystem userCS) {
622: if (userCS.getDimension() != axis.length) {
623: return false;
624: }
625: final AxisDirection[] checks = new AxisDirection[axis.length];
626: for (int i = 0; i < checks.length; i++) {
627: checks[i] = userCS.getAxis(i).getDirection().absolute();
628: }
629: next: for (int i = 0; i < axis.length; i++) {
630: final AxisDirection direction = axis[i].getDirection()
631: .absolute();
632: for (int j = 0; j < checks.length; j++) {
633: final AxisDirection candidate = checks[j];
634: if (candidate != null && candidate.equals(direction)) {
635: checks[j] = null; // Flags as already compared.
636: continue next;
637: }
638: }
639: return false;
640: }
641: return true;
642: }
643:
644: /**
645: * Compares the specified object with this coordinate system for equality.
646: *
647: * @param object The object to compare to {@code this}.
648: * @param compareMetadata {@code true} for performing a strict comparaison, or
649: * {@code false} for comparing only properties relevant to transformations.
650: * @return {@code true} if both objects are equal.
651: */
652: public boolean equals(final AbstractIdentifiedObject object,
653: final boolean compareMetadata) {
654: if (object == this ) {
655: return true; // Slight optimization.
656: }
657: if (super .equals(object, compareMetadata)) {
658: final AbstractCS that = (AbstractCS) object;
659: return equals(this .axis, that.axis, compareMetadata);
660: }
661: return false;
662: }
663:
664: /**
665: * Returns a hash value for this coordinate system.
666: *
667: * @return The hash code value. This value doesn't need to be the same
668: * in past or future versions of this class.
669: */
670: public int hashCode() {
671: int code = (int) serialVersionUID;
672: for (int i = 0; i < axis.length; i++) {
673: code = code * 37 + axis[i].hashCode();
674: }
675: return code;
676: }
677:
678: /**
679: * Format the inner part of a
680: * <A HREF="http://geoapi.sourceforge.net/snapshot/javadoc/org/opengis/referencing/doc-files/WKT.html"><cite>Well
681: * Known Text</cite> (WKT)</A> element. Note that WKT is not yet defined for coordinate system.
682: * Current implementation list the axis contained in this CS.
683: *
684: * @param formatter The formatter to use.
685: * @return The WKT element name. Current implementation default to the class name.
686: */
687: protected String formatWKT(final Formatter formatter) {
688: for (int i = 0; i < axis.length; i++) {
689: formatter.append(axis[i]);
690: }
691: formatter.setInvalidWKT(CoordinateSystem.class);
692: return super.formatWKT(formatter);
693: }
694: }
|