/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2001-2008, Open Source Geospatial Foundation (OSGeo)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* This package contains documentation from OpenGIS specifications.
* OpenGIS consortium's work is fully acknowledged here.
*/
package org.geotools.referencing.crs;
import java.util.Map;
import java.util.List;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.Collections;
import java.io.IOException;
import java.io.ObjectInputStream;
import org.opengis.referencing.crs.CompoundCRS;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.crs.SingleCRS;
import org.opengis.referencing.cs.CoordinateSystem;
import org.opengis.referencing.datum.Datum;
import org.geotools.referencing.AbstractIdentifiedObject;
import org.geotools.referencing.AbstractReferenceSystem;
import org.geotools.referencing.cs.DefaultCompoundCS;
import org.geotools.referencing.wkt.Formatter;
import org.geotools.resources.UnmodifiableArrayList;
import org.geotools.resources.i18n.ErrorKeys;
import org.geotools.resources.i18n.Errors;
import org.geotools.util.CheckedCollection;
/**
* A coordinate reference system describing the position of points through two or more
* independent coordinate reference systems. Thus it is associated with two or more
* {@linkplain CoordinateSystem coordinate systems} and {@linkplain Datum datums} by
* defining the compound CRS as an ordered set of two or more instances of
* {@link CoordinateReferenceSystem}.
*
* @since 2.1
* @source $URL$
* @version $Id$
* @author Martin Desruisseaux (IRD)
*/
public class DefaultCompoundCRS extends AbstractCRS implements CompoundCRS {
/**
* Serial number for interoperability with different versions.
*/
private static final long serialVersionUID = -2656710314586929286L;
/**
* The coordinate reference systems in this compound CRS.
* May actually be a list of {@link SingleCRS}.
*/
private final List<? extends CoordinateReferenceSystem> crs;
/**
* A decomposition of the CRS list into the single elements. Computed
* by {@link #getElements} on construction or deserialization.
*/
private transient List<SingleCRS> singles;
/**
* Constructs a new compound CRS with the same values than the specified one.
* This copy constructor provides a way to wrap an arbitrary implementation into a
* Geotools one or a user-defined one (as a subclass), usually in order to leverage
* some implementation-specific API. This constructor performs a shallow copy,
* i.e. the properties are not cloned.
*
* @param crs The coordinate reference system to copy.
*
* @since 2.2
*/
public DefaultCompoundCRS(final CompoundCRS crs) {
super(crs);
if (crs instanceof DefaultCompoundCRS) {
final DefaultCompoundCRS that = (DefaultCompoundCRS) crs;
this.crs = that.crs;
this.singles = that.singles;
} else {
this.crs = copy(crs.getCoordinateReferenceSystems());
}
}
/**
* Constructs a coordinate reference system from a name and two CRS.
*
* @param name The name.
* @param head The head CRS.
* @param tail The tail CRS.
*/
public DefaultCompoundCRS(final String name,
final CoordinateReferenceSystem head,
final CoordinateReferenceSystem tail)
{
this(name, new CoordinateReferenceSystem[] {head, tail});
}
/**
* Constructs a coordinate reference system from a name and three CRS.
*
* @param name The name.
* @param head The head CRS.
* @param middle The middle CRS.
* @param tail The tail CRS.
*/
public DefaultCompoundCRS(final String name,
final CoordinateReferenceSystem head,
final CoordinateReferenceSystem middle,
final CoordinateReferenceSystem tail)
{
this(name, new CoordinateReferenceSystem[] {head, middle, tail});
}
/**
* Constructs a coordinate reference system from a name.
*
* @param name The name.
* @param crs The array of coordinate reference system making this compound CRS.
*/
public DefaultCompoundCRS(final String name, final CoordinateReferenceSystem[] crs) {
this(Collections.singletonMap(NAME_KEY, name), crs);
}
/**
* Constructs a coordinate reference system from a set of properties.
* The properties are given unchanged to the
* {@linkplain AbstractReferenceSystem#AbstractReferenceSystem(Map) super-class constructor}.
*
* @param properties Set of properties. Should contains at least {@code "name"}.
* @param crs The array of coordinate reference system making this compound CRS.
*/
public DefaultCompoundCRS(final Map<String,?> properties, CoordinateReferenceSystem[] crs) {
super(properties, createCoordinateSystem(crs));
this.crs = copy(Arrays.asList(crs));
}
/**
* Returns a compound coordinate system for the specified array of CRS objects.
* This method is a work around for RFE #4093999 in Sun's bug database
* ("Relax constraint on placement of this()/super() call in constructors").
*/
private static CoordinateSystem createCoordinateSystem(final CoordinateReferenceSystem[] crs) {
ensureNonNull("crs", crs);
if (crs.length < 2) {
throw new IllegalArgumentException(Errors.format(
ErrorKeys.MISSING_PARAMETER_$1, "crs[" + crs.length + ']'));
}
final CoordinateSystem[] cs = new CoordinateSystem[crs.length];
for (int i=0; i<crs.length; i++) {
ensureNonNull("crs", crs, i);
cs[i] = crs[i].getCoordinateSystem();
}
return new DefaultCompoundCS(cs);
}
/**
* Returns an unmodifiable copy of the given list. As a side effect, this method computes the
* {@linkplain singles} list. If it appears that the list of {@code SingleCRS} is equals to the
* given list, then it is returned in other to share the same list in both {@link #crs} and
* {@link #singles} references.
* <p>
* <strong>WARNING:</strong> this method is invoked by constructors <em>before</em>
* the {@linkplain #crs} field is set. Do not use this field.
*/
private List<? extends CoordinateReferenceSystem> copy(
List<? extends CoordinateReferenceSystem> crs)
{
if (computeSingleCRS(crs)) {
crs = singles; // Shares the same list.
} else {
crs = UnmodifiableArrayList.wrap(crs.toArray(new CoordinateReferenceSystem[crs.size()]));
}
return crs;
}
/**
* The ordered list of coordinate reference systems.
*
* @return The coordinate reference systems as an unmodifiable list.
*/
@SuppressWarnings("unchecked") // We are safe if the list is read-only.
public List<CoordinateReferenceSystem> getCoordinateReferenceSystems() {
return (List) crs;
}
/**
* Returns the ordered list of single coordinate reference systems. If this compound CRS
* contains other compound CRS, all of them are expanded in an array of {@code SingleCRS}
* objects.
*
* @return The single coordinate reference systems as an unmodifiable list.
*/
public List<SingleCRS> getSingleCRS() {
return singles;
}
/**
* Returns the ordered list of single coordinate reference systems for the specified CRS.
* The specified CRS doesn't need to be a Geotools implementation.
*
* @param crs The coordinate reference system.
* @return The single coordinate reference systems.
* @throws ClassCastException if a CRS is neither a {@link SingleCRS} or a {@link CompoundCRS}.
*/
public static List<SingleCRS> getSingleCRS(final CoordinateReferenceSystem crs) {
final List<SingleCRS> singles;
if (crs instanceof DefaultCompoundCRS) {
singles = ((DefaultCompoundCRS) crs).getSingleCRS();
} else if (crs instanceof CompoundCRS) {
final List<CoordinateReferenceSystem> elements =
((CompoundCRS) crs).getCoordinateReferenceSystems();
singles = new ArrayList<SingleCRS>(elements.size());
getSingleCRS(elements, singles);
} else {
singles = Collections.singletonList((SingleCRS) crs);
}
return singles;
}
/**
* Recursively adds all {@link SingleCRS} in the specified list.
*
* @throws ClassCastException if a CRS is neither a {@link SingleCRS} or a {@link CompoundCRS}.
*/
private static boolean getSingleCRS(
final List<? extends CoordinateReferenceSystem> source, final List<SingleCRS> target)
{
boolean identical = true;
for (final CoordinateReferenceSystem candidate : source) {
if (candidate instanceof CompoundCRS) {
getSingleCRS(((CompoundCRS) candidate).getCoordinateReferenceSystems(), target);
identical = false;
} else {
target.add((SingleCRS) candidate);
}
}
return identical;
}
/**
* Computes the {@link #singles} field from the given CRS list and returns {@code true}
* if it has the same content.
*/
private boolean computeSingleCRS(List<? extends CoordinateReferenceSystem> crs) {
singles = new ArrayList<SingleCRS>(crs.size());
final boolean identical = getSingleCRS(crs, singles);
singles = UnmodifiableArrayList.wrap(singles.toArray(new SingleCRS[singles.size()]));
return identical;
}
/**
* Computes the single CRS on deserialization.
*/
@SuppressWarnings("unchecked")
private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
if (crs instanceof CheckedCollection) {
final Class<?> type = ((CheckedCollection) crs).getElementType();
if (SingleCRS.class.isAssignableFrom(type)) {
singles = (List) crs;
return;
}
}
computeSingleCRS(crs);
}
/**
* Compares this coordinate reference system with the specified object for equality.
*
* @param object The object to compare to {@code this}.
* @param compareMetadata {@code true} for performing a strict comparaison, or
* {@code false} for comparing only properties relevant to transformations.
* @return {@code true} if both objects are equal.
*/
@Override
public boolean equals(final AbstractIdentifiedObject object, final boolean compareMetadata) {
if (object == this) {
return true; // Slight optimization.
}
if (super.equals(object, compareMetadata)) {
final DefaultCompoundCRS that = (DefaultCompoundCRS) object;
return equals(this.crs, that.crs, compareMetadata);
}
return false;
}
/**
* Returns a hash value for this compound CRS.
*
* @return The hash code value. This value doesn't need to be the same
* in past or future versions of this class.
*/
@Override
public int hashCode() {
// Don't call superclass method since 'coordinateSystem' and 'datum' may be null.
return crs.hashCode() ^ (int)serialVersionUID;
}
/**
* Format the inner part of a
* <A HREF="http://geoapi.sourceforge.net/snapshot/javadoc/org/opengis/referencing/doc-files/WKT.html"><cite>Well
* Known Text</cite> (WKT)</A> element.
*
* @param formatter The formatter to use.
* @return The name of the WKT element type, which is {@code "COMPD_CS"}.
*/
@Override
protected String formatWKT(final Formatter formatter) {
for (final CoordinateReferenceSystem element : crs) {
formatter.append(element);
}
return "COMPD_CS";
}
}