blob: 2b57f12c27748de185a2ddaf54704c9c203fe968 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.sis.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 javax.xml.bind.annotation.XmlType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import org.opengis.referencing.datum.Datum;
import org.opengis.referencing.crs.SingleCRS;
import org.opengis.referencing.crs.CompoundCRS;
import org.opengis.referencing.crs.GeodeticCRS;
import org.opengis.referencing.crs.ProjectedCRS;
import org.opengis.referencing.crs.EngineeringCRS;
import org.opengis.referencing.crs.VerticalCRS;
import org.opengis.referencing.crs.TemporalCRS;
import org.opengis.referencing.crs.ParametricCRS;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.cs.CoordinateSystem;
import org.apache.sis.referencing.cs.AxesConvention;
import org.apache.sis.referencing.cs.DefaultCompoundCS;
import org.apache.sis.referencing.AbstractReferenceSystem;
import org.apache.sis.referencing.IdentifiedObjects;
import org.apache.sis.internal.referencing.WKTKeywords;
import org.apache.sis.internal.referencing.Resources;
import org.apache.sis.internal.referencing.WKTUtilities;
import org.apache.sis.internal.referencing.ReferencingUtilities;
import org.apache.sis.internal.util.UnmodifiableArrayList;
import org.apache.sis.internal.jaxb.referencing.SC_CRS;
import org.apache.sis.util.collection.CheckedContainer;
import org.apache.sis.util.collection.Containers;
import org.apache.sis.util.resources.Errors;
import org.apache.sis.util.ArgumentChecks;
import org.apache.sis.util.ComparisonMode;
import org.apache.sis.util.Utilities;
import org.apache.sis.io.wkt.Formatter;
import org.apache.sis.io.wkt.Convention;
/**
* A CRS describing the position of points through two or more independent coordinate reference systems.
* This class is often used for defining 4-dimensional (<var>x</var>,<var>y</var>,<var>z</var>,<var>t</var>)
* coordinate reference systems as an aggregation of simpler CRS. Below is two examples of such aggregations:
*
* <div class="horizontal-flow">
* <div><p><b>Flat list</b></p>
* <blockquote>
* <code>CompoundCRS</code> — (<var>x</var>, <var>y</var>, <var>z</var>, <var>t</var>)<br>
* <code>  ├─ProjectedCRS</code> — (<var>x</var>, <var>y</var>)<br>
* <code>  ├─VerticalCRS</code> — (<var>z</var>)<br>
* <code>  └─TemporalCRS</code> — (<var>t</var>)
* </blockquote></div>
* <div><p><b>Hierarchical structure</b></p>
* <blockquote>
* <code>CompoundCRS</code> — (<var>x</var>, <var>y</var>, <var>z</var>, <var>t</var>)<br>
* <code>  ├─CompoundCRS</code> — (<var>x</var>, <var>y</var>, <var>z</var>)<br>
* <code>  │   ├─ProjectedCRS</code> — (<var>x</var>, <var>y</var>)<br>
* <code>  │   └─VerticalCRS</code> — (<var>z</var>)<br>
* <code>  └─TemporalCRS</code> — (<var>t</var>)
* </blockquote></div>
* </div>
*
* Strictly speaking, only the flat list on the left side is allowed by OGC/ISO specifications.
* However Apache SIS relaxes this rule by allowing hierarchies as shown on the right side. This
* flexibility allows SIS to preserve information about the (<var>x</var>,<var>y</var>,<var>z</var>)
* part (e.g. the EPSG identifier) that would otherwise been lost. Users can obtain the list of their
* choice by invoking {@link #getSingleComponents()} or {@link #getComponents()} respectively.
*
* <h2>Component order</h2>
* ISO 19162 restricts compound CRS to the following components in that order:
* <ul>
* <li>A mandatory horizontal CRS (only one of two-dimensional {@code GeographicCRS} or {@code ProjectedCRS} or {@code EngineeringCRS}).</li>
* <li>Optionally followed by a {@code VerticalCRS} or a {@code ParametricCRS} (but not both).</li>
* <li>Optionally followed by a {@code TemporalCRS}.</li>
* </ul>
*
* SIS currently does not enforce those restrictions. In particular:
* <ul>
* <li>Components may appear in different order.
* <li>{@code VerticalCRS} + {@code TemporalCRS} (without horizontal component) is accepted.</li>
* <li>{@code GeocentricCRS} or three-dimensional {@code GeographicCRS} can be combined with {@code TemporalCRS}.</li>
* </ul>
*
* However users are encouraged to follow ISO 19162 restriction for better portability.
*
* <h2>Immutability and thread safety</h2>
* This class is immutable and thus thread-safe if the property <em>values</em> (not necessarily the map itself)
* and all {@link CoordinateReferenceSystem} instances given to the constructor are also immutable.
* Unless otherwise noted in the javadoc, this condition holds if all components were created using only
* SIS factories and static constants.
*
* @author Martin Desruisseaux (IRD, Geomatys)
* @version 0.8
*
* @see org.apache.sis.referencing.factory.GeodeticAuthorityFactory#createCompoundCRS(String)
*
* @since 0.4
* @module
*/
@XmlType(name = "CompoundCRSType")
@XmlRootElement(name = "CompoundCRS")
public class DefaultCompoundCRS extends AbstractCRS implements CompoundCRS {
/**
* Serial number for inter-operability with different versions.
*/
private static final long serialVersionUID = -2656710314586929287L;
/**
* The coordinate reference systems in this compound CRS.
* May be the same reference than {@link #singles}.
*
* <p><b>Consider this field as final!</b>
* This field is modified only at construction and unmarshalling time by {@link #setComponents(List)}</p>
*/
private List<? extends CoordinateReferenceSystem> components;
/**
* A decomposition of the CRS list into the single elements.
* Computed by {@link #setSingleComponents(List)} on construction, deserialization or unmarshalling.
*/
private transient List<SingleCRS> singles;
/**
* Constructs a compound CRS from the given properties and CRS.
* The properties given in argument follow the same rules than for the
* {@linkplain AbstractReferenceSystem#AbstractReferenceSystem(Map) super-class constructor}.
* The following table is a reminder of main (not all) properties:
*
* <table class="sis">
* <caption>Recognized properties (non exhaustive list)</caption>
* <tr>
* <th>Property name</th>
* <th>Value type</th>
* <th>Returned by</th>
* </tr>
* <tr>
* <td>{@value org.opengis.referencing.IdentifiedObject#NAME_KEY}</td>
* <td>{@link org.opengis.metadata.Identifier} or {@link String}</td>
* <td>{@link #getName()}</td>
* </tr>
* <tr>
* <td>{@value org.opengis.referencing.IdentifiedObject#ALIAS_KEY}</td>
* <td>{@link org.opengis.util.GenericName} or {@link CharSequence} (optionally as array)</td>
* <td>{@link #getAlias()}</td>
* </tr>
* <tr>
* <td>{@value org.opengis.referencing.IdentifiedObject#IDENTIFIERS_KEY}</td>
* <td>{@link org.opengis.metadata.Identifier} (optionally as array)</td>
* <td>{@link #getIdentifiers()}</td>
* </tr>
* <tr>
* <td>{@value org.opengis.referencing.IdentifiedObject#REMARKS_KEY}</td>
* <td>{@link org.opengis.util.InternationalString} or {@link String}</td>
* <td>{@link #getRemarks()}</td>
* </tr>
* <tr>
* <td>{@value org.opengis.referencing.ReferenceSystem#DOMAIN_OF_VALIDITY_KEY}</td>
* <td>{@link org.opengis.metadata.extent.Extent}</td>
* <td>{@link #getDomainOfValidity()}</td>
* </tr>
* <tr>
* <td>{@value org.opengis.referencing.ReferenceSystem#SCOPE_KEY}</td>
* <td>{@link org.opengis.util.InternationalString} or {@link String}</td>
* <td>{@link #getScope()}</td>
* </tr>
* </table>
*
* @param properties the properties to be given to the coordinate reference system.
* @param components the sequence of coordinate reference systems making this compound CRS.
* @throws IllegalArgumentException if the given array does not contain at least two components,
* or if two consecutive components are a geographic CRS with an ellipsoidal height.
*
* @see org.apache.sis.referencing.factory.GeodeticObjectFactory#createCompoundCRS(Map, CoordinateReferenceSystem...)
*/
public DefaultCompoundCRS(final Map<String,?> properties, final CoordinateReferenceSystem... components) {
super(properties, createCoordinateSystem(properties, components));
setComponents(Arrays.asList(components));
/*
* 'singles' is computed by the above method call. Now verify that we do not have an ellipsoidal
* height with a geographic or projected CRS (see https://issues.apache.org/jira/browse/SIS-303).
* Note that this is already be done if the given array does not contain nested CompoundCRS.
*/
if (singles != this.components) {
verify(properties, singles.toArray(new SingleCRS[singles.size()]));
}
}
/**
* Verifies that the given array does not contain duplicated horizontal or vertical components.
* Verifies also that if there is an horizontal component, then there is no ellipsoidal height
* defined separately.
*
* @param properties the user-specified properties, for determining the locale of error messages.
* @param components the components to verify.
*/
private static void verify(final Map<String,?> properties, final CoordinateReferenceSystem[] components) {
int allTypes = 0;
int isProjected = 0; // 0 for false, 1 for true.
boolean isEllipsoidalHeight = false;
for (final CoordinateReferenceSystem component : components) {
final int type;
if (component instanceof GeodeticCRS) {
type = 1; // Must match the number used in Resources.Keys.DuplicatedSpatialComponents_1.
} else if (component instanceof ProjectedCRS) {
isProjected = 1;
type = 1; // Intentionally same number than for GeographicCRS case.
} else if (component instanceof VerticalCRS) {
isEllipsoidalHeight = ReferencingUtilities.isEllipsoidalHeight(((VerticalCRS) component).getDatum());
type = 2; // Must match the number used in Resources.Keys.DuplicatedSpatialComponents_1.
} else {
continue; // Skip other types. In particular, we allow 2 temporal CRS (used in meteorology).
}
if (allTypes == (allTypes |= type)) {
throw new IllegalArgumentException(Resources.forProperties(properties)
.getString(Resources.Keys.DuplicatedSpatialComponents_1, type));
}
}
if (isEllipsoidalHeight && ((allTypes & 1) != 0)) {
throw new IllegalArgumentException(Resources.forProperties(properties)
.getString(Resources.Keys.EllipsoidalHeightNotAllowed_1, isProjected));
}
}
/**
* Returns a compound coordinate system for the specified array of CRS objects.
*
* @param properties the properties given to the constructor, or {@code null} if unknown.
* @param components the CRS components, usually singles but not necessarily.
* @return the coordinate system for the given components.
*/
private static CoordinateSystem createCoordinateSystem(final Map<String,?> properties,
final CoordinateReferenceSystem[] components)
{
ArgumentChecks.ensureNonNull("components", components);
verify(properties, components);
if (components.length < 2) {
throw new IllegalArgumentException(Errors.getResources(properties).getString(
Errors.Keys.TooFewArguments_2, 2, components.length));
}
final CoordinateSystem[] cs = new CoordinateSystem[components.length];
for (int i=0; i<components.length; i++) {
final CoordinateReferenceSystem crs = components[i];
ArgumentChecks.ensureNonNullElement("components", i, crs);
cs[i] = crs.getCoordinateSystem();
}
return new DefaultCompoundCS(cs);
}
/**
* Constructs a new coordinate reference system with the same values than the specified one.
* This copy constructor provides a way to convert an arbitrary implementation into a SIS one
* or a user-defined one (as a subclass), usually in order to leverage some implementation-specific API.
*
* <p>This constructor performs a shallow copy, i.e. the properties are not cloned.</p>
*
* @param crs the coordinate reference system to copy.
*/
protected DefaultCompoundCRS(final CompoundCRS crs) {
super(crs);
if (crs instanceof DefaultCompoundCRS) {
final DefaultCompoundCRS that = (DefaultCompoundCRS) crs;
this.components = that.components;
this.singles = that.singles;
} else {
setComponents(crs.getComponents());
}
}
/**
* Returns a SIS CRS implementation with the same values than the given arbitrary implementation.
* If the given object is {@code null}, then this method returns {@code null}.
* Otherwise if the given object is already a SIS implementation, then the given object is returned unchanged.
* Otherwise a new SIS implementation is created and initialized to the attribute values of the given object.
*
* @param object the object to get as a SIS implementation, or {@code null} if none.
* @return a SIS implementation containing the values of the given object (may be the
* given object itself), or {@code null} if the argument was null.
*/
public static DefaultCompoundCRS castOrCopy(final CompoundCRS object) {
return (object == null) || (object instanceof DefaultCompoundCRS)
? (DefaultCompoundCRS) object : new DefaultCompoundCRS(object);
}
/**
* Returns the GeoAPI interface implemented by this class.
* The SIS implementation returns {@code CompoundCRS.class}.
*
* <div class="note"><b>Note for implementers:</b>
* Subclasses usually do not need to override this method since GeoAPI does not define {@code CompoundCRS}
* sub-interface. Overriding possibility is left mostly for implementers who wish to extend GeoAPI with their
* own set of interfaces.</div>
*
* @return {@code CompoundCRS.class} or a user-defined sub-interface.
*/
@Override
public Class<? extends CompoundCRS> getInterface() {
return CompoundCRS.class;
}
/**
* Compound CRS do not have datum.
*/
@Override
final Datum getDatum() {
return null;
}
/**
* Returns the ordered list of coordinate reference systems.
* This is the list of CRS given at construction time.
* This list may contains other {@code CompoundCRS} instances, as described in class Javadoc.
* For a flattened list of {@link SingleCRS} instances, see {@link #getSingleComponents()}.
*
* @return the coordinate reference systems as an unmodifiable list.
*/
@Override
@SuppressWarnings("unchecked") // We are safe if the list is read-only.
public List<CoordinateReferenceSystem> getComponents() {
return (List<CoordinateReferenceSystem>) components;
}
/**
* Computes the {@link #components} and {@link #singles} fields from the given CRS list.
* If the two lists have the same content, then the two fields will reference the same list.
*
* @see #getComponents()
*/
private void setComponents(final List<? extends CoordinateReferenceSystem> crs) {
if (setSingleComponents(crs)) {
components = singles; // Shares the same list.
} else {
components = UnmodifiableArrayList.wrap(crs.toArray(new CoordinateReferenceSystem[crs.size()]));
}
}
/**
* Returns the ordered list of single coordinate reference systems. If this compound CRS contains
* other compound CRS, then all of them are flattened in a sequence of {@code SingleCRS} objects.
* See class Javadoc for more information.
*
* @return the single coordinate reference systems as an unmodifiable list.
*
* @see org.apache.sis.referencing.CRS#getSingleComponents(CoordinateReferenceSystem)
*/
@SuppressWarnings("ReturnOfCollectionOrArrayField")
public List<SingleCRS> getSingleComponents() {
return singles;
}
/**
* Computes the {@link #singles} field from the given CRS list and returns {@code true}
* if the given list was already a list of single CRS.
*
* <p><strong>WARNING:</strong> this method is invoked by <em>before</em> the {@linkplain #components}
* field is set. Do not use that field in this method.</p>
*
* @see #getSingleComponents()
*/
private boolean setSingleComponents(final List<? extends CoordinateReferenceSystem> crs) {
final List<SingleCRS> flattened = new ArrayList<>(crs.size());
final boolean identical = ReferencingUtilities.getSingleComponents(crs, flattened);
singles = UnmodifiableArrayList.wrap(flattened.toArray(new SingleCRS[flattened.size()]));
return identical;
}
/**
* Computes the single CRS list on deserialization.
*
* @param in the input stream from which to deserialize a compound CRS.
* @throws IOException if an I/O error occurred while reading or if the stream contains invalid data.
* @throws ClassNotFoundException if the class serialized on the stream is not on the classpath.
*/
@SuppressWarnings("unchecked")
private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
final List<? extends CoordinateReferenceSystem> components = this.components;
if (components instanceof CheckedContainer<?>) {
final Class<?> type = ((CheckedContainer<?>) components).getElementType();
if (type == SingleCRS.class) {
singles = (List<SingleCRS>) components;
return;
}
}
setSingleComponents(components);
}
/**
* Returns {@code true} if the sequence of single components is conform to the ISO 19162 restrictions.
* The <a href="http://docs.opengeospatial.org/is/12-063r5/12-063r5.html#111">WKT 2 specification at §16.1</a>
* restricts {@code CompoundCRS} to the following components in that order:
*
* <ul>
* <li>A mandatory horizontal CRS (only one of two-dimensional {@code GeographicCRS}
* or {@code ProjectedCRS} or {@code EngineeringCRS}).</li>
* <li>Optionally followed by a {@code VerticalCRS} or a {@code ParametricCRS} (but not both).</li>
* <li>Optionally followed by a {@code TemporalCRS}.</li>
* </ul>
*
* This method verifies the above criterion with the following flexibilities:
*
* <ul>
* <li>Accepts three-dimensional {@code GeodeticCRS} followed by a {@code TemporalCRS}.</li>
* </ul>
*
* This method does not verify recursively if the component are themselves standard compliant.
* In particular, this method does not verify if the geographic CRS uses (latitude, longitude)
* axes order as requested by ISO 19162.
*
* <p>This method is not yet public because of the above-cited limitations: a {@code true} return
* value is not a guarantee that the CRS is really standard-compliant.</p>
*
* @return {@code true} if this CRS is "standard" compliant, except for the above-cited limitations.
*/
@SuppressWarnings("fallthrough")
static boolean isStandardCompliant(final List<? extends CoordinateReferenceSystem> singles) {
if (Containers.isNullOrEmpty(singles)) {
return false;
}
/*
* 0 if we expect a horizontal CRS: Geographic2D, projected or engineering.
* 1 if we expect a vertical or parametric CRS (but not both).
* 2 if we expect a temporal CRS.
* 3 if we do not expect any other CRS.
*/
int state = 0;
for (final CoordinateReferenceSystem crs : singles) {
switch (state) {
case 0: {
if (crs instanceof GeodeticCRS || crs instanceof ProjectedCRS || crs instanceof EngineeringCRS) {
switch (crs.getCoordinateSystem().getDimension()) {
case 2: state = 1; continue; // Next CRS can be vertical, parametric or temporal.
case 3: state = 2; continue; // Next CRS can only be temporal.
}
}
return false;
}
case 1: {
if (crs instanceof VerticalCRS || crs instanceof ParametricCRS) {
state = 2; continue; // Next CRS can only be temporal.
}
// Fallthrough (the current CRS may be temporal)
}
case 2: {
if (crs instanceof TemporalCRS) {
state = 3; continue; // Do not expect any other CRS.
}
// Fallthrough (unexpected CRS).
}
default: {
return false;
}
}
}
return true;
}
/**
* {@inheritDoc}
*
* <p>If the given convention is {@link AxesConvention#DISPLAY_ORIENTED} or
* {@link AxesConvention#NORMALIZED}, then this method will also reorder the components
* with horizontal CRS (geodetic or projected) first, then vertical CRS, then temporal CRS.</p>
*
* @return {@inheritDoc}
*/
@Override
public synchronized DefaultCompoundCRS forConvention(final AxesConvention convention) {
ArgumentChecks.ensureNonNull("convention", convention);
DefaultCompoundCRS crs = (DefaultCompoundCRS) getCached(convention);
if (crs == null) {
crs = this;
boolean changed = false;
final boolean reorderCRS = convention.ordinal() <= AxesConvention.DISPLAY_ORIENTED.ordinal();
final List<? extends CoordinateReferenceSystem> components = reorderCRS ? singles : this.components;
final CoordinateReferenceSystem[] newComponents = new CoordinateReferenceSystem[components.size()];
for (int i=0; i<newComponents.length; i++) {
CoordinateReferenceSystem component = components.get(i);
AbstractCRS m = castOrCopy(component);
if (m != (m = m.forConvention(convention))) {
component = m;
changed = true;
}
newComponents[i] = component;
}
if (changed) {
if (reorderCRS) {
Arrays.sort(newComponents, SubTypes.BY_TYPE); // This array typically has less than 4 elements.
}
crs = new DefaultCompoundCRS(IdentifiedObjects.getProperties(this, IDENTIFIERS_KEY), newComponents);
}
crs = (DefaultCompoundCRS) setCached(convention, crs);
}
return crs;
}
/**
* Should never be invoked since we override {@link AbstractCRS#forConvention(AxesConvention)}.
*/
@Override
final AbstractCRS createSameType(final Map<String,?> properties, final CoordinateSystem cs) {
throw new AssertionError();
}
/**
* Compares this coordinate reference system with the specified object for equality.
*
* @param object the object to compare to {@code this}.
* @param mode {@link ComparisonMode#STRICT STRICT} for performing a strict comparison, or
* {@link ComparisonMode#IGNORE_METADATA IGNORE_METADATA} for comparing only
* properties relevant to transformations.
* @return {@code true} if both objects are equal.
*/
@Override
public boolean equals(final Object object, final ComparisonMode mode) {
if (object == this) {
return true; // Slight optimization.
}
if (super.equals(object, mode)) {
switch (mode) {
case STRICT: {
return components.equals(((DefaultCompoundCRS) object).components);
}
default: {
return Utilities.deepEquals(getComponents(), ((CompoundCRS) object).getComponents(), mode);
}
}
}
return false;
}
/**
* {@inheritDoc}
*
* @return {@inheritDoc}
*/
@Override
protected long computeHashCode() {
return super.computeHashCode() + 31*components.hashCode();
}
/**
* Formats this CRS as a <cite>Well Known Text</cite> {@code CompoundCRS[…]} element.
*
* <h4>WKT validity</h4>
* The WKT version 2 format restricts compound CRS to the following components in that order:
*
* <ul>
* <li>A mandatory horizontal CRS (only one of two-dimensional {@code GeographicCRS}
* or {@code ProjectedCRS} or {@code EngineeringCRS}).</li>
* <li>Optionally followed by a {@code VerticalCRS} or a {@code ParametricCRS} (but not both).</li>
* <li>Optionally followed by a {@code TemporalCRS}.</li>
* </ul>
*
* SIS does not check if this CRS is compliant with the above-cited restrictions.
*
* @return {@code "CompoundCRS"} (WKT 2) or {@code "Compd_CS"} (WKT 1).
*
* @see <a href="http://docs.opengeospatial.org/is/12-063r5/12-063r5.html#110">WKT 2 specification §16</a>
*/
@Override
protected String formatTo(final Formatter formatter) {
WKTUtilities.appendName(this, formatter, null);
final Convention convention = formatter.getConvention();
final List<? extends CoordinateReferenceSystem> crs;
final boolean isStandardCompliant;
final boolean isWKT1 = convention.majorVersion() == 1;
if (isWKT1 || convention == Convention.INTERNAL) {
crs = getComponents();
isStandardCompliant = true; // WKT 1 does not put any restriction.
} else {
crs = getSingleComponents();
isStandardCompliant = isStandardCompliant(crs);
}
for (final CoordinateReferenceSystem element : crs) {
formatter.newLine();
formatter.append(WKTUtilities.toFormattable(element));
}
formatter.newLine(); // For writing the ID[…] element on its own line.
if (!isStandardCompliant) {
formatter.setInvalidWKT(this, null);
}
return isWKT1 ? WKTKeywords.Compd_CS : WKTKeywords.CompoundCRS;
}
//////////////////////////////////////////////////////////////////////////////////////////////////
//////// ////////
//////// XML support with JAXB ////////
//////// ////////
//////// The following methods are invoked by JAXB using reflection (even if ////////
//////// they are private) or are helpers for other methods invoked by JAXB. ////////
//////// Those methods can be safely removed if Geographic Markup Language ////////
//////// (GML) support is not needed. ////////
//////// ////////
//////////////////////////////////////////////////////////////////////////////////////////////////
/**
* Constructs a new object in which every attributes are set to a null or empty value.
* <strong>This is not a valid object.</strong> This constructor is strictly reserved
* to JAXB, which will assign values to the fields using reflexion.
*/
private DefaultCompoundCRS() {
components = Collections.emptyList();
singles = Collections.emptyList();
/*
* At least one component CRS is mandatory for SIS working. We do not verify their presence here
* because the verification would have to be done in an 'afterMarshal(…)' method and throwing an
* exception in that method causes the whole unmarshalling to fail. But the SC_CRS adapter does
* some verifications (indirectly, by testing for coordinate system existence).
*/
}
/**
* Returns the CRS components to marshal. We use this private methods instead than annotating
* {@link #getSingleComponents()} directly for two reasons:
*
* <ul>
* <li>Use array instead of {@code List} in order to force JAXB to invoke the setter method.
* This setter is needed for performing additional work after setting the list of CRS.</li>
*
* <li>Allow a slightly asymmetry: marshal {@code SingleCRS} components for compliance with
* the standard, but accept the more generic {@code CoordinateReferenceSystem} elements
* at unmarshalling time.</li>
* </ul>
*/
@XmlJavaTypeAdapter(SC_CRS.class)
@XmlElement(name = "componentReferenceSystem", required = true)
private CoordinateReferenceSystem[] getXMLComponents() {
final List<SingleCRS> crs = getSingleComponents();
return crs.toArray(new CoordinateReferenceSystem[crs.size()]);
}
/**
* Invoked by JAXB for setting the components of this compound CRS.
*/
private void setXMLComponents(final CoordinateReferenceSystem[] crs) {
components = setSingleComponents(Arrays.asList(crs)) ? singles : UnmodifiableArrayList.wrap(crs);
setCoordinateSystem("coordinateSystem", createCoordinateSystem(null, crs));
}
}