-module(geokit@latlng).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/geokit/latlng.gleam").
-export([new/2, wrap/2, lat/1, lng/1, equal/2]).
-export_type([lat_lng/0, lat_lng_error/0]).
-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(
" Opaque `LatLng` (geographic coordinate) shared by every module\n"
" in `geokit`.\n"
"\n"
" Latitudes are in degrees, in the range [-90, 90], with positive\n"
" values north of the equator. Longitudes are in degrees, in the\n"
" range [-180, 180], with positive values east of the prime\n"
" meridian. Out-of-range inputs to [`new`](#new) return a typed\n"
" error; use [`wrap`](#wrap) when your source data may be\n"
" denormalised (for example, sensor output that crosses the\n"
" antimeridian).\n"
).
-opaque lat_lng() :: {lat_lng, float(), float()}.
-type lat_lng_error() :: {lat_out_of_range, float()} |
{lng_out_of_range, float()}.
-file("src/geokit/latlng.gleam", 39).
?DOC(
" Build a `LatLng` from latitude and longitude in degrees.\n"
"\n"
" ```gleam\n"
" import geokit/latlng\n"
"\n"
" let assert Ok(tokyo) = latlng.new(lat: 35.6812, lng: 139.7671)\n"
" ```\n"
).
-spec new(float(), float()) -> {ok, lat_lng()} | {error, lat_lng_error()}.
new(Lat, Lng) ->
gleam@bool:guard(
(Lat < -90.0) orelse (Lat > 90.0),
{error, {lat_out_of_range, Lat}},
fun() ->
gleam@bool:guard(
(Lng < -180.0) orelse (Lng > 180.0),
{error, {lng_out_of_range, Lng}},
fun() -> {ok, {lat_lng, Lat, Lng}} end
)
end
).
-file("src/geokit/latlng.gleam", 68).
-spec wrap_longitude(float()) -> float().
wrap_longitude(Lng) ->
gleam@bool:guard(
(Lng >= -180.0) andalso (Lng =< 180.0),
Lng,
fun() ->
Shifted = Lng + 180.0,
Wrapped = Shifted - (360.0 * math:floor(Shifted / 360.0)),
Result = Wrapped - 180.0,
gleam@bool:guard(
Result > 180.0,
180.0,
fun() ->
gleam@bool:guard(
Result < -180.0,
-180.0,
fun() -> Result end
)
end
)
end
).
-file("src/geokit/latlng.gleam", 62).
?DOC(
" Build a `LatLng`, normalising longitude into `[-180, 180]` and\n"
" clamping latitude into `[-90, 90]`. Use this when the source data\n"
" may be denormalised (sensor output crossing the antimeridian,\n"
" great-circle calculations producing 181°, ...).\n"
"\n"
" ```gleam\n"
" import geokit/latlng\n"
"\n"
" let p = latlng.wrap(lat: 91.0, lng: 181.0)\n"
" // p has lat = 90.0, lng = -179.0\n"
" ```\n"
).
-spec wrap(float(), float()) -> lat_lng().
wrap(Lat, Lng) ->
Lat_clamped = gleam@float:clamp(Lat, -90.0, 90.0),
Lng_wrapped = wrap_longitude(Lng),
{lat_lng, Lat_clamped, Lng_wrapped}.
-file("src/geokit/latlng.gleam", 86).
?DOC(" Latitude in degrees.\n").
-spec lat(lat_lng()) -> float().
lat(P) ->
erlang:element(2, P).
-file("src/geokit/latlng.gleam", 91).
?DOC(" Longitude in degrees.\n").
-spec lng(lat_lng()) -> float().
lng(P) ->
erlang:element(3, P).
-file("src/geokit/latlng.gleam", 99).
?DOC(
" Value equality. Two `LatLng` values are equal iff their `lat` and\n"
" `lng` components compare equal.\n"
).
-spec equal(lat_lng(), lat_lng()) -> boolean().
equal(A, B) ->
(erlang:element(2, A) =:= erlang:element(2, B)) andalso (erlang:element(
3,
A
)
=:= erlang:element(3, B)).