| /* |
| * 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.Objects; |
| import jakarta.xml.bind.annotation.XmlType; |
| import jakarta.xml.bind.annotation.XmlElement; |
| import jakarta.xml.bind.annotation.XmlRootElement; |
| import javax.measure.Unit; |
| import javax.measure.quantity.Angle; |
| import org.opengis.referencing.cs.CartesianCS; |
| import org.opengis.referencing.cs.SphericalCS; |
| import org.opengis.referencing.cs.EllipsoidalCS; |
| import org.opengis.referencing.cs.CoordinateSystem; |
| import org.opengis.referencing.crs.GeodeticCRS; |
| import org.opengis.referencing.crs.SingleCRS; |
| import org.opengis.referencing.datum.GeodeticDatum; |
| import org.opengis.referencing.datum.PrimeMeridian; |
| import org.opengis.metadata.Identifier; |
| import org.apache.sis.referencing.AbstractReferenceSystem; |
| import org.apache.sis.referencing.CRS; |
| import org.apache.sis.referencing.cs.AbstractCS; |
| import org.apache.sis.referencing.internal.Legacy; |
| import org.apache.sis.referencing.privy.AxisDirections; |
| import org.apache.sis.referencing.privy.WKTKeywords; |
| import org.apache.sis.referencing.privy.WKTUtilities; |
| import org.apache.sis.referencing.privy.ReferencingUtilities; |
| import org.apache.sis.metadata.privy.ImplementationHelper; |
| import org.apache.sis.util.resources.Errors; |
| import org.apache.sis.io.wkt.Convention; |
| import org.apache.sis.io.wkt.Formatter; |
| import org.apache.sis.measure.Units; |
| |
| // Specific to the main branch: |
| import org.opengis.referencing.ReferenceIdentifier; |
| |
| |
| /** |
| * A 2- or 3-dimensional coordinate reference system based on a geodetic reference frame. |
| * The CRS is geographic if associated with an ellipsoidal coordinate system, |
| * or geocentric if associated with a spherical or Cartesian coordinate system. |
| * |
| * <p><b>Used with datum type:</b> |
| * {@linkplain org.apache.sis.referencing.datum.DefaultGeodeticDatum Geodetic}.<br> |
| * <b>Used with coordinate system types:</b> |
| * {@linkplain org.apache.sis.referencing.cs.DefaultCartesianCS Cartesian}, |
| * {@linkplain org.apache.sis.referencing.cs.DefaultSphericalCS Spherical} or |
| * {@linkplain org.apache.sis.referencing.cs.DefaultEllipsoidalCS Ellipsoidal}. |
| * </p> |
| * |
| * @author Martin Desruisseaux (IRD, Geomatys) |
| */ |
| @XmlType(name = "GeodeticCRSType", propOrder = { |
| "ellipsoidalCS", |
| "cartesianCS", |
| "sphericalCS", |
| "datum" |
| }) |
| @XmlRootElement(name = "GeodeticCRS") |
| class DefaultGeodeticCRS extends AbstractCRS implements GeodeticCRS { // If made public, see comment in getDatum(). |
| /** |
| * Serial number for inter-operability with different versions. |
| */ |
| private static final long serialVersionUID = -6205678223972395910L; |
| |
| /** |
| * The datum. |
| * |
| * <p><b>Consider this field as final!</b> |
| * This field is modified only at unmarshalling time by {@link #setDatum(GeodeticDatum)}</p> |
| * |
| * @see #getDatum() |
| */ |
| @SuppressWarnings("serial") // Most SIS implementations are serializable. |
| private GeodeticDatum datum; |
| |
| /** |
| * Creates a coordinate reference system from the given properties, datum and coordinate system. |
| * The properties given in argument follow the same rules as for the |
| * {@linkplain AbstractReferenceSystem#AbstractReferenceSystem(Map) super-class constructor}. |
| * |
| * <p>This constructor is not public because it does not verify the {@code cs} type.</p> |
| * |
| * @param properties the properties to be given to the coordinate reference system. |
| * @param datum the datum. |
| * @param cs the coordinate system. |
| */ |
| DefaultGeodeticCRS(final Map<String,?> properties, |
| final GeodeticDatum datum, |
| final CoordinateSystem cs) |
| { |
| super(properties, cs); |
| this.datum = Objects.requireNonNull(datum); |
| } |
| |
| /** |
| * Creates a new CRS derived from the specified one, but with different axis order or unit. |
| * This is for implementing the {@link #createSameType(AbstractCS)} method only. |
| */ |
| DefaultGeodeticCRS(final DefaultGeodeticCRS original, final ReferenceIdentifier id, final AbstractCS cs) { |
| super(original, id, cs); |
| datum = original.datum; |
| } |
| |
| /** |
| * Constructs a new coordinate reference system with the same values as 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 DefaultGeodeticCRS(final GeodeticCRS crs) { |
| super(crs); |
| datum = crs.getDatum(); |
| } |
| |
| /** |
| * Creates an exception to throw for a coordinate system of illegal class. |
| * This is a helper method for sub-classes. |
| * |
| * @param cs the user-specified coordinate system. |
| * @return the exception to throw. |
| */ |
| static IllegalArgumentException illegalCoordinateSystemType(final CoordinateSystem cs) { |
| return new IllegalArgumentException(Errors.format(Errors.Keys.IllegalCoordinateSystem_1, |
| ReferencingUtilities.getInterface(CoordinateSystem.class, cs))); |
| } |
| |
| /** |
| * Returns the GeoAPI interface implemented by this class. |
| * The SIS implementation returns {@code GeodeticCRS.class}. |
| * Subclasses implementing a more specific GeoAPI interface shall override this method. |
| * |
| * @return the coordinate reference system interface implemented by this class. |
| */ |
| @Override |
| public Class<? extends GeodeticCRS> getInterface() { |
| return GeodeticCRS.class; |
| } |
| |
| /** |
| * Returns the datum. |
| * |
| * This method is overridden is subclasses for documentation purpose only, mostly for showing |
| * this method in the appropriate position in javadoc (instead of at the bottom of the page). |
| * If {@code DefaultGeodeticCRS} is made public in a future SIS version, then we could remove |
| * the overridden methods. |
| * |
| * @return the datum. |
| */ |
| @Override |
| @XmlElement(name = "geodeticDatum", required = true) |
| public GeodeticDatum getDatum() { |
| return datum; |
| } |
| |
| /** |
| * Returns a coordinate reference system of the same type as this CRS but with different axes. |
| * This method shall be overridden by all {@code DefaultGeodeticCRS} subclasses in this package. |
| * |
| * @param cs the coordinate system with new axes. |
| * @return new CRS of the same type and datum than this CRS, but with the given axes. |
| */ |
| @Override |
| AbstractCRS createSameType(final AbstractCS cs) { |
| return new DefaultGeodeticCRS(this, null, cs); |
| } |
| |
| /** |
| * Formats this CRS as a <i>Well Known Text</i> {@code GeodeticCRS[…]} element. |
| * More information about the WKT format is documented in subclasses. |
| * |
| * @return {@code "GeodeticCRS"} (WKT 2) or {@code "GeogCS"}/{@code "GeocCS"} (WKT 1). |
| */ |
| @Override |
| protected String formatTo(final Formatter formatter) { |
| WKTUtilities.appendName(this, formatter, null); |
| CoordinateSystem cs = getCoordinateSystem(); |
| final Convention convention = formatter.getConvention(); |
| final boolean isWKT1 = (convention.majorVersion() == 1); |
| final boolean isGeographicWKT1 = isWKT1 && (cs instanceof EllipsoidalCS); |
| if (isGeographicWKT1 && cs.getDimension() == 3) { |
| /* |
| * Version 1 of WKT format did not have three-dimensional GeographicCRS. Instead, such CRS were formatted |
| * as a CompoundCRS made of a two-dimensional GeographicCRS with a VerticalCRS for the ellipsoidal height. |
| * Note that such compound is illegal in WKT 2 and ISO 19111 standard, as ellipsoidal height shall not be |
| * separated from the geographic component. So we perform this separation only at WKT 1 formatting time. |
| */ |
| SingleCRS first = CRS.getHorizontalComponent(this); |
| SingleCRS second = CRS.getVerticalComponent(this, true); |
| if (first != null && second != null) { // Should not be null, but we are paranoiac. |
| if (AxisDirections.isVertical(cs.getAxis(0).getDirection())) { |
| // It is very unusual to have VerticalCRS first, but our code tries to be robust. |
| final SingleCRS t = first; |
| first = second; second = t; |
| } |
| formatter.newLine(); formatter.append(WKTUtilities.toFormattable(first)); |
| formatter.newLine(); formatter.append(WKTUtilities.toFormattable(second)); |
| formatter.newLine(); |
| return WKTKeywords.Compd_CS; |
| } |
| } |
| /* |
| * Unconditionally format the datum element, followed by the prime meridian. |
| * The prime meridian is part of datum according ISO 19111, but is formatted |
| * as a sibling (rather than a child) element in WKT for historical reasons. |
| */ |
| @SuppressWarnings("LocalVariableHidesMemberVariable") |
| final GeodeticDatum datum = getDatum(); // Gives subclasses a chance to override. |
| formatter.newLine(); |
| formatter.append(WKTUtilities.toFormattable(datum)); |
| formatter.newLine(); |
| final PrimeMeridian pm = datum.getPrimeMeridian(); |
| final Unit<Angle> angularUnit = AxisDirections.getAngularUnit(cs, null); |
| if (convention != Convention.WKT2_SIMPLIFIED || // Really this specific enum, not Convention.isSimplified(). |
| ReferencingUtilities.getGreenwichLongitude(pm, Units.DEGREE) != 0) |
| { |
| final Unit<Angle> oldUnit = formatter.addContextualUnit(angularUnit); |
| formatter.indent(1); |
| formatter.append(WKTUtilities.toFormattable(pm)); |
| formatter.indent(-1); |
| formatter.newLine(); |
| formatter.restoreContextualUnit(angularUnit, oldUnit); |
| } |
| /* |
| * Get the coordinate system to format. This will also determine the units to write and the keyword to |
| * return in WKT 1 format. Note that for the WKT 1 format, we need to replace the coordinate system by |
| * an instance conform to the legacy conventions. |
| * |
| * We cannot delegate the work below to subclasses, because XML unmarshalling of a geodetic CRS will |
| * NOT create an instance of a subclass (because the distinction between geographic and geocentric CRS |
| * is not anymore in ISO 19111:2007). |
| */ |
| final boolean isBaseCRS; |
| if (isWKT1) { |
| if (!isGeographicWKT1) { // If not geographic, then presumed geocentric. |
| if (cs instanceof CartesianCS) { |
| cs = Legacy.forGeocentricCRS((CartesianCS) cs, true); |
| } else { |
| formatter.setInvalidWKT(cs, null); // SphericalCS was not supported in WKT 1. |
| } |
| } |
| isBaseCRS = false; |
| } else { |
| isBaseCRS = isBaseCRS(formatter); |
| } |
| /* |
| * Format the coordinate system, except if this CRS is the base CRS of an AbstractDerivedCRS in WKT 2 format. |
| * This is because ISO 19162 omits the coordinate system definition of enclosed base CRS in order to simplify |
| * the WKT. The 'formatCS(…)' method may write axis unit before or after the axes depending on whether we are |
| * formatting WKT version 1 or 2 respectively. |
| * |
| * Note that even if we do not format the CS, we may still write the units if we are formatting in "simplified" |
| * mode (as opposed to the more verbose mode). This looks like the opposite of what we would expect, but this is |
| * because formatting the unit here allow us to avoid repeating the unit in projection parameters when this CRS |
| * is part of a ProjectedCRS. Note however that in such case, the units to format are the angular units because |
| * the linear units will be formatted in the enclosing PROJCS[…] element. |
| */ |
| if (!isBaseCRS || convention == Convention.INTERNAL) { |
| formatCS(formatter, cs, ReferencingUtilities.getUnit(cs), isWKT1); // Will also format the axes unit. |
| } else if (convention.isSimplified()) { |
| formatter.append(formatter.toContextualUnit(angularUnit)); |
| } |
| /* |
| * For WKT 1, the keyword depends on the subclass: "GeogCS" for GeographicCRS or "GeocCS" for GeocentricCRS. |
| * However, we cannot rely on the subclass for choosing the keyword, because after XML unmarhaling we only |
| * have a GeodeticCRS. We need to make the choice in this base class. The CS type is a sufficient criterion. |
| */ |
| if (isWKT1) { |
| return isGeographicWKT1 ? WKTKeywords.GeogCS : WKTKeywords.GeocCS; |
| } else { |
| return isBaseCRS ? WKTKeywords.BaseGeodCRS |
| : formatter.shortOrLong(WKTKeywords.GeodCRS, WKTKeywords.GeodeticCRS); |
| } |
| } |
| |
| |
| |
| |
| /* |
| ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ |
| ┃ ┃ |
| ┃ 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 value. |
| * <strong>This is not a valid object.</strong> This constructor is strictly |
| * reserved to JAXB, which will assign values to the fields using reflection. |
| */ |
| DefaultGeodeticCRS() { |
| /* |
| * The datum and the coordinate system are 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. |
| */ |
| } |
| |
| /** |
| * Invoked by JAXB at unmarshalling time. |
| * |
| * @see #getDatum() |
| */ |
| private void setDatum(final GeodeticDatum value) { |
| if (datum == null) { |
| datum = value; |
| } else { |
| ImplementationHelper.propertyAlreadySet(DefaultGeodeticCRS.class, "setDatum", "geodeticDatum"); |
| } |
| } |
| |
| /** |
| * Invoked by JAXB at marshalling time. |
| * |
| * <h4>Implementation note</h4> |
| * The usual way to handle {@code <xs:choice>} with JAXB is to annotate a single method like below: |
| * |
| * {@snippet lang="java" : |
| * @Override |
| * @XmlElements({ |
| * @XmlElement(name = "ellipsoidalCS", type = DefaultEllipsoidalCS.class), |
| * @XmlElement(name = "cartesianCS", type = DefaultCartesianCS.class), |
| * @XmlElement(name = "sphericalCS", type = DefaultSphericalCS.class) |
| * }) |
| * public CoordinateSystem getCoordinateSystem() { |
| * return super.getCoordinateSystem(); |
| * } |
| * } |
| * |
| * However, our attempts to apply this approach worked for {@code DefaultParameterValue} but not for this class: |
| * for an unknown reason, the unmarshalled CS object is empty. |
| * |
| * @see <a href="http://issues.apache.org/jira/browse/SIS-166">SIS-166</a> |
| */ |
| @XmlElement(name="ellipsoidalCS") private EllipsoidalCS getEllipsoidalCS() {return getCoordinateSystem(EllipsoidalCS.class);} |
| @XmlElement(name="cartesianCS") private CartesianCS getCartesianCS() {return getCoordinateSystem(CartesianCS .class);} |
| @XmlElement(name="sphericalCS") private SphericalCS getSphericalCS() {return getCoordinateSystem(SphericalCS .class);} |
| |
| /** |
| * Invoked by JAXB at unmarshalling time. |
| * |
| * @see #getEllipsoidalCS() |
| */ |
| private void setEllipsoidalCS(final EllipsoidalCS cs) {super.setCoordinateSystem("ellipsoidalCS", cs);} |
| private void setCartesianCS (final CartesianCS cs) {super.setCoordinateSystem("cartesianCS", cs);} |
| private void setSphericalCS (final SphericalCS cs) {super.setCoordinateSystem("sphericalCS", cs);} |
| } |