geodesic_module Module

Implementation of geodesic routines in modern Fortran

See also

  • This module contains modernized versions of the Fortran code from geographiclib-fortran. The geographiclib subroutines are documented at: sourceforge These are Fortran implementation of the geodesic algorithms described in: C. F. F. Karney, Algorithms for geodesics, J. Geodesy 87, 43--55 (2013). [Apr 23, 2022 version]
  • Some of the code was also split off from the Fortran Astrodynamics Toolkit (see geodesy_module.f90). [Nov 6, 2022 version]

geographiclib Notes

The principal advantages of these algorithms over previous ones (e.g., Vincenty, 1975) are:

  • accurate to round off for |f| < 1/50
  • the solution of the inverse problem is always found
  • differential and integral properties of geodesics are computed

The shortest path between two points on the ellipsoid at (lat1, lon1) and (lat2, lon2) is called the geodesic. Its length is s12 and the geodesic from point 1 to point 2 has forward azimuths azi1 and azi2 at the two end points.

Traditionally two geodesic problems are considered:

  • the direct problem -- given lat1, lon1, s12, and azi1, determine lat2, lon2, and azi2. This is solved by the subroutine direct.
  • the inverse problem -- given lat1, lon1, lat2, lon2, determine s12, azi1, and azi2. This is solved by the subroutine inverse.

The ellipsoid is specified by its equatorial radius a (typically in meters) and flattening f. The routines are accurate to round off with real(wp) arithmetic provided that |f| < 1/50 for the WGS84 ellipsoid, the errors are less than 15 nanometers. (Reasonably accurate results are obtained for |f| < 1/5.) For a prolate ellipsoid, specify f < 0.

The routines also calculate several other quantities of interest:

  • SS12 is the area between the geodesic from point 1 to point 2 and the equator; i.e., it is the area, measured counter-clockwise, of the geodesic quadrilateral with corners (lat1,lon1), (0,lon1), (0,lon2), and (lat2,lon2).
  • m12, the reduced length of the geodesic is defined such that if the initial azimuth is perturbed by dazi1 (radians) then the second point is displaced by m12 dazi1 in the direction perpendicular to the geodesic. On a curved surface the reduced length obeys a symmetry relation, m12 + m21 = 0. On a flat surface, we have m12 = s12.
  • MM12 and MM21 are geodesic scales. If two geodesics are parallel at point 1 and separated by a small distance dt, then they are separated by a distance MM12 dt at point 2. MM21 is defined similarly (with the geodesics being parallel to one another at point 2). On a flat surface, we have MM12 = MM21 = 1.
  • a12 is the arc length on the auxiliary sphere. This is a construct for converting the problem to one in spherical trigonometry. a12 is measured in degrees. The spherical arc length from one equator crossing to the next is always 180 deg.

If points 1, 2, and 3 lie on a single geodesic, then the following addition rules hold:

  • s13 = s12 + s23
  • a13 = a12 + a23
  • SS13 = SS12 + SS23
  • m13 = m12 MM23 + m23 MM21
  • MM13 = MM12 MM23 - (1 - MM12 MM21) m23 / m12
  • MM31 = MM32 MM21 - (1 - MM23 MM32) m12 / m23

The shortest distance returned by the solution of the inverse problem is (obviously) uniquely defined. However, in a few special cases there are multiple azimuths which yield the same shortest distance. Here is a catalog of those cases:

  • lat1 = -lat2 (with neither point at a pole). If azi1 = azi2, the geodesic is unique. Otherwise there are two geodesics and the second one is obtained by setting [azi1, azi2] --> [azi2, azi1], [MM12, MM21] --> [MM21, MM12], SS12 --> -SS12. (This occurs when the longitude difference is near +/- 180 deg for oblate ellipsoids.)
  • lon2 = lon1 +/- 180 deg (with neither point at a pole). If azi1 = 0 deg or +/- 180 deg, the geodesic is unique. Otherwise there are two geodesics and the second one is obtained by setting [azi1, azi2] --> [-azi1, -azi2], SS12 --> -SS12. (This occurs when lat2 is near -lat1 for prolate ellipsoids.)
  • Points 1 and 2 at opposite poles. There are infinitely many geodesics which can be generated by setting [azi1, azi2] --> [azi1, azi2] + [d, -d], for arbitrary d. (For spheres, this prescription applies when points 1 and 2 are antipodal.)
  • s12 = 0 (coincident points). There are infinitely many geodesics which can be generated by setting [azi1, azi2] --> [azi1, azi2] + [d, d], for arbitrary d.

These routines are a simple transcription of the corresponding C++ classes in geographiclib. Because of the limitations of Fortran 77, the classes have been replaced by simple subroutines with no attempt to save "state" across subroutine calls. Most of the internal comments have been retained. However, in the process of transcription some documentation has been lost and the documentation for the C++ classes, geodesic_module::Geodesic, geodesic_module::GeodesicLine, and geodesic_module::PolygonAreaT, should be consulted. The C++ code remains the "reference implementation". Think twice about restructuring the internals of the Fortran code since this may make porting fixes from the C++ code more difficult.

License

geographiclib-fortran Copyright (c) Charles Karney (2012-2022) charles@karney.com and licensed under the MIT/X11 License. For more information, see https://geographiclib.sourceforge.io/



Variables

Type Visibility Attributes Name Initial
integer, public, parameter :: geodesic_wp = wp

Real working precision


Functions

public function sumx(u, v, t)

Arguments

Type IntentOptional Attributes Name
real(kind=wp), intent(in) :: u
real(kind=wp), intent(in) :: v
real(kind=wp), intent(out) :: t

Return Value real(kind=wp)

public function AngNormalize(x)

Arguments

Type IntentOptional Attributes Name
real(kind=wp), intent(in) :: x

Return Value real(kind=wp)

public function LatFix(x)

Arguments

Type IntentOptional Attributes Name
real(kind=wp), intent(in) :: x

Return Value real(kind=wp)

public function AngDiff(x, y, e)

Compute y - x. x and y must both lie in [-180, 180]. The result is equivalent to computing the difference exactly, reducing it to (-180, 180] and rounding the result. Note that this prescription allows -180 to be returned (e.g., if x is tiny and negative and y = 180). The error in the difference is returned in e

Arguments

Type IntentOptional Attributes Name
real(kind=wp), intent(in) :: x
real(kind=wp), intent(in) :: y
real(kind=wp), intent(out) :: e

Return Value real(kind=wp)

public function AngRound(x)

The makes the smallest gap in x = 1/16 - nextafter(1/16, 0) = 1/2^57 for reals = 0.7 pm on the earth if x is an angle in degrees. (This is about 1000 times more resolution than we get with angles around 90 degrees.) We use this to avoid having to deal with near singular cases when x is non-zero but tiny (e.g., 1.0e-200).

Arguments

Type IntentOptional Attributes Name
real(kind=wp), intent(in) :: x

Return Value real(kind=wp)

public pure function great_circle_distance(r, long1, lat1, long2, lat2) result(d)

Author
Jacob Williams
Date
7/13/2014

Great circle distance on a spherical body, using the Vincenty algorithm.

Read more…

Arguments

Type IntentOptional Attributes Name
real(kind=wp), intent(in) :: r

radius of the body [km]

real(kind=wp), intent(in) :: long1

longitude of first site [rad]

real(kind=wp), intent(in) :: lat1

latitude of the first site [rad]

real(kind=wp), intent(in) :: long2

longitude of the second site [rad]

real(kind=wp), intent(in) :: lat2

latitude of the second site [rad]

Return Value real(kind=wp)

great circle distance from 1 to 2 [km]

public pure function geocentric_radius(a, b, lat) result(r)

The distance from the center of a celestial body (e.g., the Earth) to a point on the spheroid surface at a specified geodetic latitude.

Read more…

Arguments

Type IntentOptional Attributes Name
real(kind=wp), intent(in) :: a

equatorial radius (km)

real(kind=wp), intent(in) :: b

polar radius of point (km)

real(kind=wp), intent(in) :: lat

geodetic latitude of point (rad)

Return Value real(kind=wp)

distance from center of body to point (km)


Subroutines

public subroutine direct(a, f, lat1, lon1, azi1, s12a12, flags, lat2, lon2, azi2, outmask, a12s12, m12, MM12, MM21, SS12)

Solve the direct geodesic problem.

Read more…

Arguments

Type IntentOptional Attributes Name
real(kind=wp), intent(in) :: a

the equatorial radius (meters).

real(kind=wp), intent(in) :: f

the flattening of the ellipsoid. Setting f = 0 gives a sphere. Negative f gives a prolate ellipsoid.

real(kind=wp), intent(in) :: lat1

lat1 latitude of point 1 (degrees). lat1 should be in the range [-90 deg, 90 deg].

real(kind=wp), intent(in) :: lon1

lon1 longitude of point 1 (degrees).

real(kind=wp), intent(in) :: azi1

azi1 azimuth at point 1 (degrees).

real(kind=wp), intent(in) :: s12a12

if arcmode is not set, this is the distance from point 1 to point 2 (meters); otherwise it is the arc length from point 1 to point 2 (degrees); it can be negative.

integer, intent(in) :: flags

a bitor'ed combination of the arcmode and unroll flags.

Read more…
real(kind=wp), intent(out) :: lat2

latitude of point 2 (degrees).

real(kind=wp), intent(out) :: lon2

longitude of point 2 (degrees).

real(kind=wp), intent(out) :: azi2

(forward) azimuth at point 2 (degrees). The value azi2 returned is in the range [-180 deg, 180 deg].

integer, intent(in) :: outmask

a bitor'ed combination of mask values specifying which of the following parameters should be set.

Read more…
real(kind=wp), intent(out) :: a12s12

if arcmode is not set, this is the arc length from point 1 to point 2 (degrees); otherwise it is the distance from point 1 to point 2 (meters).

real(kind=wp), intent(out) :: m12

reduced length of geodesic (meters).

real(kind=wp), intent(out) :: MM12

geodesic scale of point 2 relative to point 1 (dimensionless).

real(kind=wp), intent(out) :: MM21

geodesic scale of point 1 relative to point 2 (dimensionless).

real(kind=wp), intent(out) :: SS12

area under the geodesic ().

public subroutine inverse(a, f, lat1, lon1, lat2, lon2, s12, azi1, azi2, outmask, a12, m12, MM12, MM21, SS12)

Solve the inverse geodesic problem.

Read more…

Arguments

Type IntentOptional Attributes Name
real(kind=wp), intent(in) :: a

the equatorial radius (meters).

real(kind=wp), intent(in) :: f

the flattening of the ellipsoid. Setting f = 0 gives a sphere. Negative f gives a prolate ellipsoid.

real(kind=wp), intent(in) :: lat1

latitude of point 1 (degrees).

real(kind=wp), intent(in) :: lon1

longitude of point 1 (degrees).

real(kind=wp), intent(in) :: lat2

latitude of point 2 (degrees).

real(kind=wp), intent(in) :: lon2

longitude of point 2 (degrees).

real(kind=wp), intent(out) :: s12

distance from point 1 to point 2 (meters).

real(kind=wp), intent(out) :: azi1

azimuth at point 1 (degrees).

real(kind=wp), intent(out) :: azi2

(forward) azimuth at point 2 (degrees).

integer, intent(in) :: outmask

a bitor'ed combination of mask values specifying which of the following parameters should be set. outmask is an integer in [0, 16) whose binary bits are interpreted as follows:

Read more…
real(kind=wp), intent(out) :: a12

arc length from point 1 to point 2 (degrees).

real(kind=wp), intent(out) :: m12

reduced length of geodesic (meters).

real(kind=wp), intent(out) :: MM12

MM12 geodesic scale of point 2 relative to point 1 (dimensionless).

real(kind=wp), intent(out) :: MM21

MM21 geodesic scale of point 1 relative to point 2 (dimensionless).

real(kind=wp), intent(out) :: SS12

SS12 area under the geodesic ().

public subroutine area(a, f, lats, lons, n, AA, PP)

Determine the area of a geodesic polygon

Read more…

Arguments

Type IntentOptional Attributes Name
real(kind=wp), intent(in) :: a

the equatorial radius (meters).

real(kind=wp), intent(in) :: f

the flattening of the ellipsoid. Setting f = 0 gives a sphere. Negative f gives a prolate ellipsoid.

real(kind=wp), intent(in) :: lats(n)

an array of the latitudes of the vertices (degrees). lats should be in the range [-90 deg, 90 deg].

real(kind=wp), intent(in) :: lons(n)

an array of the longitudes of the vertices (degrees).

integer, intent(in) :: n

number of points

real(kind=wp), intent(out) :: AA

the (signed) area of the polygon ().

real(kind=wp), intent(out) :: PP

the perimeter of the polygon.

public pure subroutine sincosd(x, sinx, cosx)

Compute sin(x) and cos(x) with x in degrees

Arguments

Type IntentOptional Attributes Name
real(kind=wp), intent(in) :: x
real(kind=wp), intent(out) :: sinx
real(kind=wp), intent(out) :: cosx

public pure subroutine heikkinen(rvec, a, b, h, lon, lat)

Author
Jacob Williams

Heikkinen routine for cartesian to geodetic transformation

Read more…

Arguments

Type IntentOptional Attributes Name
real(kind=wp), intent(in), dimension(3) :: rvec

position vector [km]

real(kind=wp), intent(in) :: a

geoid semimajor axis [km]

real(kind=wp), intent(in) :: b

geoid semiminor axis [km]

real(kind=wp), intent(out) :: h

geodetic altitude [km]

real(kind=wp), intent(out) :: lon

longitude [rad]

real(kind=wp), intent(out) :: lat

geodetic latitude [rad]

public pure subroutine olson(rvec, a, b, h, long, lat)

Author
Jacob Williams

Olson routine for cartesian to geodetic transformation.

Read more…

Arguments

Type IntentOptional Attributes Name
real(kind=wp), intent(in), dimension(3) :: rvec

position vector [km]

real(kind=wp), intent(in) :: a

geoid semimajor axis [km]

real(kind=wp), intent(in) :: b

geoid semiminor axis [km]

real(kind=wp), intent(out) :: h

geodetic altitude [km]

real(kind=wp), intent(out) :: long

longitude [rad]

real(kind=wp), intent(out) :: lat

geodetic latitude [rad]

public subroutine direct_vincenty(a, f, glat1, glon1, faz, s, glat2, glon2, baz)

Author
Jacob Williams

Solve the "direct" geodetic problem: given the latitude and longitude of one point and the azimuth and distance to a second point, determine the latitude and longitude of that second point. The solution is obtained using the algorithm by Vincenty.

Read more…

Arguments

Type IntentOptional Attributes Name
real(kind=wp), intent(in) :: a

semimajor axis of ellipsoid [m]

real(kind=wp), intent(in) :: f

flattening of ellipsoid [-]

real(kind=wp), intent(in) :: glat1

latitude of 1 [rad]

real(kind=wp), intent(in) :: glon1

longitude of 1 [rad]

real(kind=wp), intent(in) :: faz

forward azimuth 1->2 [rad]

real(kind=wp), intent(in) :: s

distance from 1->2 [m]

real(kind=wp), intent(out) :: glat2

latitude of 2 [rad]

real(kind=wp), intent(out) :: glon2

longitude of 2 [rad]

real(kind=wp), intent(out) :: baz

back azimuth 2->1 [rad]

public subroutine geodetic_to_cartesian(a, b, glat, lon, h, r)

Author
Jacob Williams

Geodetic latitude, longitude, and height to Cartesian position vector.

Read more…

Arguments

Type IntentOptional Attributes Name
real(kind=wp), intent(in) :: a

geoid semimajor axis [km]

real(kind=wp), intent(in) :: b

geoid semiminor axis [km]

real(kind=wp), intent(in) :: glat

geodetic latitude [rad]

real(kind=wp), intent(in) :: lon

longitude [rad]

real(kind=wp), intent(in) :: h

geodetic altitude [km]

real(kind=wp), intent(out), dimension(3) :: r

Cartesian position vector [x,y,z]

public subroutine inverse_vincenty(a, rf, b1, l1, b2, l2, faz, baz, s, it, sig, lam, kind)

INVERSE computes the geodetic azimuth and distance between two points, given their geographic positions.

Read more…

Arguments

Type IntentOptional Attributes Name
real(kind=wp), intent(in) :: a

Equatorial semimajor axis

real(kind=wp), intent(in) :: rf

reciprocal flattening (1/f)

real(kind=wp), intent(in) :: b1

latitude of point 1 (rad, positive north)

real(kind=wp), intent(in) :: l1

longitude of point 1 (rad, positive east)

real(kind=wp), intent(in) :: b2

latitude of point 2 (rad, positive north)

real(kind=wp), intent(in) :: l2

longitude of point 2 (rad, positive east)

real(kind=wp), intent(out) :: faz

Forward azimuth (rad, clockwise from north)

real(kind=wp), intent(out) :: baz

Back azimuth (rad, clockwise from north)

real(kind=wp), intent(out) :: s

Ellipsoidal distance

integer, intent(out) :: it

iteration count

real(kind=wp), intent(out) :: sig

spherical distance on auxiliary sphere

real(kind=wp), intent(out) :: lam

longitude difference on auxiliary sphere

integer, intent(out) :: kind

solution flag: kind=1, long-line; kind=2, antipodal

public subroutine geodetic_to_cartesian_triaxial(ax, ay, b, phi, lambda, h, r)

Function computes the Cartesian coordinates given the geodetic latitude (phi), longitude (lambda) and height (h) of a point related to an ellipsoid defined by its three semiaxes ax, ay and b

Read more…

Arguments

Type IntentOptional Attributes Name
real(kind=wp), intent(in) :: ax

semiaxes (0 < b <= ay <= ax)

real(kind=wp), intent(in) :: ay

semiaxes (0 < b <= ay <= ax)

real(kind=wp), intent(in) :: b

semiaxes (0 < b <= ay <= ax)

real(kind=wp), intent(in) :: phi

geodetic latitude (radians)

real(kind=wp), intent(in) :: lambda

geodetic longitude (radians)

real(kind=wp), intent(in) :: h

geodetic height

real(kind=wp), intent(out), dimension(3) :: r

Cartesian position vector [x,y,z]

public pure subroutine geodetic_to_cartesian_triaxial_2(a, b, c, lat, long, h, r)

Geodetic to Cartesian for Triaxial Ellipsoid.

Read more…

Arguments

Type IntentOptional Attributes Name
real(kind=wp), intent(in) :: a

ellipsoid radii a >= b >= c

real(kind=wp), intent(in) :: b

ellipsoid radii a >= b >= c

real(kind=wp), intent(in) :: c

ellipsoid radii a >= b >= c

real(kind=wp), intent(in) :: lat

latitude (rad)

real(kind=wp), intent(in) :: long

longitude (rad)

real(kind=wp), intent(in) :: h

altitude

real(kind=wp), intent(out), dimension(3) :: r

Cartesian coordinates (x,y,z)

public subroutine cartesian_to_geodetic_triaxial(ax, ay, b, r, tol, phi, lambda, h)

Function computes the geodetic latitude (phi), longitude (lambda) and height (h) of a point related to an ellipsoid defined by its three semiaxes ax, ay and b (0 < b <= ay <= ax) given Cartesian coordinates Xi, Yi, Zi and tolerance (tol). Latitude and longitude are returned in radians.

Read more…

Arguments

Type IntentOptional Attributes Name
real(kind=wp), intent(in) :: ax

semiaxes (0 < b <= ay <= ax)

real(kind=wp), intent(in) :: ay

semiaxes (0 < b <= ay <= ax)

real(kind=wp), intent(in) :: b

semiaxes (0 < b <= ay <= ax)

real(kind=wp), intent(in), dimension(3) :: r

Cartesian coordinates (x,y,z)

real(kind=wp), intent(in) :: tol

tolerance (may be set to zero)

real(kind=wp), intent(out) :: phi

geodetic latitude (radians)

real(kind=wp), intent(out) :: lambda

geodetic longitude (radians)

real(kind=wp), intent(out) :: h

geodetic height

public subroutine CartesianIntoGeodeticI(ax, ay, az, r, latitude, longitude, altitude, error)

Cartesian to Geodetic I

Read more…

Arguments

Type IntentOptional Attributes Name
real(kind=wp), intent(in) :: ax

semiaxes of the celestial body: ax>ay>az

real(kind=wp), intent(in) :: ay

semiaxes of the celestial body: ax>ay>az

real(kind=wp), intent(in) :: az

semiaxes of the celestial body: ax>ay>az

real(kind=wp), intent(in), dimension(3) :: r

cartesian coordinates of the considered point in the first octant: xG, yG, zG with (xG,yG,zG)<>(0,0,0)

real(kind=wp), intent(out) :: latitude

geodetic coordinates of the considered point

real(kind=wp), intent(out) :: longitude

geodetic coordinates of the considered point

real(kind=wp), intent(out) :: altitude

geodetic coordinates of the considered point

real(kind=wp), intent(in) :: error

Values smaller than error treated as 0.0

public subroutine CartesianIntoGeodeticII(ax, ay, az, r, latitude, longitude, altitude, error)

Cartesian into Geodetic II

Read more…

Arguments

Type IntentOptional Attributes Name
real(kind=wp), intent(in) :: ax

semiaxes of the celestial body: ax>ay>az

real(kind=wp), intent(in) :: ay

semiaxes of the celestial body: ax>ay>az

real(kind=wp), intent(in) :: az

semiaxes of the celestial body: ax>ay>az

real(kind=wp), intent(in), dimension(3) :: r

cartesian coordinates of the considered point in the first octant: xG, yG, zG with (xG,yG,zG)<>(0,0,0)

real(kind=wp), intent(out) :: latitude

geodetic coordinates of the considered point

real(kind=wp), intent(out) :: longitude

geodetic coordinates of the considered point

real(kind=wp), intent(out) :: altitude

geodetic coordinates of the considered point

real(kind=wp), intent(in) :: error

Values smaller than error treated as 0.0

public subroutine cartesian_to_geodetic_triaxial_2(a, b, c, r, eps, phi, lambda, h)

Cartesian to geodetic for Triaxial Ellipsoid.

Read more…

Arguments

Type IntentOptional Attributes Name
real(kind=wp), intent(in) :: a

ellipsoid radii a >= b >= c

real(kind=wp), intent(in) :: b

ellipsoid radii a >= b >= c

real(kind=wp), intent(in) :: c

ellipsoid radii a >= b >= c

real(kind=wp), intent(in), dimension(3) :: r

Cartesian coordinates (x,y,z)

real(kind=wp), intent(in) :: eps

convergence tolerance

real(kind=wp), intent(out) :: phi

latitude (rad)

real(kind=wp), intent(out) :: lambda

longitude (rad)

real(kind=wp), intent(out) :: h

altitude