/*
 *   DIS/x : An implementation of the IEEE 1278.1 protocol
 *
 *   Copyright (C) 1997, Riley Rainey (rrainey@ix.netcom.com)
 *
 *   This library is free software; you can redistribute it and/or
 *   modify it under the terms of either:
 *
 *   a) the GNU Library General Public License as published by the Free
 *   Software Foundation; either version 2 of the License, or (at your
 *   option) any later version.  A description of the terms and conditions
 *   of the GLPL may be found in the "COPYING.LIB" file.
 *
 *   b) the "Artistic License" which comes with this Kit.  Information
 *   about this license may be found in the "Artistic" file.
 *
 *   This library is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *   Library General Public License or the Artistic License for more details.
 *
 *   You should have received a copy of the GNU Library General Public
 *   License along with this library; if not, write to the Free
 *   Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 *   Information describing how to contact the author can be found in the
 *   README file.
 */

/**
 * Earth Cartesian geocentric coordinates system, calculations and conversions.
 * In the DIS 2.0 coordinate system:
 * 
 * - positive X axis points to 0N, 0E
 * - positive Y axis points to 0N 90E
 * - positive Z axis is North
 *
 * THE WGS84 ELLIPSOID
 * The world is considered a perfect ellipsoid based on the WGS84 standard --
 * no correction is made to take into account height differences between the
 * ellipsoid and the actual shape of the Earth (the "geoid"). The surface of the
 * WGS84 ellipsoid is described by the equation below:
 * 
 *     x^2/a^2 + y^2/a^2 + z^2/b^2 = 1
 * 
 * where "a" the equatorial (or major) semi-axis, and "b" is the polar (or minor)
 * semi-axis of the ellipsoid. This surface is our "sea level" and all the
 * altitudes are referred to this base level. Note that the z axis is the spin
 * axis of our ellipsoidal Earth, and the x and y semi-axis are both equal to "a".
 * The north pole is located at (0,0,b).
 * The south pole is located at (0,0,-b).
 * The equatorial line is the circle x^2 + y^2 = a^2 with z=0.
 * The zero meridian of Greenwich is the semi-ellipse x^2/a^2 + z^2/b^2 = 1
 * with x >= 0, y = 0.
 * 
 * DEFINITION OF THE GEODETIC LONGITUDE
 * Because of this symmetry around the z spin axis, the geodetic longitude can
 * be easily defined and calculated for any given point (x,y,z) as:
 * 
 *     longitude = atan(y/x)
 * 
 * This formula is valid for any point, at any longitude, any latitude and
 * altitude, and not only for the points on the ellipsoid.
 * 
 * DEFINITION OF LOCAL VERTICAL
 * For any point on the surface of the ellipsoid, the line perpendicular to the
 * surface is the local vertical (our "plumb line") and the tangent plane is our
 * local horizon. Note that the local vertical line DOES NOT cross the center of
 * the ellipsoid as it would on a simple sphere.
 * 
 * DEFINITION OF THE GEODETIC ALTITUDE
 * The geodetic altitude of a point (x,y,z) above (or even below) the surface of
 * the ellipsoid is defined as the distance of the point from the surface of the
 * ellipsoid measured over a line perpendicular to the ellipsoid, that is over
 * the local vertical. The altitude is taken positive above the surface, negative
 * below.
 * 
 * DEFINITION OF THE GEODETIC LATITUDE
 * The geodetic latitude of a point is defined as the angle between the local
 * vertical line and the xy plane, taken positive for z>0.
 * 
 * @file
 * @author Riley Rainey
 * @version $Date: 2020/01/08 06:20:05 $
 */

#ifndef EARTH_H
#define EARTH_H

#include "../../V/Vlibmath.h"

#ifdef earth_IMPORT
	#define EXTERN
#else
	#define EXTERN extern
#endif

/** WGS84 equatorial semi-axis "a" (m). */
#define earth_MAJOR	6378137.0

/** WGS84 polar semi-axis "b" (m). */
#define earth_MINOR	6356752.3142

/** Eccentricity = sqrt(1 - (b/a)^2). */
#define earth_ECCENTRICITY	0.081819190928906199466

/** Eccentricity squared. */
#define earth_ECCENTRICITY_SQR	0.006694380004260806515

/**
 * Geodetic coordinates of a point.
 */
typedef struct {
	
	/** Geodetic latitude (RAD, [M_PI/2, -M_PI/2]). */
	double    latitude;
	
	/** Geodetic longitude (RAD, [-M_PI, M_PI]). */
	double    longitude;
	
	/** Geodetic altitude (m). */
	double    z;
	
} earth_LatLonAlt;

typedef enum {
	earth_LLM_DMS,      /* dd-mm-ss.ss[EWNS] */
	earth_LLM_DM,       /* dd mm [EWNS] */
	earth_LLM_D,        /* dd [EWNS] */
	earth_LLM_SIGNED_D
} earth_LatLonDisplayFormat;

/**
 * Normalizes the latitude.
 * @param lat Latitude to normalize (RAD).
 * @return Latitude normalized to the range [-M_PI/2,+M_PI/2]. If not finite,
 * returns the value itself.
 */
EXTERN double earth_normalizeLatitude(double lat);

/**
 * Normalizes the longitude.
 * @param lon Longitude to normalize (RAD).
 * @return Longitude normalized to the range ]-M_PI,+M_PI]. If not finite,
 * returns the value itself.
 */
EXTERN double earth_normalizeLongitude(double lon);

/**
 * Shift location d meters on a given geodetic course (radians). As usual,
 * course 0 degrees is north, course 90 degrees is east.
 * FIXME: the result is undefined at and near the north and south pole, and
 * quite random numbers come out.
 * @param p
 * @param sin_course Sine of the course.
 * @param cos_course Cosine of the course.
 * @param d_meters Step length, constant altitude (m).
 */
EXTERN void earth_updateLatLon(earth_LatLonAlt * p,
	double sin_course, double cos_course, double d_meters);

/**
 * Shift location d_meters meters on a given geodetic course (radians).
 * FIXME: the result is undefined at and near the north and south pole, and
 * quite random numbers come out.
 * @return new outbound heading correct for the new location in delta_course_rad
 */
EXTERN void earth_updateLatLonEx(earth_LatLonAlt * p,
	double cos_course, double sin_course, double d_meters,
	double * delta_course_rad );

/**
 * Format a latitude. If the latitude is not finite or out of the range, then
 * the literal number is returned using the standard "%g" format descriptor.
 * @param s Destination buffer.
 * @param s_capacity Destination buffer capacity.
 * @param la Latitude (RAD).
 * @param mode
 * @return Pointer to the same "s" string.
 */
EXTERN char * earth_latitudeToString(char *s, int s_capacity, double la, earth_LatLonDisplayFormat  mode);

/**
 * Format a longitude. If the latitude is not finite or out of the range, then
 * the literal number is returned using the standard "%g" forma descriptor.
 * @param s Destination buffer.
 * @param s_capacity Destination buffer capacity.
 * @param lo Longitude (RAD).
 * @param mode
 * @return Pointer to the same "s" string.
 */
EXTERN char * earth_longitudeToString(char *s, int s_capacity, double lo, earth_LatLonDisplayFormat  mode);

/**
 * Formats Cartesian coordinates of a point on the Earth, rounded to 1 meter of
 * precision.
 * @param s Destination string.
 * @param s_capacity Capacity of the destination string.
 * @param xyz Cartesian coordinates of the point (m).
 */
EXTERN void earth_XYZToString(char *s, int s_capacity, VPoint *xyz);

/**
 * Formats geographic coordinates.
 * @param s Destination string.
 * @param s_capacity Capacity of the destination string.
 * @param w Geographic coordinates to format.
 * @param mode
 */
EXTERN void earth_LatLonAltToString(char * s, int s_capacity,
	earth_LatLonAlt *w, earth_LatLonDisplayFormat  mode);

/**
 * Convert Cartesian coordinates into WGS84 geodetic lat/lon/z.
 * @param p Cartesian coordinates to convert (m).
 * @param w Resulting world coordinates.
 */
EXTERN void earth_XYZToLatLonAlt(VPoint * p, earth_LatLonAlt * w);

/**
 * Convert WGS84 geodetic lat/lon/z into Cartesian coordinates.
 * @param w World coordinates to convert.
 * @param Resulting Cartesian coordinates (m).
 */
EXTERN void earth_LatLonAltToXYZ(earth_LatLonAlt * w, VPoint * p);

/**
 * Parse a latitude string. Example: "12-34-56.7N" or "12.5824N".
 * @param s Latitude string.
 * @param latitude_rad Resulting parsed latitude (RAD).
 * @return True if parsing succeeded.
 */
EXTERN int earth_parseLatitude(char *s, double *latitude_rad);

/**
 * Parse a longitude string. Example: "123-45-06.7E" or "123.7519E".
 * @param s Longitude string.
 * @param longitude_rad Resulting parsed longitude (RAD).
 * @return True if parsing succeeded.
 */
EXTERN int earth_parseLongitude(char *s, double *longitude_rad);

/**
 * Parse latitude and longitude string.
 * @param s Latitude and longitude separated by at least one white space
 * character; the syntax of each component is the same of a single latitude
 * and longitude.
 * @param w Here returns parsed latitude and longitude; altitude set to zero.
 * @return True if parsing succeeded.
 */
EXTERN char * earth_parseLatLon(char *s, earth_LatLonAlt * w);

/**
 * Generate a transform matrix to get from Cartesian to local NED coordinates.
 */
EXTERN void earth_generateWorldToLocalMatrix(earth_LatLonAlt *w, VMatrix *XYZtoNED);

#undef EXTERN
#endif
