-module(geokit@bbox).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/geokit/bbox.gleam").
-export([compute/1]).
-export_type([b_box_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(
" Bounding box computation for a [`Geometry`](./geometry.html#Geometry).\n"
"\n"
" A bounding box is the smallest axis-aligned rectangle that\n"
" contains every point of the geometry. The box is reported as the\n"
" south-west and north-east corners. Empty inputs produce\n"
" [`EmptyGeometry`](#BBoxError).\n"
"\n"
" The implementation does **not** account for the antimeridian: a\n"
" geometry that straddles 180° longitude is treated as if the\n"
" world were a flat rectangle and produces a box that spans almost\n"
" the entire globe in the longitude axis. Antimeridian-aware\n"
" bounding boxes (the \"narrow\" variant chosen by `Turfjs`,\n"
" `terraformer`, etc.) are an explicit design choice and are out\n"
" of scope for this module.\n"
).
-type b_box_error() :: empty_geometry.
-file("src/geokit/bbox.gleam", 96).
-spec append_list(list(GPE), list(GPE)) -> list(GPE).
append_list(Left, Right) ->
case Left of
[] ->
Right;
[Head | Tail] ->
[Head | append_list(Tail, Right)]
end.
-file("src/geokit/bbox.gleam", 82).
-spec flatten_rings(list(list(geokit@latlng:lat_lng()))) -> list(geokit@latlng:lat_lng()).
flatten_rings(Rings) ->
case Rings of
[] ->
[];
[Head | Tail] ->
append_list(Head, flatten_rings(Tail))
end.
-file("src/geokit/bbox.gleam", 89).
-spec flatten_polygons(list(list(list(geokit@latlng:lat_lng())))) -> list(geokit@latlng:lat_lng()).
flatten_polygons(Polygons) ->
case Polygons of
[] ->
[];
[Head | Tail] ->
append_list(flatten_rings(Head), flatten_polygons(Tail))
end.
-file("src/geokit/bbox.gleam", 73).
-spec all_points(geokit@geometry:geometry()) -> list(geokit@latlng:lat_lng()).
all_points(Geometry) ->
case Geometry of
{point, P} ->
[P];
{line_string, Ps} ->
Ps;
{polygon, Rings} ->
flatten_rings(Rings);
{multi_polygon, Polygons} ->
flatten_polygons(Polygons)
end.
-file("src/geokit/bbox.gleam", 103).
-spec min_float(float(), float()) -> float().
min_float(A, B) ->
gleam@bool:guard(A < B, A, fun() -> B end).
-file("src/geokit/bbox.gleam", 108).
-spec max_float(float(), float()) -> float().
max_float(A, B) ->
gleam@bool:guard(A > B, A, fun() -> B end).
-file("src/geokit/bbox.gleam", 50).
-spec extend_loop(
list(geokit@latlng:lat_lng()),
float(),
float(),
float(),
float()
) -> {float(), float(), float(), float()}.
extend_loop(Points, Min_lat, Max_lat, Min_lng, Max_lng) ->
case Points of
[] ->
{Min_lat, Max_lat, Min_lng, Max_lng};
[Head | Tail] ->
Lat = geokit@latlng:lat(Head),
Lng = geokit@latlng:lng(Head),
extend_loop(
Tail,
min_float(Min_lat, Lat),
max_float(Max_lat, Lat),
min_float(Min_lng, Lng),
max_float(Max_lng, Lng)
)
end.
-file("src/geokit/bbox.gleam", 29).
?DOC(" Compute the bounding box of `geometry` as `#(sw, ne)`.\n").
-spec compute(geokit@geometry:geometry()) -> {ok,
{geokit@latlng:lat_lng(), geokit@latlng:lat_lng()}} |
{error, b_box_error()}.
compute(Geometry) ->
case all_points(Geometry) of
[] ->
{error, empty_geometry};
[Head | Tail] ->
{Min_lat, Max_lat, Min_lng, Max_lng} = extend_loop(
Tail,
geokit@latlng:lat(Head),
geokit@latlng:lat(Head),
geokit@latlng:lng(Head),
geokit@latlng:lng(Head)
),
Sw = geokit@latlng:wrap(Min_lat, Min_lng),
Ne = geokit@latlng:wrap(Max_lat, Max_lng),
{ok, {Sw, Ne}}
end.