blob: f8f75d36ed877519ea35a8e8010cb522a4e79cf3 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.sis.referencing.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);
}
}
}