All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.opensextant.geodesy.Geodetic2DEllipse Maven / Gradle / Ivy

Go to download

Geodesy is a small package to manipulate points on the surface of an ellipsoid model of a planetary sphere

The newest version!
/****************************************************************************************
 *  Geodetic2DEllipse.java
 *
 *  Created: Nov 2, 2007
 *
 *  @author Paul Silvey
 *
 *  (C) Copyright MITRE Corporation 2007
 *
 *  The program is provided "as is" without any warranty express or implied, including 
 *  the warranty of non-infringement and the implied warranties of merchantability and 
 *  fitness for a particular purpose.  The Copyright owner will not be liable for any 
 *  damages suffered by you as a result of using the Program.  In no event will the 
 *  Copyright owner be liable for any special, indirect or consequential damages or 
 *  lost profits even if the Copyright owner has been advised of the possibility of 
 *  their occurrence.
 *
 ***************************************************************************************/
package org.opensextant.geodesy;

import edu.umd.cs.findbugs.annotations.NonNull;

import java.io.Serializable;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * The Geodetic2DEllipse class represents an ellipse on the surface of the earth
 * (itself modeled as an Ellipsoid). Similar to the Geodetic2DPoint class, instances
 * of this object are lightweight in the sense that they represent ellipses in terms
 * of simple parameters: center, semiMajorAxis, semiMinorAxis, and orientation.
 * The center point is specified as a Geodetic2DPoint, the axes are given in meters,
 * and the orientation is an Angle (clockwise positive from North to the semiMajor
 * axis). Just as with the Geodetic2DPoint class, the reference Ellipsoid earth model
 * is not explicitly carried with the objects, but may be needed for some uses of the
 * ellipse. Because an ellipse is symmetrical along either axis, two ellipses whose
 * orientations differ by 180 degrees are equal if all the other parameters are equal.
 */
public class Geodetic2DEllipse implements Serializable {
    private static final long serialVersionUID = 1L;

    @NonNull private Geodetic2DPoint center;
    private double semiMajorAxis;
    private double semiMinorAxis;
    @NonNull private Angle orientation;
    private transient WeakReference> boundary;
    
    private static final String NULL_ERROR = "Ellipse parameters can not be null";
    private static final String AXIS_ERROR = "Semi-major axis must be greater than semi-minor axis";

    /**
     * Default constructor makes a geodetic ellipse at the central meridian on the
     * equator (0, 0), with 0 semi major and minor axes, and 0 degrees orientation.
     */
    public Geodetic2DEllipse() {
        this.center = new Geodetic2DPoint();
        this.semiMajorAxis = 0.0;
        this.semiMinorAxis = 0.0;
        this.orientation = new Angle();
    }

    /**
     * The constructor takes a center point, semi major and minor axes in meters,
     * and angle or orientation from North to the semi major axis.
     *
     * @param center        Geodetic2DPoint at the center of the ellipse
     * @param semiMajorAxis length in meters of the ellipse's semi major axis
     * @param semiMinorAxis length in meters of the ellipse's semi minor axis
     * @param orientation   Angle from North to semi major axis for this ellipse
     * @throws IllegalArgumentException if null parameter or axis ordering error
     */
    public Geodetic2DEllipse(Geodetic2DPoint center, double semiMajorAxis,
                             double semiMinorAxis, Angle orientation)
            throws IllegalArgumentException {
        if ((center == null) || (orientation == null))
            throw new IllegalArgumentException(NULL_ERROR);
        if (semiMajorAxis < semiMinorAxis)
            throw new IllegalArgumentException(AXIS_ERROR);
        this.center = center;
        this.semiMajorAxis = semiMajorAxis;
        this.semiMinorAxis = semiMinorAxis;
        this.orientation = orientation;
    }

    
    /**
     * Get the boundary list for the ellipse
     * @param count the number of slices that the list should include, non zero
     * and should be a multiple of four for best results.
     * @return the boundary iterator
     */
	@NonNull
    public Iterable boundary(int count) {
    	List blist;
    	if (boundary != null) {
    		blist = boundary.get();
    		if (blist != null && blist.size() == count) {
    			return blist;
    		}
    	}
    	blist = Collections.unmodifiableList(makeBoundary(count));
    	boundary = new WeakReference>(blist);
    	return blist;
    }
    
    /**
     * Make a boundary list for a given count
     * @param count the number of slices in the boundary list
     * @return the boundary list, never null or empty
     */
	@NonNull
    private List makeBoundary(int count) {
    	// omega is the ellipse orientation angle (final rotation adjustment)
        Angle omega = new Angle(getOrientation().inRadians());
        // theta is the normalized circular sweep angle that
        // varys from -180 to +180 degrees
        Angle theta = new Angle(-Math.PI);
        // delta is the variable angular increment value
        Angle delta;

        // Use polar coordinate equation for an ellipse, where e is the
        // eccentricity.  Angle (theta) sweeps around the circle, and r
        // changes as a function of it
        double t, r;
        double a = getSemiMajorAxis();
        double b = getSemiMinorAxis();
        double e = Math.sqrt(1.0 - ((b * b) / (a * a)));

        // nominal delta in degrees (average angle for n steps around circle)
        double nDelta = 360.0 / count;
        Angle nominalDelta = new Angle(nDelta, Angle.DEGREES);

        // We compute r as a function of theta,
        // then rotate by ellipse orientation amount
        Geodetic2DArc arc = new Geodetic2DArc(getCenter(), 0.0, omega);
        Angle stepAngle = new Angle(0.0, Angle.DEGREES);
        List rval = new ArrayList();
        for (int step = 0; step < count; step++) {
            // y cycles from -1 to +1 to -1 from 0 deg to 180 deg, repeats
            double y = ((1.0 - Math.cos(stepAngle.inRadians() * 2.0)) - 1.0);
            // delta varies from 0 to 2 * nominal delta
            // to make small steps near major axis and larger ones near minor
            delta = new Angle(nDelta + (nDelta * y), Angle.DEGREES);
            theta = theta.add(delta);
            t = Math.cos(theta.inRadians());
            r = b / (Math.sqrt(1.0 - (e * e * t * t)));
            arc.setDistanceAndAzimuth(r, theta.add(omega));
            rval.add(arc.getPoint2());
            stepAngle = stepAngle.add(nominalDelta);
        }
        return rval;
    }
    
    /**
     * This method returns the Geodetic2DPoint at the center of this ellipse.
     *
     * @return Geodetic2DPoint center of ellipse
     */
	@NonNull
    public Geodetic2DPoint getCenter() {
        return center;
    }

    /**
     * This method is used to set the geodetic center point of this ellipse.
     *
     * @param center new Geodetic2DPoint center of this ellipse
     * @throws IllegalArgumentException error if center is null
     */
    public void setCenter(Geodetic2DPoint center) throws IllegalArgumentException {
        if (center == null)
            throw new IllegalArgumentException(NULL_ERROR);
        this.center = center;
    }

    /**
     * This method returns the semi major axis of this ellipse, in meters.
     *
     * @return double distance in meters of this ellipse's semi major axis
     */
    public double getSemiMajorAxis() {
        return semiMajorAxis;
    }

    /**
     * This method is used to set the semi major axis (in meters) for this ellipse.
     *
     * @param semiMajorAxis double distance in meters of this ellipse's semi major axis
     * @throws IllegalArgumentException if axis ordering error
     */
    public void setSemiMajorAxis(double semiMajorAxis) throws IllegalArgumentException {
        if (semiMajorAxis < this.semiMinorAxis)
            throw new IllegalArgumentException(AXIS_ERROR);
        this.semiMajorAxis = semiMajorAxis;
    }

    /**
     * This method returns the semi minor axis of this ellipse, in meters.
     *
     * @return double distance in meters of this ellipse's semi minor axis
     */
    public double getSemiMinorAxis() {
        return semiMinorAxis;
    }

    /**
     * This method is used to set the semi minor axis (in meters) for this ellipse.
     *
     * @param semiMinorAxis double distance in meters of this ellipse's semi minor axis
     * @throws IllegalArgumentException if axis ordering error
     */
    public void setSemiMinorAxis(double semiMinorAxis) throws IllegalArgumentException {
        if (this.semiMajorAxis < semiMinorAxis)
            throw new IllegalArgumentException(AXIS_ERROR);
        this.semiMinorAxis = semiMinorAxis;
    }

    /**
     * This method is used to set the semi major and minor axes of this ellipse, in meters.
     *
     * @param semiMajorAxis length in meters of the ellipse's semi major axis
     * @param semiMinorAxis length in meters of the ellipse's semi minor axis
     * @throws IllegalArgumentException if axis ordering error
     */
    public void setSemiAxes(double semiMajorAxis, double semiMinorAxis)
            throws IllegalArgumentException {
        if (semiMajorAxis < semiMinorAxis)
            throw new IllegalArgumentException(AXIS_ERROR);
        this.semiMajorAxis = semiMajorAxis;
        this.semiMinorAxis = semiMinorAxis;
    }

    /**
     * This method returns the Angle of orientation from North to the semi major axis
     * of this ellipse.
     *
     * @return Angle of orientation of this ellipse
     */
	@NonNull
    public Angle getOrientation() {
        return orientation;
    }

    /**
     * This method is used to set the Angle of orientation from North to the semi major
     * axis for this ellipse.
     *
     * @param orientation Angle from North to the semi major axis for this ellipse
     * @throws IllegalArgumentException error if orientation is null
     */
    public void setOrientation(Angle orientation) throws IllegalArgumentException {
        if (orientation == null)
            throw new IllegalArgumentException(NULL_ERROR);
        this.orientation = orientation;
    }

    /**
     * This method returns a hash code for this Geodetic2DEllipse object. Ellipses that
     * have equal parameters (or only differ in orientation by 180 degrees) have the
     * same hash code, since they are considered equal objects.
     *
     * @return a hash code value for this object.
     */
    @Override
    public int hashCode() {
        // Normalize orientation in radians to between -PI and 0.0 inclusive
        double normAng = orientation.inRadians;
        if (normAng > 0) normAng = normAng - Math.PI;
        return 31 * center.hashCode() +
                Double.valueOf(semiMajorAxis).hashCode() ^
                Double.valueOf(semiMinorAxis).hashCode() ^
                Double.valueOf(normAng).hashCode();
    }

    /**
     * This method is used to compare this ellipse to another Geodetic2DEllipse object.
     * Ellipses that have equal parameters (or only differ in orientation by 180 degrees)
     * are considered equal objects.
     *
     * @param ellipse Geodetic2DEllipse to compare to this one
     * @return boolean indicating whether this ellipse is equal to the specified ellipse
     */
    public boolean equals(Geodetic2DEllipse ellipse) {
        if (this == ellipse) {
            return true;
        }
        boolean result = false;
        if (ellipse != null && this.getCenter().equals(ellipse.getCenter())) {
            if ((this.semiMajorAxis == ellipse.semiMajorAxis) &&
                    (this.semiMinorAxis == ellipse.semiMinorAxis)) {
                if (this.orientation.equals(ellipse.orientation))
                    result = true;
                else {
                    Angle temp = new Angle(Math.PI).add(this.orientation);
                    if (temp.equals(ellipse.orientation)) result = true;
                }
            }
        }
        return result;
    }

    /**
     * This method is used to compare this ellipse to another Geodetic2DEllipse object.
     * Ellipses that have equal parameters (or only differ in orientation by 180 degrees)
     * are considered equal objects.
     *
     * @param that Geodetic2DEllipse to compare to this one
     * @return boolean indicating whether this ellipse is equal to the specified ellipse
     */
    @Override
    public boolean equals(Object that) {
        return that instanceof Geodetic2DEllipse && equals((Geodetic2DEllipse) that);
    }

    /**
     * The toString method formats the ellipse for printing.
     *
     * @return String representation of center point, axes, and orientation
     */
    @Override
    public String toString() {
        return "(" + center + "," + semiMajorAxis + "m by " +
                semiMinorAxis + "m at " + orientation + ")";
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy