/*
 * 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.measure;

import org.opengis.geometry.DirectPosition;
import org.opengis.referencing.cs.AxisDirection;


/**
 * A latitude angle in decimal degrees.
 * Positive latitudes are North, while negative latitudes are South.
 * The latitude symbol is the Greek lower-case letter phi (φ).
 *
 * <p>Because the Earth is not a perfect sphere, there is small differences in the latitude values of a point
 * depending on how the latitude is defined:</p>
 *
 * <ul>
 *   <li><cite>Geodetic latitude</cite> is the angle between the equatorial plane and a line perpendicular
 *       to the {@linkplain org.apache.sis.referencing.datum.DefaultEllipsoid ellipsoid} surface.</li>
 *   <li><cite>Geocentric latitude</cite> is the angle between the equatorial plane and a line going from
 *       the Earth center. It differs from geodetic latitude by less than 11 angular minutes.</li>
 *   <li><cite>Astronomical latitude</cite> is the angle between the equatorial plane and a line given
 *       by the direction of a plumb line (the "true vertical").</li>
 *   <li>Above list is not exhaustive. There is also <cite>geomagnetic latitude</cite>, <i>etc.</i></li>
 * </ul>
 *
 * The kind of latitude is unspecified by this {@code Latitude} class, and rather depends on the context:
 * the latitude is <cite>geodetic</cite> if the coordinate reference system is
 * {@linkplain org.apache.sis.referencing.crs.DefaultGeographicCRS geographic},
 * or <cite>geocentric</cite> if the coordinate reference system is
 * {@linkplain org.apache.sis.referencing.crs.DefaultGeocentricCRS geocentric}.
 * If the context is unknown, then geodetic latitude can usually be assumed.
 *
 * <h2>Immutability and thread safety</h2>
 * This final class is immutable and thus inherently thread-safe.
 *
 * @author  Martin Desruisseaux (MPO, IRD, Geomatys)
 * @version 0.8
 *
 * @see Longitude
 * @see AngleFormat
 * @see org.apache.sis.geometry.CoordinateFormat
 *
 * @since 0.3
 */
public final class Latitude extends Angle {
    /**
     * Serial number for inter-operability with different versions.
     */
    private static final long serialVersionUID = -2227675003893702061L;

    /**
     * Minimum usual value for latitude ({@value}°).
     *
     * @see org.apache.sis.referencing.cs.DefaultCoordinateSystemAxis#getMinimumValue()
     */
    public static final double MIN_VALUE = -90;

    /**
     * Maximum usual value for latitude (+{@value}°).
     *
     * @see org.apache.sis.referencing.cs.DefaultCoordinateSystemAxis#getMaximumValue()
     */
    public static final double MAX_VALUE = +90;

    /**
     * Construct a new latitude with the specified angular value.
     *
     * @param  φ  latitude value in decimal degrees.
     */
    public Latitude(final double φ) {
        super(φ);
    }

    /**
     * Constructs a newly allocated {@code Latitude} object that contain the angular value
     * represented by the string. The string should represent an angle in either fractional
     * degrees (e.g. 45.5°) or degrees with minutes and seconds (e.g. 45°30').
     * The hemisphere (N or S) is optional (default to North).
     *
     * <p>This is a convenience constructor mostly for testing purpose, since it uses a fixed
     * locale. Developers should consider using {@link AngleFormat} for end-user applications
     * instead of this constructor.</p>
     *
     * @param  string  a string to be converted to a {@code Latitude}.
     * @throws NumberFormatException if the string does not contain a parsable angle,
     *         or represents a longitude angle.
     *
     * @see AngleFormat#parse(String)
     */
    public Latitude(final String string) throws NumberFormatException {
        super(string);
    }

    /**
     * Constructs a newly allocated object containing the latitude value of the given position.
     * For this method, the latitude value is defined as the angular value associated to the first axis
     * oriented toward {@linkplain AxisDirection#NORTH North} or {@linkplain AxisDirection#SOUTH South}.
     * Note that this is not necessarily the <cite>geodetic latitudes</cite> used in
     * {@linkplain org.apache.sis.referencing.crs.DefaultGeographicCRS geographic CRS};
     * it may also be <cite>geocentric latitudes</cite>.
     *
     * <p>If the axis direction is South, then the sign of the coordinate value is inverted.
     * If the coordinate value uses another angular units than {@linkplain Units#DEGREE degrees},
     * then a unit conversion is applied.</p>
     *
     * @param  position  the coordinate from which to extract the latitude value in degrees.
     * @throws IllegalArgumentException if the given coordinate it not associated to a CRS,
     *         or if no axis oriented toward North or South is found, or if that axis does
     *         not use {@linkplain Units#isAngular angular units}.
     *
     * @since 0.8
     */
    public Latitude(final DirectPosition position) throws IllegalArgumentException {
        super(valueOf(position, AxisDirection.NORTH, AxisDirection.SOUTH));
    }

    /**
     * Returns the hemisphere character for an angle of the given sign.
     * This is used only by {@link #toString()}, not by {@link AngleFormat}.
     */
    @Override
    final char hemisphere(final boolean negative) {
        return negative ? 'S' : 'N';
    }

    /**
     * Upper threshold before to format an angle as an ordinary number.
     * This is used only by {@link #toString()}, not by {@link AngleFormat}.
     */
    @Override
    final double maximum() {
        return 90;
    }

    /**
     * Returns the given latitude value clamped to the [{@linkplain #MIN_VALUE -90} … {@linkplain #MAX_VALUE 90}]° range.
     * If the given value is outside the latitude range, then this method replaces it by ±90° with the same sign than the
     * given φ value.
     *
     * <p>Special cases:</p>
     * <ul>
     *   <li>{@linkplain Double#NaN NaN} values are returned unchanged</li>
     *   <li>±∞ are mapped to ±90° (with the same sign)</li>
     *   <li>±0 are returned unchanged (i.e. the sign of negative and positive zero is preserved)</li>
     * </ul>
     *
     * @param  φ  the latitude value in decimal degrees.
     * @return the given value clamped to the [-90 … 90]° range, or NaN if the given value was NaN.
     *
     * @see Longitude#normalize(double)
     *
     * @since 0.4
     */
    public static double clamp(final double φ) {
        if (φ < MIN_VALUE) return MIN_VALUE;
        if (φ > MAX_VALUE) return MAX_VALUE;
        return φ;
    }
}
