src/geokit@bearing.erl

-module(geokit@bearing).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/geokit/bearing.gleam").
-export([initial/2, final/2]).

-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.

?MODULEDOC(
    " Initial and final compass bearing between two `LatLng` points.\n"
    "\n"
    " Bearings are reported in degrees clockwise from true north, in\n"
    " `[0, 360)`. The initial bearing is the direction of travel when\n"
    " departing `a` along the great-circle route to `b`; the final\n"
    " bearing is the direction of arrival at `b`. The two differ along\n"
    " any route except along a meridian or the equator (where they are\n"
    " equal).\n"
    "\n"
    " Formula: <https://www.movable-type.co.uk/scripts/latlong.html>.\n"
).

-file("src/geokit/bearing.gleam", 55).
-spec normalise_degrees(float()) -> float().
normalise_degrees(Value) ->
    Scaled = Value / 360.0,
    Result = Value - (360.0 * math:floor(Scaled)),
    gleam@bool:guard(
        Result < +0.0,
        +0.0,
        fun() ->
            gleam@bool:guard(Result >= 360.0, +0.0, fun() -> Result end)
        end
    ).

-file("src/geokit/bearing.gleam", 30).
?DOC(
    " Initial bearing (forward azimuth) from `a` to `b`, in degrees in\n"
    " `[0, 360)`.\n"
    "\n"
    " ```gleam\n"
    " import geokit/bearing\n"
    " import geokit/latlng\n"
    "\n"
    " let assert Ok(tokyo) = latlng.new(lat: 35.6812, lng: 139.7671)\n"
    " let assert Ok(osaka) = latlng.new(lat: 34.6937, lng: 135.5023)\n"
    " bearing.initial(from: tokyo, to: osaka)\n"
    " // ~= 254.0\n"
    " ```\n"
).
-spec initial(geokit@latlng:lat_lng(), geokit@latlng:lat_lng()) -> float().
initial(From, To) ->
    Phi_1 = gleam_community@maths:degrees_to_radians(geokit@latlng:lat(From)),
    Phi_2 = gleam_community@maths:degrees_to_radians(geokit@latlng:lat(To)),
    Lambda_1 = gleam_community@maths:degrees_to_radians(geokit@latlng:lng(From)),
    Lambda_2 = gleam_community@maths:degrees_to_radians(geokit@latlng:lng(To)),
    Delta_lambda = Lambda_2 - Lambda_1,
    Y = gleam_community@maths:sin(Delta_lambda) * gleam_community@maths:cos(
        Phi_2
    ),
    X = (gleam_community@maths:cos(Phi_1) * gleam_community@maths:sin(Phi_2)) - ((gleam_community@maths:sin(
        Phi_1
    )
    * gleam_community@maths:cos(Phi_2))
    * gleam_community@maths:cos(Delta_lambda)),
    Theta = gleam_community@maths:atan2(Y, X),
    normalise_degrees(gleam_community@maths:radians_to_degrees(Theta)).

-file("src/geokit/bearing.gleam", 50).
?DOC(
    " Final bearing when arriving at `to` along the great-circle route\n"
    " from `from`, in degrees in `[0, 360)`. Computed as the reverse of\n"
    " `initial(from: to, to: from)` plus 180°.\n"
).
-spec final(geokit@latlng:lat_lng(), geokit@latlng:lat_lng()) -> float().
final(From, To) ->
    Reverse = initial(To, From),
    normalise_degrees(Reverse + 180.0).