-module(geokit@geojson).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/geokit/geojson.gleam").
-export([encode_geometry/1, encode_feature/2, encode_feature_collection/2, decode_geometry/1, decode_feature/2, decode_feature_collection/2]).
-export_type([geo_json_error/0, feature_id/0, feature/1]).
-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(
" GeoJSON (RFC 7946) encode / decode.\n"
"\n"
" Maps geokit's [`Geometry`](../geometry.html) ADT to and from the\n"
" JSON shapes defined in RFC 7946:\n"
"\n"
" - `Point` / `LineString` / `Polygon` / `MultiPolygon` round-trip\n"
" through this module.\n"
" - `MultiPoint`, `MultiLineString`, and `GeometryCollection` are\n"
" valid GeoJSON types but are not currently representable in\n"
" `Geometry`; decoding returns [`UnsupportedType`](#GeoJsonError).\n"
"\n"
" Coordinate order in GeoJSON is `[longitude, latitude]`, opposite\n"
" of the `lat: ..., lng: ...` constructors elsewhere in geokit.\n"
" The encoder and decoder handle the swap so callers never see the\n"
" reversed order. Altitude (a third coordinate) is accepted on\n"
" decode but discarded.\n"
"\n"
" Properties on [`Feature`](#Feature) and `FeatureCollection` are\n"
" user-typed: pass a `Json` builder when encoding and a\n"
" `decode.Decoder` when decoding. Use `gleam/dynamic/decode.dynamic`\n"
" and `gleam/json.null()` if you don't care about properties.\n"
).
-type geo_json_error() :: {invalid_json, binary()} |
{invalid_structure, binary()} |
{unknown_type, binary()} |
{unsupported_type, binary()} |
{invalid_position, list(float())} |
{invalid_lat_lng, geokit@latlng:lat_lng_error()}.
-type feature_id() :: {string_id, binary()} | {int_id, integer()}.
-type feature(GZF) :: {feature,
geokit@geometry:geometry(),
GZF,
gleam@option:option(feature_id())}.
-file("src/geokit/geojson.gleam", 167).
-spec position_to_json(geokit@latlng:lat_lng()) -> gleam@json:json().
position_to_json(P) ->
gleam@json:preprocessed_array(
[gleam@json:float(geokit@latlng:lng(P)),
gleam@json:float(geokit@latlng:lat(P))]
).
-file("src/geokit/geojson.gleam", 159).
-spec rings_to_json(list(list(geokit@latlng:lat_lng()))) -> gleam@json:json().
rings_to_json(Rings) ->
gleam@json:preprocessed_array(
gleam@list:map(
Rings,
fun(Ring) ->
gleam@json:preprocessed_array(
gleam@list:map(Ring, fun position_to_json/1)
)
end
)
).
-file("src/geokit/geojson.gleam", 175).
-spec object_with_coords(binary(), gleam@json:json()) -> gleam@json:json().
object_with_coords(Type_name, Coordinates) ->
gleam@json:object(
[{<<"type"/utf8>>, gleam@json:string(Type_name)},
{<<"coordinates"/utf8>>, Coordinates}]
).
-file("src/geokit/geojson.gleam", 141).
-spec geometry_to_json(geokit@geometry:geometry()) -> gleam@json:json().
geometry_to_json(G) ->
case G of
{point, P} ->
object_with_coords(<<"Point"/utf8>>, position_to_json(P));
{line_string, Points} ->
object_with_coords(
<<"LineString"/utf8>>,
gleam@json:preprocessed_array(
gleam@list:map(Points, fun position_to_json/1)
)
);
{polygon, Rings} ->
object_with_coords(<<"Polygon"/utf8>>, rings_to_json(Rings));
{multi_polygon, Polygons} ->
object_with_coords(
<<"MultiPolygon"/utf8>>,
gleam@json:preprocessed_array(
gleam@list:map(Polygons, fun rings_to_json/1)
)
)
end.
-file("src/geokit/geojson.gleam", 85).
?DOC(
" Encode a `Geometry` as a GeoJSON string. The result is a compact\n"
" JSON document with no whitespace.\n"
"\n"
" ```gleam\n"
" import geokit/geojson\n"
" import geokit/geometry\n"
" import geokit/latlng\n"
"\n"
" let assert Ok(p) = latlng.new(lat: 35.0, lng: 139.0)\n"
" geojson.encode_geometry(geometry: geometry.Point(p))\n"
" // == \"{\\\"type\\\":\\\"Point\\\",\\\"coordinates\\\":[139.0,35.0]}\"\n"
" ```\n"
).
-spec encode_geometry(geokit@geometry:geometry()) -> binary().
encode_geometry(Geometry) ->
_pipe = Geometry,
_pipe@1 = geometry_to_json(_pipe),
gleam@json:to_string(_pipe@1).
-file("src/geokit/geojson.gleam", 197).
-spec id_to_json(feature_id()) -> gleam@json:json().
id_to_json(Id) ->
case Id of
{string_id, Value} ->
gleam@json:string(Value);
{int_id, Value@1} ->
gleam@json:int(Value@1)
end.
-file("src/geokit/geojson.gleam", 184).
-spec feature_to_json(feature(HAA), fun((HAA) -> gleam@json:json())) -> gleam@json:json().
feature_to_json(Feature, To_json) ->
Base = [{<<"type"/utf8>>, gleam@json:string(<<"Feature"/utf8>>)},
{<<"geometry"/utf8>>, geometry_to_json(erlang:element(2, Feature))},
{<<"properties"/utf8>>, To_json(erlang:element(3, Feature))}],
Entries = case erlang:element(4, Feature) of
none ->
Base;
{some, Id} ->
lists:append(Base, [{<<"id"/utf8>>, id_to_json(Id)}])
end,
gleam@json:object(Entries).
-file("src/geokit/geojson.gleam", 91).
?DOC(
" Encode a `Feature` as a GeoJSON string. Pass a function that turns\n"
" your properties type into a `Json` value.\n"
).
-spec encode_feature(feature(GZG), fun((GZG) -> gleam@json:json())) -> binary().
encode_feature(Feature, To_json) ->
_pipe = Feature,
_pipe@1 = feature_to_json(_pipe, To_json),
gleam@json:to_string(_pipe@1).
-file("src/geokit/geojson.gleam", 99).
?DOC(" Encode a list of features as a GeoJSON `FeatureCollection`.\n").
-spec encode_feature_collection(
list(feature(GZI)),
fun((GZI) -> gleam@json:json())
) -> binary().
encode_feature_collection(Features, To_json) ->
Entries = [{<<"type"/utf8>>,
gleam@json:string(<<"FeatureCollection"/utf8>>)},
{<<"features"/utf8>>,
gleam@json:preprocessed_array(
gleam@list:map(
Features,
fun(F) -> feature_to_json(F, To_json) end
)
)}],
_pipe = gleam@json:object(Entries),
gleam@json:to_string(_pipe).
-file("src/geokit/geojson.gleam", 217).
-spec json_error_to_geojson(gleam@json:decode_error()) -> geo_json_error().
json_error_to_geojson(Err) ->
case Err of
unexpected_end_of_input ->
{invalid_json, <<"unexpected end of input"/utf8>>};
{unexpected_byte, Byte} ->
{invalid_json, <<"unexpected byte: "/utf8, Byte/binary>>};
{unexpected_sequence, Seq} ->
{invalid_json, <<"unexpected sequence: "/utf8, Seq/binary>>};
{unable_to_decode, _} ->
{invalid_structure,
<<"JSON shape did not match expected GeoJSON"/utf8>>}
end.
-file("src/geokit/geojson.gleam", 206).
-spec parse_with(
binary(),
gleam@dynamic@decode:decoder({ok, HAC} | {error, geo_json_error()})
) -> {ok, HAC} | {error, geo_json_error()}.
parse_with(Input, Decoder) ->
case gleam@json:parse(Input, Decoder) of
{error, Err} ->
{error, json_error_to_geojson(Err)};
{ok, {ok, Value}} ->
{ok, Value};
{ok, {error, Geojson_err}} ->
{error, Geojson_err}
end.
-file("src/geokit/geojson.gleam", 301).
-spec wrap_position(float(), float()) -> {ok, geokit@latlng:lat_lng()} |
{error, geo_json_error()}.
wrap_position(Lng, Lat) ->
case geokit@latlng:new(Lat, Lng) of
{ok, P} ->
{ok, P};
{error, E} ->
{error, {invalid_lat_lng, E}}
end.
-file("src/geokit/geojson.gleam", 293).
-spec parse_position(list(float())) -> {ok, geokit@latlng:lat_lng()} |
{error, geo_json_error()}.
parse_position(Coords) ->
case Coords of
[Lng, Lat] ->
wrap_position(Lng, Lat);
[Lng@1, Lat@1, _] ->
wrap_position(Lng@1, Lat@1);
_ ->
{error, {invalid_position, Coords}}
end.
-file("src/geokit/geojson.gleam", 261).
-spec raw_point_to_geometry(list(float())) -> {ok, geokit@geometry:geometry()} |
{error, geo_json_error()}.
raw_point_to_geometry(Coords) ->
gleam@result:map(parse_position(Coords), fun(Point) -> {point, Point} end).
-file("src/geokit/geojson.gleam", 266).
-spec raw_line_string_to_geometry(list(list(float()))) -> {ok,
geokit@geometry:geometry()} |
{error, geo_json_error()}.
raw_line_string_to_geometry(Coords) ->
gleam@result:map(
gleam@list:try_map(Coords, fun parse_position/1),
fun(Points) -> {line_string, Points} end
).
-file("src/geokit/geojson.gleam", 287).
-spec parse_rings(list(list(list(float())))) -> {ok,
list(list(geokit@latlng:lat_lng()))} |
{error, geo_json_error()}.
parse_rings(Raw) ->
gleam@list:try_map(
Raw,
fun(Ring) -> gleam@list:try_map(Ring, fun parse_position/1) end
).
-file("src/geokit/geojson.gleam", 273).
-spec raw_polygon_to_geometry(list(list(list(float())))) -> {ok,
geokit@geometry:geometry()} |
{error, geo_json_error()}.
raw_polygon_to_geometry(Coords) ->
gleam@result:map(parse_rings(Coords), fun(Rings) -> {polygon, Rings} end).
-file("src/geokit/geojson.gleam", 280).
-spec raw_multi_polygon_to_geometry(list(list(list(list(float()))))) -> {ok,
geokit@geometry:geometry()} |
{error, geo_json_error()}.
raw_multi_polygon_to_geometry(Coords) ->
gleam@result:map(
gleam@list:try_map(Coords, fun parse_rings/1),
fun(Polygons) -> {multi_polygon, Polygons} end
).
-file("src/geokit/geojson.gleam", 227).
-spec geometry_decoder() -> gleam@dynamic@decode:decoder({ok,
geokit@geometry:geometry()} |
{error, geo_json_error()}).
geometry_decoder() ->
gleam@dynamic@decode:field(
<<"type"/utf8>>,
{decoder, fun gleam@dynamic@decode:decode_string/1},
fun(Type_) -> case Type_ of
<<"Point"/utf8>> ->
gleam@dynamic@decode:field(
<<"coordinates"/utf8>>,
gleam@dynamic@decode:list(
{decoder, fun gleam@dynamic@decode:decode_float/1}
),
fun(Coords) ->
gleam@dynamic@decode:success(
raw_point_to_geometry(Coords)
)
end
);
<<"LineString"/utf8>> ->
gleam@dynamic@decode:field(
<<"coordinates"/utf8>>,
gleam@dynamic@decode:list(
gleam@dynamic@decode:list(
{decoder,
fun gleam@dynamic@decode:decode_float/1}
)
),
fun(Coords@1) ->
gleam@dynamic@decode:success(
raw_line_string_to_geometry(Coords@1)
)
end
);
<<"Polygon"/utf8>> ->
gleam@dynamic@decode:field(
<<"coordinates"/utf8>>,
gleam@dynamic@decode:list(
gleam@dynamic@decode:list(
gleam@dynamic@decode:list(
{decoder,
fun gleam@dynamic@decode:decode_float/1}
)
)
),
fun(Coords@2) ->
gleam@dynamic@decode:success(
raw_polygon_to_geometry(Coords@2)
)
end
);
<<"MultiPolygon"/utf8>> ->
gleam@dynamic@decode:field(
<<"coordinates"/utf8>>,
gleam@dynamic@decode:list(
gleam@dynamic@decode:list(
gleam@dynamic@decode:list(
gleam@dynamic@decode:list(
{decoder,
fun gleam@dynamic@decode:decode_float/1}
)
)
)
),
fun(Coords@3) ->
gleam@dynamic@decode:success(
raw_multi_polygon_to_geometry(Coords@3)
)
end
);
<<"MultiPoint"/utf8>> ->
gleam@dynamic@decode:success(
{error, {unsupported_type, Type_}}
);
<<"MultiLineString"/utf8>> ->
gleam@dynamic@decode:success(
{error, {unsupported_type, Type_}}
);
<<"GeometryCollection"/utf8>> ->
gleam@dynamic@decode:success(
{error, {unsupported_type, Type_}}
);
Other ->
gleam@dynamic@decode:success({error, {unknown_type, Other}})
end end
).
-file("src/geokit/geojson.gleam", 118).
?DOC(" Decode a GeoJSON geometry string back to a `Geometry`.\n").
-spec decode_geometry(binary()) -> {ok, geokit@geometry:geometry()} |
{error, geo_json_error()}.
decode_geometry(Input) ->
parse_with(Input, geometry_decoder()).
-file("src/geokit/geojson.gleam", 332).
-spec id_decoder() -> gleam@dynamic@decode:decoder(feature_id()).
id_decoder() ->
gleam@dynamic@decode:one_of(
gleam@dynamic@decode:map(
{decoder, fun gleam@dynamic@decode:decode_string/1},
fun(Field@0) -> {string_id, Field@0} end
),
[gleam@dynamic@decode:map(
{decoder, fun gleam@dynamic@decode:decode_int/1},
fun(Field@0) -> {int_id, Field@0} end
)]
).
-file("src/geokit/geojson.gleam", 310).
-spec feature_decoder(gleam@dynamic@decode:decoder(HBP)) -> gleam@dynamic@decode:decoder({ok,
feature(HBP)} |
{error, geo_json_error()}).
feature_decoder(Properties_decoder) ->
gleam@dynamic@decode:field(
<<"type"/utf8>>,
{decoder, fun gleam@dynamic@decode:decode_string/1},
fun(Type_) -> case Type_ of
<<"Feature"/utf8>> ->
gleam@dynamic@decode:field(
<<"geometry"/utf8>>,
geometry_decoder(),
fun(Raw_geometry) ->
gleam@dynamic@decode:field(
<<"properties"/utf8>>,
Properties_decoder,
fun(Props) ->
gleam@dynamic@decode:optional_field(
<<"id"/utf8>>,
none,
gleam@dynamic@decode:optional(
id_decoder()
),
fun(Id) ->
gleam@dynamic@decode:success(
gleam@result:map(
Raw_geometry,
fun(G) ->
{feature, G, Props, Id}
end
)
)
end
)
end
)
end
);
Other ->
gleam@dynamic@decode:then(
gleam@dynamic@decode:success(nil),
fun(_) ->
gleam@dynamic@decode:success(
{error, {unknown_type, Other}}
)
end
)
end end
).
-file("src/geokit/geojson.gleam", 124).
?DOC(
" Decode a GeoJSON Feature. Pass a decoder for your properties type.\n"
" To accept any shape, pass `decode.dynamic`.\n"
).
-spec decode_feature(binary(), gleam@dynamic@decode:decoder(GZN)) -> {ok,
feature(GZN)} |
{error, geo_json_error()}.
decode_feature(Input, Properties) ->
parse_with(Input, feature_decoder(Properties)).
-file("src/geokit/geojson.gleam", 338).
-spec feature_collection_decoder(gleam@dynamic@decode:decoder(HBW)) -> gleam@dynamic@decode:decoder({ok,
list(feature(HBW))} |
{error, geo_json_error()}).
feature_collection_decoder(Properties_decoder) ->
gleam@dynamic@decode:field(
<<"type"/utf8>>,
{decoder, fun gleam@dynamic@decode:decode_string/1},
fun(Type_) -> case Type_ of
<<"FeatureCollection"/utf8>> ->
gleam@dynamic@decode:field(
<<"features"/utf8>>,
gleam@dynamic@decode:list(
feature_decoder(Properties_decoder)
),
fun(Features) ->
gleam@dynamic@decode:success(
gleam@result:all(Features)
)
end
);
Other ->
gleam@dynamic@decode:then(
gleam@dynamic@decode:success(nil),
fun(_) ->
gleam@dynamic@decode:success(
{error, {unknown_type, Other}}
)
end
)
end end
).
-file("src/geokit/geojson.gleam", 132).
?DOC(" Decode a GeoJSON FeatureCollection into a list of features.\n").
-spec decode_feature_collection(binary(), gleam@dynamic@decode:decoder(GZS)) -> {ok,
list(feature(GZS))} |
{error, geo_json_error()}.
decode_feature_collection(Input, Properties) ->
parse_with(Input, feature_collection_decoder(Properties)).