| /* |
| * 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.Length; |
| import javax.measure.UnitConverter; |
| import javax.xml.bind.Unmarshaller; |
| 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.datum.Ellipsoid; |
| import org.opengis.referencing.ReferenceIdentifier; |
| import org.apache.sis.internal.util.Numerics; |
| import org.apache.sis.internal.util.DoubleDouble; |
| import org.apache.sis.internal.jaxb.gml.Measure; |
| import org.apache.sis.internal.jaxb.referencing.SecondDefiningParameter; |
| import org.apache.sis.internal.metadata.MetadataUtilities; |
| import org.apache.sis.internal.referencing.Formulas; |
| import org.apache.sis.internal.referencing.WKTKeywords; |
| import org.apache.sis.referencing.IdentifiedObjects; |
| import org.apache.sis.referencing.AbstractIdentifiedObject; |
| import org.apache.sis.io.wkt.Formatter; |
| import org.apache.sis.io.wkt.Convention; |
| import org.apache.sis.util.ComparisonMode; |
| import org.apache.sis.util.Utilities; |
| import org.apache.sis.measure.Units; |
| |
| import static java.lang.Double.*; |
| import static org.apache.sis.util.ArgumentChecks.ensureStrictlyPositive; |
| import static org.apache.sis.util.ArgumentChecks.ensureNonNull; |
| |
| |
| /** |
| * Geometric figure that can be used to describe the approximate shape of the earth. |
| * In mathematical terms, it is a surface formed by the rotation of an ellipse about |
| * its minor axis. An ellipsoid requires two defining parameters: |
| * |
| * <ul> |
| * <li>{@linkplain #getSemiMajorAxis() semi-major axis} and |
| * {@linkplain #getInverseFlattening() inverse flattening}, or</li> |
| * <li>{@linkplain #getSemiMajorAxis() semi-major axis} and |
| * {@linkplain #getSemiMinorAxis() semi-minor axis}.</li> |
| * </ul> |
| * |
| * Some numerical values derived from the above properties are: |
| * |
| * <ul> |
| * <li>{@linkplain #getAuthalicRadius() authalic radius}</li> |
| * <li>{@linkplain #getEccentricity() eccentricity}</li> |
| * </ul> |
| * |
| * <div class="section">Creating new ellipsoid instances</div> |
| * 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 ellipsoids, |
| * 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 an ellipsoid. |
| * 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 an {@code Ellipsoid} from one of the static convenience shortcuts listed in |
| * {@link org.apache.sis.referencing.CommonCRS#ellipsoid()}.</li> |
| * <li>Create an {@code Ellipsoid} from an identifier in a database by invoking |
| * {@link org.opengis.referencing.datum.DatumAuthorityFactory#createEllipsoid(String)}.</li> |
| * <li>Create an {@code Ellipsoid} by invoking the {@code DatumFactory.createEllipsoid(…)} or {@code createFlattenedSphere(…)} |
| * method (implemented for example by {@link org.apache.sis.referencing.factory.GeodeticObjectFactory}).</li> |
| * <li>Create a {@code DefaultEllipsoid} by invoking the |
| * {@link #createEllipsoid(Map, double, double, Unit) createEllipsoid(…)} or |
| * {@link #createFlattenedSphere(Map, double, double, Unit) createFlattenedSphere(…)} |
| * static methods defined in this class.</li> |
| * </ol> |
| * |
| * <b>Example:</b> the following code gets the WGS84 ellipsoid: |
| * |
| * {@preformat java |
| * Ellipsoid e = CommonCRS.WGS84.ellipsoid(); |
| * } |
| * |
| * <div class="section">Immutability and thread safety</div> |
| * This class is immutable and thus thread-safe if the property <em>values</em> (not necessarily the map itself) |
| * given to the constructors 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 1.0 |
| * |
| * @see org.apache.sis.referencing.CommonCRS#ellipsoid() |
| * @see org.apache.sis.referencing.factory.GeodeticAuthorityFactory#createEllipsoid(String) |
| * |
| * @since 0.4 |
| * @module |
| */ |
| @XmlType(name = "EllipsoidType", propOrder = { |
| "semiMajorAxisMeasure", |
| "secondDefiningParameter" |
| }) |
| @XmlRootElement(name = "Ellipsoid") |
| public class DefaultEllipsoid extends AbstractIdentifiedObject implements Ellipsoid { |
| /** |
| * Serial number for inter-operability with different versions. |
| */ |
| private static final long serialVersionUID = -1149451543954764081L; |
| |
| /** |
| * Tolerance threshold for comparing floating point numbers. |
| * |
| * @see Numerics#COMPARISON_THRESHOLD |
| */ |
| private static final double COMPARISON_THRESHOLD = 1E-10; |
| |
| /** |
| * The equatorial radius. This field should be considered as final. |
| * It is modified only by JAXB at unmarshalling time. |
| * |
| * @see #getSemiMajorAxis() |
| */ |
| private double semiMajorAxis; |
| |
| /** |
| * The polar radius. This field should be considered as final. |
| * It is modified only by JAXB at unmarshalling time. |
| * |
| * @see #getSemiMinorAxis() |
| */ |
| private double semiMinorAxis; |
| |
| /** |
| * The inverse of the flattening value, or {@link Double#POSITIVE_INFINITY} if the ellipsoid is a sphere. |
| * This field shall be considered as final. It is modified only by JAXB at unmarshalling time. |
| * |
| * @see #getInverseFlattening() |
| */ |
| private double inverseFlattening; |
| |
| /** |
| * Tells if the Inverse Flattening is definitive for this ellipsoid. |
| * This field shall be considered as final. It is modified only by JAXB at unmarshalling time. |
| * |
| * @see #isIvfDefinitive() |
| */ |
| private boolean ivfDefinitive; |
| |
| /** |
| * The units of the semi-major and semi-minor axis values. |
| */ |
| private Unit<Length> unit; |
| |
| /** |
| * Creates a new ellipsoid using the specified axis length. |
| * 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 semiMajorAxis the equatorial radius. |
| * @param semiMinorAxis the polar radius. |
| * @param inverseFlattening the inverse of the flattening value. |
| * @param ivfDefinitive {@code true} if the inverse flattening is definitive. |
| * @param unit the units of the semi-major and semi-minor axis values. |
| * |
| * @see #createEllipsoid(Map, double, double, Unit) |
| * @see #createFlattenedSphere(Map, double, double, Unit) |
| */ |
| protected DefaultEllipsoid(final Map<String,?> properties, |
| final double semiMajorAxis, |
| final double semiMinorAxis, |
| final double inverseFlattening, |
| final boolean ivfDefinitive, |
| final Unit<Length> unit) |
| { |
| super(properties); |
| ensureNonNull ("unit", unit); |
| ensureStrictlyPositive("semiMajorAxis", semiMajorAxis); |
| ensureStrictlyPositive("semiMinorAxis", semiMinorAxis); |
| ensureStrictlyPositive("inverseFlattening", inverseFlattening); |
| this.unit = unit; |
| this.semiMajorAxis = semiMajorAxis; |
| this.semiMinorAxis = semiMinorAxis; |
| this.inverseFlattening = inverseFlattening; |
| this.ivfDefinitive = ivfDefinitive; |
| } |
| |
| /** |
| * Creates a new ellipsoid 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 ellipsoid the ellipsoid to copy. |
| * |
| * @see #castOrCopy(Ellipsoid) |
| */ |
| protected DefaultEllipsoid(final Ellipsoid ellipsoid) { |
| super(ellipsoid); |
| semiMajorAxis = ellipsoid.getSemiMajorAxis(); |
| semiMinorAxis = ellipsoid.getSemiMinorAxis(); |
| inverseFlattening = ellipsoid.getInverseFlattening(); |
| ivfDefinitive = ellipsoid.isIvfDefinitive(); |
| unit = ellipsoid.getAxisUnit(); |
| } |
| |
| /** |
| * Creates a new ellipsoid using the specified properties and axis length. |
| * The properties map is given unchanged to the |
| * {@linkplain AbstractIdentifiedObject#AbstractIdentifiedObject(Map) super-class constructor}. |
| * |
| * @param properties the properties to be given to the identified object. |
| * @param semiMajorAxis the equatorial radius in the given unit. |
| * @param semiMinorAxis the polar radius in the given unit. |
| * @param unit the units of the semi-major and semi-minor axis values. |
| * @return an ellipsoid with the given axis length. |
| * |
| * @see org.apache.sis.referencing.factory.GeodeticObjectFactory#createEllipsoid(Map, double, double, Unit) |
| */ |
| public static DefaultEllipsoid createEllipsoid(final Map<String,?> properties, |
| final double semiMajorAxis, |
| final double semiMinorAxis, |
| final Unit<Length> unit) |
| { |
| if (semiMajorAxis == semiMinorAxis) { |
| return new Sphere(properties, semiMajorAxis, false, unit); |
| } else { |
| return new DefaultEllipsoid(properties, semiMajorAxis, semiMinorAxis, |
| Formulas.getInverseFlattening(semiMajorAxis, semiMinorAxis), false, unit); |
| } |
| } |
| |
| /** |
| * Creates a new ellipsoid using the specified properties, axis length and inverse flattening value. |
| * The properties map is given unchanged to the |
| * {@linkplain AbstractIdentifiedObject#AbstractIdentifiedObject(Map) super-class constructor}. |
| * |
| * @param properties the properties to be given to the identified object. |
| * @param semiMajorAxis the equatorial radius in the given unit. |
| * @param inverseFlattening the inverse flattening value. |
| * @param unit the units of the semi-major and semi-minor axis values. |
| * @return an ellipsoid with the given axis length. |
| * |
| * @see org.apache.sis.referencing.factory.GeodeticObjectFactory#createFlattenedSphere(Map, double, double, Unit) |
| */ |
| public static DefaultEllipsoid createFlattenedSphere(final Map<String,?> properties, |
| final double semiMajorAxis, |
| final double inverseFlattening, |
| final Unit<Length> unit) |
| { |
| if (isInfinite(inverseFlattening)) { |
| return new Sphere(properties, semiMajorAxis, true, unit); |
| } else { |
| return new DefaultEllipsoid(properties, semiMajorAxis, |
| Formulas.getSemiMinor(semiMajorAxis, inverseFlattening), |
| inverseFlattening, true, unit); |
| } |
| } |
| |
| /** |
| * Returns a SIS ellipsoid 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 DefaultEllipsoid castOrCopy(final Ellipsoid object) { |
| if (object == null || object instanceof DefaultEllipsoid) { |
| return (DefaultEllipsoid) object; |
| } |
| final Map<String,?> properties = IdentifiedObjects.getProperties(object); |
| final double semiMajor = object.getSemiMajorAxis(); |
| final Unit<Length> unit = object.getAxisUnit(); |
| return object.isIvfDefinitive() ? |
| createFlattenedSphere(properties, semiMajor, object.getInverseFlattening(), unit) : |
| createEllipsoid (properties, semiMajor, object.getSemiMinorAxis(), unit); |
| } |
| |
| /** |
| * Returns the GeoAPI interface implemented by this class. |
| * The SIS implementation returns {@code Ellipsoid.class}. |
| * |
| * <div class="note"><b>Note for implementers:</b> |
| * Subclasses usually do not need to override this method since GeoAPI does not define {@code Ellipsoid} |
| * sub-interface. Overriding possibility is left mostly for implementers who wish to extend GeoAPI with |
| * their own set of interfaces.</div> |
| * |
| * @return {@code Ellipsoid.class} or a user-defined sub-interface. |
| */ |
| @Override |
| public Class<? extends Ellipsoid> getInterface() { |
| return Ellipsoid.class; |
| } |
| |
| /** |
| * Returns the linear unit of the {@linkplain #getSemiMajorAxis() semi-major} |
| * and {@linkplain #getSemiMinorAxis() semi-minor} axis values. |
| * |
| * @return the axis linear unit. |
| */ |
| @Override |
| public Unit<Length> getAxisUnit() { |
| return unit; |
| } |
| |
| /** |
| * Length of the semi-major axis of the ellipsoid. |
| * This is the equatorial radius in {@linkplain #getAxisUnit() axis linear unit}. |
| * |
| * @return length of semi-major axis. |
| */ |
| @Override |
| public double getSemiMajorAxis() { |
| return semiMajorAxis; |
| } |
| |
| /** |
| * Length of the semi-minor axis of the ellipsoid. This is the |
| * polar radius in {@linkplain #getAxisUnit() axis linear unit}. |
| * |
| * @return length of semi-minor axis. |
| */ |
| @Override |
| public double getSemiMinorAxis() { |
| return semiMinorAxis; |
| } |
| |
| /** |
| * Returns the radius of a hypothetical sphere having the same surface than this ellipsoid. |
| * The radius is expressed in {@linkplain #getAxisUnit() axis linear unit}. |
| * |
| * @return the radius of a sphere having the same surface than this ellipsoid. |
| * |
| * @see org.apache.sis.referencing.CommonCRS#SPHERE |
| */ |
| public double getAuthalicRadius() { |
| return Formulas.getAuthalicRadius(getSemiMajorAxis(), getSemiMinorAxis()); |
| } |
| |
| /** |
| * The ratio of the distance between the center and a focus of the ellipse to the length of its semi-major axis. |
| * The eccentricity can alternately be computed from the equation: ℯ = √(2f - f²) where <var>f</var> is the |
| * flattening factor (not inverse). |
| * |
| * @return ℯ, the eccentricity of this ellipsoid. |
| */ |
| public double getEccentricity() { |
| final DoubleDouble e = eccentricitySquared(); |
| e.sqrt(); |
| return e.doubleValue(); |
| } |
| |
| /** |
| * Returns the square of the {@link #getEccentricity() eccentricity} value. |
| * |
| * <div class="note"><b>Purpose:</b> |
| * this convenience method is provided because ℯ² is frequently used in coordinate operations, |
| * actually more often than ℯ. This convenience method avoids the cost of computing the square |
| * root when not needed.</div> |
| * |
| * @return ℯ², the square of the eccentricity value. |
| * |
| * @since 0.7 |
| */ |
| public double getEccentricitySquared() { |
| return eccentricitySquared().doubleValue(); |
| } |
| |
| /** |
| * Computes the square of the eccentricity value with ℯ² = 2f - f². |
| * |
| * <div class="note"><b>Implementation note:</b> |
| * we use the flattening factor for this computation because the inverse flattening factor is usually the |
| * second defining parameter. But even if the second defining parameter of this ellipsoid was rather the |
| * semi-minor axis, the fact that we use double-double arithmetic should give the same result anyway.</div> |
| */ |
| private DoubleDouble eccentricitySquared() { |
| final DoubleDouble f = flattening(this); |
| final DoubleDouble eccentricitySquared = new DoubleDouble(f); |
| eccentricitySquared.multiply(2); |
| f.square(); |
| eccentricitySquared.subtract(f); |
| return eccentricitySquared; |
| } |
| |
| /** |
| * Computes the flattening factor (not inverse) of the given ellipsoid. |
| * This method chooses the formula depending on whether the defining parameter is the inverse flattening factor |
| * or the semi-minor axis length. The defining parameters are presumed fully accurate in base 10 (even if this |
| * is of course not possible in the reality), because those parameters are definitions given by authorities. |
| * |
| * <div class="note"><b>Analogy:</b> |
| * the conversion factor from inches to centimetres is 2.54 <em>by definition</em>. Even if we could find a more |
| * accurate value matching historical measurements, the 2.54 value is the internationally agreed value for all |
| * conversions. This value is (by convention) defined in base 10 and has no exact {@code double} representation. |
| * </div> |
| */ |
| private static DoubleDouble flattening(final Ellipsoid e) { |
| final DoubleDouble f; |
| if (e.isIvfDefinitive()) { |
| f = DoubleDouble.createAndGuessError(e.getInverseFlattening()); // Presumed accurate in base 10 (not 2) by definition. |
| f.inverseDivide(1); |
| } else { |
| f = DoubleDouble.createAndGuessError(e.getSemiMajorAxis()); // Presumed accurate in base 10 (not 2) by definition. |
| final double value = f.value; |
| final double error = f.error; |
| f.subtractGuessError(e.getSemiMinorAxis()); // Presumed accurate in base 10 (not 2) by definition. |
| f.divide(value, error); |
| } |
| return f; |
| } |
| |
| /** |
| * Returns the value of the inverse of the flattening constant. Flattening is a value |
| * used to indicate how closely an ellipsoid approaches a spherical shape. The inverse |
| * flattening is related to the equatorial/polar radius by the formula: |
| * |
| * <blockquote> |
| * <var>ivf</var> = <var>r</var><sub>e</sub> / (<var>r</var><sub>e</sub> - <var>r</var><sub>p</sub>). |
| * </blockquote> |
| * |
| * For perfect spheres (i.e. if {@link #isSphere()} returns {@code true}), |
| * the {@link Double#POSITIVE_INFINITY} value is used. |
| * |
| * @return the inverse flattening value. |
| */ |
| @Override |
| public double getInverseFlattening() { |
| return inverseFlattening; |
| } |
| |
| /** |
| * Indicates if the {@linkplain #getInverseFlattening() inverse flattening} is definitive for |
| * this ellipsoid. Some ellipsoids use the IVF as the defining value, and calculate the polar |
| * radius whenever asked. Other ellipsoids use the polar radius to calculate the IVF whenever |
| * asked. This distinction can be important to avoid floating-point rounding errors. |
| * |
| * @return {@code true} if the {@linkplain #getInverseFlattening inverse flattening} is definitive, |
| * or {@code false} if the {@linkplain #getSemiMinorAxis() polar radius} is definitive. |
| */ |
| @Override |
| public boolean isIvfDefinitive() { |
| return ivfDefinitive; |
| } |
| |
| /** |
| * {@code true} if the ellipsoid is degenerate and is actually a sphere. |
| * The sphere is completely defined by the {@linkplain #getSemiMajorAxis() semi-major axis}, |
| * which is the radius of the sphere. |
| * |
| * @return {@code true} if the ellipsoid is degenerate and is actually a sphere. |
| */ |
| @Override |
| public boolean isSphere() { |
| return semiMajorAxis == semiMinorAxis; |
| } |
| |
| /** |
| * Returns the difference between the semi-major axis length of two ellipsoids. |
| * If the two ellipsoid does not use the same unit of measurement, than the axis |
| * length of the other ellipsoid is converted into the units of this ellipsoid axis. |
| * |
| * <div class="note"><b>Example:</b> |
| * {@code WGS84.semiMajorAxisDifference(ED50)} returns 251 metres. This information is a parameter of |
| * {@linkplain org.apache.sis.referencing.operation.transform.MolodenskyTransform Molodensky transformations}.</div> |
| * |
| * @param other the other ellipsoid from which to get semi-major axis length difference. |
| * @return (<var>other</var> ellipsoid semi-major axis) - (<var>this</var> ellipsoid semi-major axis). |
| * |
| * @since 0.7 |
| */ |
| public double semiMajorAxisDifference(final Ellipsoid other) { |
| double semiMajor = other.getSemiMajorAxis(); |
| semiMajor = other.getAxisUnit().getConverterTo(getAxisUnit()).convert(semiMajor); // Often a no-op. |
| final DoubleDouble a = DoubleDouble.createAndGuessError(semiMajor); // Presumed accurate in base 10 if no unit conversion. |
| a.subtractGuessError(getSemiMajorAxis()); // Presumed accurate in base 10 (not 2) by definition. |
| return a.doubleValue(); |
| } |
| |
| /** |
| * Returns the difference between the flattening factor of two ellipsoids. |
| * This method returns 0 if the two ellipsoids are equal. |
| * |
| * <div class="note"><b>Example:</b> |
| * {@code WGS84.flatteningDifference(ED50)} returns approximately 1.41927E-05. This information is a parameter of |
| * {@linkplain org.apache.sis.referencing.operation.transform.MolodenskyTransform Molodensky transformations}.</div> |
| * |
| * @param other the other ellipsoid from which to get flattening difference. |
| * @return (<var>other</var> ellipsoid flattening) - (<var>this</var> ellipsoid flattening). |
| * |
| * @since 0.7 |
| */ |
| public double flatteningDifference(final Ellipsoid other) { |
| final DoubleDouble f = flattening(other); |
| f.subtract(flattening(this)); |
| return f.doubleValue(); |
| } |
| |
| /** |
| * Compares this ellipsoid 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 |
| @SuppressWarnings("fallthrough") |
| public boolean equals(final Object object, final ComparisonMode mode) { |
| if (object == this) { |
| return true; // Slight optimization. |
| } |
| if (!super.equals(object, mode)) { |
| return false; |
| } |
| switch (mode) { |
| case STRICT: { |
| final DefaultEllipsoid that = (DefaultEllipsoid) object; |
| return ivfDefinitive == that.ivfDefinitive && |
| Numerics.equals(this.semiMajorAxis, that.semiMajorAxis) && |
| Numerics.equals(this.semiMinorAxis, that.semiMinorAxis) && |
| Numerics.equals(this.inverseFlattening, that.inverseFlattening) && |
| Objects.equals(this.unit, that.unit); |
| } |
| case BY_CONTRACT: { |
| /* |
| * isIvfDefinitive has no incidence on calculation using ellipsoid parameters, |
| * so we consider it as metadata that can be ignored in IGNORE_METADATA mode. |
| */ |
| if (isIvfDefinitive() != ((Ellipsoid) object).isIvfDefinitive()) { |
| return false; |
| } |
| // Fall through |
| } |
| case IGNORE_METADATA: { |
| /* |
| * "Inverse flattening factor" and "semi-minor axis length" are computed from each other, |
| * so we do not need to compare both of them. But in non-approximated mode we nevertheless |
| * compare both as a safety against rounding errors. |
| */ |
| if (!Numerics.equals(getInverseFlattening(), ((Ellipsoid) object).getInverseFlattening())) { |
| return false; |
| } |
| // Fall through |
| } |
| default: { |
| /* |
| * Note: DefaultPrimeMeridian.equals(object, IGNORE_METADATA) ignores the unit. |
| * But we do not perform the same relaxation here because the ellipsoid unit will |
| * become the linear unit of map projections if the user does not overwrite them |
| * with an explicit CoordinateSystem declaration. |
| */ |
| final Ellipsoid that = (Ellipsoid) object; |
| final Unit<Length> unit = getAxisUnit(); // In case the user override this method. |
| if (!Utilities.deepEquals(unit, that.getAxisUnit(), mode)) { |
| return false; |
| } |
| final UnitConverter c = mode.isApproximate() ? unit.getConverterTo(Units.METRE) : null; |
| boolean isMinor = false; |
| double v1 = this.getSemiMajorAxis(); |
| double v2 = that.getSemiMajorAxis(); |
| if (c == null ? Numerics.equals(v1, v2) : Numerics.epsilonEqual( |
| c.convert(v1), c.convert(v2), Formulas.LINEAR_TOLERANCE)) |
| { |
| isMinor = true; |
| v1 = this.getSemiMinorAxis(); |
| v2 = that.getSemiMinorAxis(); |
| if (c == null ? Numerics.equals(v1, v2) : Numerics.epsilonEqual( |
| c.convert(v1), c.convert(v2), Formulas.LINEAR_TOLERANCE)) |
| { |
| return true; |
| } |
| } |
| assert (mode != ComparisonMode.DEBUG) : Numerics.messageForDifference( |
| isMinor ? "semiMinorAxis" : "semiMajorAxis", 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(semiMajorAxis) + |
| 31 * Double.doubleToLongBits(ivfDefinitive ? inverseFlattening : semiMinorAxis); |
| } |
| |
| /** |
| * Formats this ellipsoid as a <cite>Well Known Text</cite> {@code Ellipsoid[…]} element. |
| * |
| * @return {@code "Ellipsoid"} (WKT 2) or {@code "Spheroid"} (WKT 1). |
| * |
| * @see <a href="http://docs.opengeospatial.org/is/12-063r5/12-063r5.html#52">WKT 2 specification §8.2.1</a> |
| */ |
| @Override |
| protected String formatTo(final Formatter formatter) { |
| super.formatTo(formatter); |
| final Convention convention = formatter.getConvention(); |
| final boolean isWKT1 = convention.majorVersion() == 1; |
| final Unit<Length> unit = getAxisUnit(); // Gives to users a chance to override properties. |
| double length = getSemiMajorAxis(); |
| if (isWKT1) { |
| length = unit.getConverterTo(Units.METRE).convert(length); |
| } |
| formatter.append(length); |
| final double inverseFlattening = getInverseFlattening(); // Gives to users a chance to override properties. |
| formatter.append(isInfinite(inverseFlattening) ? 0 : inverseFlattening); |
| if (isWKT1) { |
| return WKTKeywords.Spheroid; |
| } |
| if (!convention.isSimplified() || !Units.METRE.equals(unit)) { |
| formatter.append(unit); |
| } |
| return WKTKeywords.Ellipsoid; |
| } |
| |
| |
| |
| |
| ////////////////////////////////////////////////////////////////////////////////////////////////// |
| //////// //////// |
| //////// 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 DefaultEllipsoid() { |
| super(org.apache.sis.internal.referencing.NilReferencingObject.INSTANCE); |
| /* |
| * We need to let the DefaultEllipsoid fields unitialized because afterUnmarshal(…) |
| * will check for zero values. We can not thrown an exception from 'afterUnmarshal' |
| * because it would cause the whole unmarshalling to fail. But the CD_Ellipsoid |
| * adapter does some verifications. |
| */ |
| } |
| |
| /** |
| * After the unmarshalling process, only one value between {@link #semiMinorAxis} and |
| * {@link #inverseFlattening} has been defined. Since the {@link #semiMajorAxis} has |
| * been defined, it is now possible to calculate the value of the missing parameter |
| * using the values of those that are set. |
| * |
| * @see #setSemiMajorAxisMeasure(Measure) |
| * @see #setSecondDefiningParameter(SecondDefiningParameter) |
| */ |
| private void afterUnmarshal(Unmarshaller unmarshaller, Object parent) { |
| if (ivfDefinitive) { |
| if (semiMinorAxis == 0) { |
| semiMinorAxis = Formulas.getSemiMinor(semiMajorAxis, inverseFlattening); |
| } |
| } else { |
| if (inverseFlattening == 0) { |
| inverseFlattening = Formulas.getInverseFlattening(semiMajorAxis, semiMinorAxis); |
| } else if (inverseFlattening == Double.POSITIVE_INFINITY && semiMinorAxis == 0) { |
| semiMinorAxis = semiMajorAxis; |
| } |
| } |
| if (unit == null) { |
| unit = Units.METRE; |
| Measure.missingUOM(DefaultEllipsoid.class, "semiMajorAxis"); |
| } |
| } |
| |
| /** |
| * Returns the semi-major axis value as a measurement. |
| * This method is invoked by JAXB for XML marshalling. |
| */ |
| @XmlElement(name = "semiMajorAxis", required = true) |
| private Measure getSemiMajorAxisMeasure() { |
| return new Measure(semiMajorAxis, unit); |
| } |
| |
| /** |
| * Sets the semi-major axis value. |
| * This method is invoked by JAXB at unmarshalling time only. |
| * |
| * @see #setSecondDefiningParameter(SecondDefiningParameter) |
| * @see #afterUnmarshal(Unmarshaller, Object) |
| */ |
| private void setSemiMajorAxisMeasure(final Measure measure) { |
| if (semiMajorAxis == 0) { |
| final Unit<Length> uom = unit; // In case semi-minor were defined before semi-major. |
| ensureStrictlyPositive("semiMajorAxis", semiMajorAxis = measure.value); |
| unit = measure.getUnit(Length.class); |
| harmonizeAxisUnits(uom); |
| } else { |
| MetadataUtilities.propertyAlreadySet(DefaultEllipsoid.class, "setSemiMajorAxisMeasure", "semiMajorAxis"); |
| } |
| } |
| |
| /** |
| * Returns the object to be marshalled as the {@code SecondDefiningParameter} XML element. The |
| * returned object contains the values for {@link #semiMinorAxis} or {@link #inverseFlattening}, |
| * according to the {@link #isIvfDefinitive()} value. This method is for JAXB marshalling only. |
| */ |
| @XmlElement(name = "secondDefiningParameter", required = true) |
| private SecondDefiningParameter getSecondDefiningParameter() { |
| return new SecondDefiningParameter(this, true); |
| } |
| |
| /** |
| * Sets the second defining parameter value, either the inverse of the flattening |
| * value or the semi minor axis value, according to what have been defined in the |
| * second defining parameter given. This is for JAXB unmarshalling process only. |
| * |
| * @see #setSemiMajorAxisMeasure(Measure) |
| * @see #afterUnmarshal(Unmarshaller, Object) |
| */ |
| private void setSecondDefiningParameter(SecondDefiningParameter second) { |
| if (second.secondDefiningParameter != null) { |
| second = second.secondDefiningParameter; |
| } |
| boolean duplicate = false; |
| if (Boolean.TRUE.equals(second.isSphere)) { |
| duplicate = (inverseFlattening != 0); |
| if (!duplicate) { |
| inverseFlattening = Double.POSITIVE_INFINITY; |
| } |
| } |
| final Measure measure = second.measure; |
| if (measure != null) { |
| final boolean isIvfDefinitive = second.isIvfDefinitive(); |
| duplicate |= (isIvfDefinitive ? inverseFlattening : semiMinorAxis) != 0; |
| if (!duplicate) { |
| ivfDefinitive = isIvfDefinitive; |
| double value = measure.value; |
| if (isIvfDefinitive) { |
| /* |
| * Interpreting an inverse flattening factor of 0 as synonymous of infinity |
| * is a Well-Known Text (WKT) convention, not part of GML standard. However |
| * in practice some software do that. |
| */ |
| if (value == 0) { |
| value = Double.POSITIVE_INFINITY; |
| } |
| ensureStrictlyPositive("inverseFlattening", inverseFlattening = value); |
| } else { |
| ensureStrictlyPositive("semiMinorAxis", semiMinorAxis = value); |
| harmonizeAxisUnits(measure.getUnit(Length.class)); |
| } |
| } |
| } |
| if (duplicate) { |
| MetadataUtilities.propertyAlreadySet(DefaultEllipsoid.class, |
| "setSecondDefiningParameter", "secondDefiningParameter"); |
| } |
| } |
| |
| /** |
| * Ensures that the semi-minor axis uses the same unit than the semi-major one. |
| * The {@link #unit} field shall be set to the semi-major axis unit before this method call. |
| * |
| * @param uom the semi-minor axis unit. |
| */ |
| private void harmonizeAxisUnits(final Unit<Length> uom) { |
| if (unit == null) { |
| unit = uom; |
| } else if (uom != null && uom != unit) { |
| semiMinorAxis = uom.getConverterTo(unit).convert(semiMinorAxis); |
| } |
| } |
| } |