| /* |
| * 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)); |
| } |
| } |