| /* |
| * 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.datum; |
| |
| import java.util.Map; |
| import java.util.Objects; |
| import javax.measure.Unit; |
| import javax.measure.quantity.Angle; |
| import javax.xml.bind.annotation.XmlType; |
| import javax.xml.bind.annotation.XmlElement; |
| import javax.xml.bind.annotation.XmlRootElement; |
| import org.opengis.util.GenericName; |
| import org.opengis.util.InternationalString; |
| import org.opengis.referencing.ReferenceIdentifier; |
| import org.opengis.referencing.datum.PrimeMeridian; |
| import org.opengis.referencing.crs.GeneralDerivedCRS; |
| import org.apache.sis.referencing.AbstractIdentifiedObject; |
| import org.apache.sis.internal.referencing.Formulas; |
| import org.apache.sis.internal.referencing.WKTUtilities; |
| import org.apache.sis.internal.referencing.ReferencingUtilities; |
| import org.apache.sis.internal.metadata.MetadataUtilities; |
| import org.apache.sis.internal.referencing.WKTKeywords; |
| import org.apache.sis.internal.jaxb.gml.Measure; |
| import org.apache.sis.internal.util.Numerics; |
| import org.apache.sis.io.wkt.Formatter; |
| import org.apache.sis.io.wkt.Convention; |
| import org.apache.sis.util.ComparisonMode; |
| import org.apache.sis.measure.Units; |
| |
| import static org.apache.sis.util.ArgumentChecks.ensureFinite; |
| import static org.apache.sis.util.ArgumentChecks.ensureNonNull; |
| |
| |
| /** |
| * Defines the origin from which longitude values are determined. |
| * |
| * <h2>Creating new prime meridian instances</h2> |
| * New instances can be created either directly by specifying all information to a factory method (choices 3 |
| * and 4 below), or indirectly by specifying the identifier of an entry in a database (choices 1 and 2 below). |
| * In particular, the <a href="http://www.epsg.org">EPSG</a> database provides definitions for many prime meridians, |
| * and Apache SIS provides convenience shortcuts for some of them. |
| * |
| * <p>Choice 1 in the following list is the easiest but most restrictive way to get a prime meridian. |
| * The other choices provide more freedom. Each choice delegates its work to the subsequent items |
| * (in the default configuration), so this list can been seen as <cite>top to bottom</cite> API.</p> |
| * |
| * <ol> |
| * <li>Create a {@code PrimeMeridian} from one of the static convenience shortcuts listed in |
| * {@link org.apache.sis.referencing.CommonCRS#primeMeridian()}.</li> |
| * <li>Create a {@code PrimeMeridian} from an identifier in a database by invoking |
| * {@link org.opengis.referencing.datum.DatumAuthorityFactory#createPrimeMeridian(String)}.</li> |
| * <li>Create a {@code PrimeMeridian} by invoking the {@code DatumFactory.createPrimeMeridian(…)} method |
| * (implemented for example by {@link org.apache.sis.referencing.factory.GeodeticObjectFactory}).</li> |
| * <li>Create a {@code DefaultPrimeMeridian} by invoking the |
| * {@linkplain #DefaultPrimeMeridian(Map, double, Unit) constructor}.</li> |
| * </ol> |
| * |
| * <b>Example:</b> the following code gets the Greenwich prime meridian: |
| * |
| * {@preformat java |
| * PrimeMeridian pm = CommonCRS.WGS84.primeMeridian(); |
| * } |
| * |
| * <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) |
| * 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) |
| * @author Cédric Briançon (Geomatys) |
| * @version 0.7 |
| * |
| * @see org.apache.sis.referencing.CommonCRS#primeMeridian() |
| * @see org.apache.sis.referencing.factory.GeodeticAuthorityFactory#createPrimeMeridian(String) |
| * |
| * @since 0.4 |
| * @module |
| */ |
| @XmlType(name = "PrimeMeridianType") |
| @XmlRootElement(name = "PrimeMeridian") |
| public class DefaultPrimeMeridian extends AbstractIdentifiedObject implements PrimeMeridian { |
| /** |
| * Serial number for inter-operability with different versions. |
| */ |
| private static final long serialVersionUID = 541978454643213305L;; |
| |
| /** |
| * Longitude of the prime meridian measured from the Greenwich meridian, positive eastward. |
| * |
| * <p><b>Consider this field as final!</b> |
| * This field is modified only at unmarshalling time by {@link #setGreenwichMeasure(Measure)}</p> |
| */ |
| private double greenwichLongitude; |
| |
| /** |
| * The angular unit of the {@linkplain #getGreenwichLongitude() Greenwich longitude}. |
| * |
| * <p><b>Consider this field as final!</b> |
| * This field is modified only at unmarshalling time by {@link #setGreenwichMeasure(Measure)}</p> |
| */ |
| private Unit<Angle> angularUnit; |
| |
| /** |
| * Creates a prime meridian from the given properties. The properties map is given unchanged to the |
| * {@linkplain AbstractIdentifiedObject#AbstractIdentifiedObject(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 ReferenceIdentifier} or {@link String}</td> |
| * <td>{@link #getName()}</td> |
| * </tr> |
| * <tr> |
| * <td>{@value org.opengis.referencing.IdentifiedObject#ALIAS_KEY}</td> |
| * <td>{@link 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 ReferenceIdentifier} (optionally as array)</td> |
| * <td>{@link #getIdentifiers()}</td> |
| * </tr> |
| * <tr> |
| * <td>{@value org.opengis.referencing.IdentifiedObject#REMARKS_KEY}</td> |
| * <td>{@link InternationalString} or {@link String}</td> |
| * <td>{@link #getRemarks()}</td> |
| * </tr> |
| * </table> |
| * |
| * @param properties the properties to be given to the identified object. |
| * @param greenwichLongitude the longitude value relative to the Greenwich Meridian. |
| * @param angularUnit the angular unit of the longitude. |
| * |
| * @see org.apache.sis.referencing.factory.GeodeticObjectFactory#createPrimeMeridian(Map, double, Unit) |
| */ |
| public DefaultPrimeMeridian(final Map<String,?> properties, final double greenwichLongitude, |
| final Unit<Angle> angularUnit) |
| { |
| super(properties); |
| ensureFinite("greenwichLongitude", greenwichLongitude); |
| ensureNonNull("angularUnit", angularUnit); |
| this.greenwichLongitude = greenwichLongitude; |
| this.angularUnit = angularUnit; |
| } |
| |
| /** |
| * Creates a new prime meridian 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 meridian the prime meridian to copy. |
| * |
| * @see #castOrCopy(PrimeMeridian) |
| */ |
| protected DefaultPrimeMeridian(final PrimeMeridian meridian) { |
| super(meridian); |
| greenwichLongitude = meridian.getGreenwichLongitude(); |
| angularUnit = meridian.getAngularUnit(); |
| } |
| |
| /** |
| * Returns a SIS prime meridian 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 DefaultPrimeMeridian castOrCopy(final PrimeMeridian object) { |
| return (object == null) || (object instanceof DefaultPrimeMeridian) |
| ? (DefaultPrimeMeridian) object : new DefaultPrimeMeridian(object); |
| } |
| |
| /** |
| * Returns the GeoAPI interface implemented by this class. |
| * The SIS implementation returns {@code PrimeMeridian.class}. |
| * |
| * <div class="note"><b>Note for implementers:</b> |
| * Subclasses usually do not need to override this method since GeoAPI does not define {@code PrimeMeridian} |
| * sub-interface. Overriding possibility is left mostly for implementers who wish to extend GeoAPI with their |
| * own set of interfaces.</div> |
| * |
| * @return {@code PrimeMeridian.class} or a user-defined sub-interface. |
| */ |
| @Override |
| public Class<? extends PrimeMeridian> getInterface() { |
| return PrimeMeridian.class; |
| } |
| |
| /** |
| * Longitude of the prime meridian measured from the Greenwich meridian, positive eastward. |
| * |
| * @return the prime meridian Greenwich longitude, in {@linkplain #getAngularUnit() angular unit}. |
| */ |
| @Override |
| public double getGreenwichLongitude() { |
| return greenwichLongitude; |
| } |
| |
| /** |
| * Returns the longitude value relative to the Greenwich Meridian, expressed in the specified units. |
| * This convenience method makes it easier to obtain longitude in decimal degrees using the following |
| * code, regardless of the underlying angular units of this prime meridian: |
| * |
| * {@preformat java |
| * double longitudeInDegrees = primeMeridian.getGreenwichLongitude(Units.DEGREE); |
| * } |
| * |
| * @param unit the unit in which to express longitude. |
| * @return the Greenwich longitude in the given units. |
| */ |
| public double getGreenwichLongitude(final Unit<Angle> unit) { |
| return getAngularUnit().getConverterTo(unit).convert(getGreenwichLongitude()); |
| } |
| |
| /** |
| * Returns the angular unit of the Greenwich longitude. |
| * |
| * @return the angular unit of the {@linkplain #getGreenwichLongitude() Greenwich longitude}. |
| */ |
| @Override |
| public Unit<Angle> getAngularUnit() { |
| return angularUnit; |
| } |
| |
| /** |
| * Compares this prime meridian 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 coordinate 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: { |
| final DefaultPrimeMeridian that = (DefaultPrimeMeridian) object; |
| return Numerics.equals(this.greenwichLongitude, that.greenwichLongitude) && |
| Objects.equals(this.angularUnit, that.angularUnit); |
| } |
| case BY_CONTRACT: { |
| final PrimeMeridian that = (PrimeMeridian) object; |
| return Numerics.equals(getGreenwichLongitude(), that.getGreenwichLongitude()) && |
| Objects.equals(getAngularUnit(), that.getAngularUnit()); |
| } |
| default: { |
| final double v1 = getGreenwichLongitude(Units.DEGREE); |
| final double v2 = ReferencingUtilities.getGreenwichLongitude((PrimeMeridian) object, Units.DEGREE); |
| if (mode == ComparisonMode.IGNORE_METADATA) { |
| /* |
| * We relax the check on unit of measurement because EPSG uses sexagesimal degrees |
| * for the Greenwich meridian. Requirying the same unit would make more difficult |
| * for isWGS84(…) methods to recognize EPSG's WGS84. We allow this relaxation here |
| * because the unit of the prime meridian is usually not inherited by axes (indeed, |
| * they are often not the same in the EPSG dataset). The same is not true for other |
| * objects like DefaultEllipsoid. |
| */ |
| return Numerics.equals(v1, v2); |
| } else if (Numerics.epsilonEqual(v1, v2, Formulas.ANGULAR_TOLERANCE)) { |
| return true; |
| } |
| assert (mode != ComparisonMode.DEBUG) : Numerics.messageForDifference("greenwichLongitude", v1, v2); |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Invoked by {@code hashCode()} for computing the hash code when first needed. |
| * See {@link org.apache.sis.referencing.AbstractIdentifiedObject#computeHashCode()} |
| * for more information. |
| * |
| * @return the hash code value. This value may change in any future Apache SIS version. |
| */ |
| @Override |
| protected long computeHashCode() { |
| return super.computeHashCode() + Double.doubleToLongBits(greenwichLongitude) + Objects.hashCode(angularUnit); |
| } |
| |
| /** |
| * Returns {@code true} if the given formatter is in the process of formatting the prime meridian of a base CRS |
| * of an {@code AbstractDerivedCRS}. In such case, base CRS coordinate system axes shall not be formatted, which |
| * has the consequence of bringing the {@code UNIT[…]} element right below the {@code PRIMEM[…]} one. Example: |
| * |
| * {@preformat wkt |
| * ProjectedCRS[“NTF (Paris) / Lambert zone II”, |
| * BaseGeodCRS[“NTF (Paris)”, |
| * Datum[“Nouvelle Triangulation Francaise”, |
| * Ellipsoid[“NTF”, 6378249.2, 293.4660212936269]], |
| * PrimeMeridian[“Paris”, 2.5969213], |
| * AngleUnit[“grad”, 0.015707963267948967]], |
| * Conversion[“Lambert zone II”, |
| * etc... |
| * } |
| * |
| * If we were not formatting a base CRS, we would have many lines between {@code PrimeMeridian[…]} and |
| * {@code AngleUnit[…]} in the above example, which would make less obvious that the angle unit applies |
| * also to the prime meridian. It does not bring any ambiguity from an ISO 19162 standard point of view, |
| * but historically some other software products interpreted the {@code PRIMEM[…]} units wrongly, which |
| * is why we try to find a compromise between keeping the WKT simple and avoiding an historical ambiguity. |
| * |
| * @see org.apache.sis.referencing.crs.AbstractCRS#isBaseCRS(Formatter) |
| */ |
| private static boolean isElementOfBaseCRS(final Formatter formatter) { |
| return formatter.getEnclosingElement(2) instanceof GeneralDerivedCRS; |
| } |
| |
| /** |
| * Returns {@code true} if {@link #formatTo(Formatter)} should conservatively format the angular unit |
| * even if it would be legal to omit it. |
| * |
| * <h4>Rational</h4> |
| * According the ISO 19162 standard, it is legal to omit the {@code PrimeMeridian} angular unit when |
| * that unit is the same than the unit of the axes of the enclosing {@code GeographicCRS}. However the |
| * relationship between the CRS axes and the prime meridian is less obvious in WKT2 than it was in WKT1, |
| * because the WKT2 {@code UNIT[…]} element is far from the {@code PRIMEM[…]} element while it was just |
| * below it in WKT1. Furthermore, the {@code PRIMEM[…]} unit is one source of incompatibility between |
| * various WKT1 parsers (i.e. some popular libraries are not conform to OGC 01-009 and ISO 19162). |
| * So we are safer to unconditionally format any unit other than degrees, even if we could legally |
| * omit them. |
| * |
| * <p>However in order to keep the WKT slightly simpler in {@link Convention#WKT2_SIMPLIFIED} mode, |
| * we make an exception to the above-cited safety if the {@code UNIT[…]} element is formatted right |
| * below the {@code PRIMEM[…]} one, which happen if we are inside a base CRS. |
| * See {@link #isElementOfBaseCRS(Formatter)} for more discussion. |
| */ |
| private static boolean beConservative(final Formatter formatter, final Unit<Angle> contextualUnit) { |
| return !contextualUnit.equals(Units.DEGREE) && !isElementOfBaseCRS(formatter); |
| } |
| |
| /** |
| * Formats this prime meridian as a <cite>Well Known Text</cite> {@code PrimeMeridian[…]} element. |
| * |
| * @return {@code "PrimeMeridian"} (WKT 2) or {@code "PrimeM"} (WKT 1). |
| * |
| * @see <a href="http://docs.opengeospatial.org/is/12-063r5/12-063r5.html#53">WKT 2 specification §8.2.2</a> |
| */ |
| @Override |
| protected String formatTo(final Formatter formatter) { |
| super.formatTo(formatter); |
| final Convention convention = formatter.getConvention(); |
| final boolean isWKT1 = (convention.majorVersion() == 1); |
| final Unit<Angle> contextualUnit = formatter.toContextualUnit(Units.DEGREE); |
| Unit<Angle> unit = contextualUnit; |
| if (!isWKT1) { |
| unit = getAngularUnit(); |
| if (convention != Convention.INTERNAL) { |
| unit = WKTUtilities.toFormattable(unit); |
| } |
| } |
| formatter.append(getGreenwichLongitude(unit)); |
| if (isWKT1) { |
| return WKTKeywords.PrimeM; |
| } |
| if (!convention.isSimplified() || !contextualUnit.equals(unit) || beConservative(formatter, contextualUnit)) { |
| formatter.append(unit); |
| } |
| return formatter.shortOrLong(WKTKeywords.PrimeM, WKTKeywords.PrimeMeridian); |
| } |
| |
| |
| |
| |
| ////////////////////////////////////////////////////////////////////////////////////////////////// |
| //////// //////// |
| //////// 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 reflexion. |
| */ |
| private DefaultPrimeMeridian() { |
| super(org.apache.sis.internal.referencing.NilReferencingObject.INSTANCE); |
| /* |
| * Angular units 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 CD_PrimeMeridian adapter does some |
| * verifications. |
| */ |
| } |
| |
| /** |
| * Invoked by JAXB for obtaining the Greenwich longitude to marshal together with its {@code "uom"} attribute. |
| */ |
| @XmlElement(name = "greenwichLongitude", required = true) |
| private Measure getGreenwichMeasure() { |
| return new Measure(greenwichLongitude, angularUnit); |
| } |
| |
| /** |
| * Invoked by JAXB for setting the Greenwich longitude and its unit of measurement. |
| */ |
| private void setGreenwichMeasure(final Measure measure) { |
| if (greenwichLongitude == 0 && angularUnit == null) { |
| greenwichLongitude = measure.value; |
| angularUnit = measure.getUnit(Angle.class); |
| if (angularUnit == null) { |
| /* |
| * Missing unit: if the Greenwich longitude is zero, any angular unit gives the same result |
| * (assuming that the missing unit was not applying an offset), so we can select a default. |
| * If the Greenwich longitude is not zero, presume egrees but log a warning. |
| */ |
| angularUnit = Units.DEGREE; |
| if (greenwichLongitude != 0) { |
| Measure.missingUOM(DefaultPrimeMeridian.class, "setGreenwichMeasure"); |
| } |
| } |
| } else { |
| MetadataUtilities.propertyAlreadySet(DefaultPrimeMeridian.class, "setGreenwichMeasure", "greenwichLongitude"); |
| } |
| } |
| } |