-module(geokit@distance).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/geokit/distance.gleam").
-export([haversine/2, haversine_km/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(
" Great-circle distance between two `LatLng` points.\n"
"\n"
" The haversine formula is used; see\n"
" <https://en.wikipedia.org/wiki/Haversine_formula>. Distances are\n"
" reported in metres using the WGS84 mean Earth radius\n"
" (6_371_008.8 m). The error against a Vincenty ellipsoidal\n"
" distance is bounded by 0.5 % for any two points on Earth, which\n"
" matches every consumer-grade mapping API.\n"
).
-file("src/geokit/distance.gleam", 67).
-spec newton_sqrt(float(), float(), integer()) -> float().
newton_sqrt(Value, Guess, Iterations) ->
gleam@bool:guard(
(Iterations =< 0) orelse (Guess =:= +0.0),
Guess,
fun() -> newton_sqrt(Value, (Guess + (case Guess of
+0.0 -> +0.0;
-0.0 -> -0.0;
Gleam@denominator -> Value / Gleam@denominator
end)) / 2.0, Iterations - 1) end
).
-file("src/geokit/distance.gleam", 62).
?DOC(
" Square root of a non-negative input via Newton iteration. Caller\n"
" must ensure the argument is `>= 0`; on a negative input the\n"
" function returns `0.0`.\n"
).
-spec sqrt_nonneg(float()) -> float().
sqrt_nonneg(X) ->
gleam@bool:guard(X =< +0.0, +0.0, fun() -> newton_sqrt(X, X, 32) end).
-file("src/geokit/distance.gleam", 30).
?DOC(
" Great-circle distance between `a` and `b`, in metres.\n"
"\n"
" ```gleam\n"
" import geokit/distance\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"
" distance.haversine(a: tokyo, b: osaka)\n"
" // ~= 396_900.0 (≈ 397 km)\n"
" ```\n"
).
-spec haversine(geokit@latlng:lat_lng(), geokit@latlng:lat_lng()) -> float().
haversine(A, B) ->
Lat_a = gleam_community@maths:degrees_to_radians(geokit@latlng:lat(A)),
Lat_b = gleam_community@maths:degrees_to_radians(geokit@latlng:lat(B)),
Delta_lat = gleam_community@maths:degrees_to_radians(
geokit@latlng:lat(B) - geokit@latlng:lat(A)
),
Delta_lng = gleam_community@maths:degrees_to_radians(
geokit@latlng:lng(B) - geokit@latlng:lng(A)
),
Half_lat = Delta_lat / 2.0,
Half_lng = Delta_lng / 2.0,
Sin_half_lat = gleam_community@maths:sin(Half_lat),
Sin_half_lng = gleam_community@maths:sin(Half_lng),
H = (Sin_half_lat * Sin_half_lat) + (((gleam_community@maths:cos(Lat_a) * gleam_community@maths:cos(
Lat_b
))
* Sin_half_lng)
* Sin_half_lng),
H_clamped = gleam@float:clamp(H, +0.0, 1.0),
Root_h = sqrt_nonneg(H_clamped),
Root_one_minus_h = sqrt_nonneg(1.0 - H_clamped),
C = 2.0 * gleam_community@maths:atan2(Root_h, Root_one_minus_h),
6371008.8 * C.
-file("src/geokit/distance.gleam", 55).
?DOC(
" Great-circle distance between `a` and `b`, in kilometres.\n"
" Convenience wrapper around [`haversine`](#haversine).\n"
).
-spec haversine_km(geokit@latlng:lat_lng(), geokit@latlng:lat_lng()) -> float().
haversine_km(A, B) ->
haversine(A, B) / 1000.0.