Skip to content

Commit

Permalink
feat: generate actual json in `encode function
Browse files Browse the repository at this point in the history
BREAKING_CHANGE:
`encode_geojson` function was generating `dynamic.Dynamic` which is pretty useless from consumer standpoint. Now it is refactored to generate `Json` type which can be stringified with `json.to_string`.

Note: currently intentionally left encoding arbitrary `Feature` properties unimplemented.

Signed-off-by: Aleksei Gurianov <gurianov@gmail.com>
  • Loading branch information
Guria committed Sep 30, 2024
1 parent 7a022ea commit b5c19b4
Show file tree
Hide file tree
Showing 13 changed files with 219 additions and 215 deletions.
17 changes: 14 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,16 +34,27 @@ pub fn main() {
case result {
Ok(geojson) -> {
// Successfully decoded GeoJSON
let encoded = gleojson.encode_geojson(geojson)
// encoded is now a Dynamic representation of the GeoJSON object
// You can use it for further processing or encoding back to JSON
}
Error(errors) -> {
todo
// Handle decoding errors
// errors contains information about what went wrong during decoding
}
}
// Construct GeoJSON from types
let geojson = gleojson.GeoJSONFeatureCollection(
gleojson.FeatureCollection([
gleojson.Feature(
geometry: option.Some(gleojson.Point([1.0, 2.0])),
properties: option.None,
id: option.Some(gleojson.StringId("feature-id")),
),
]),
)
// Encode to JSON string
gleojson.encode_geojson(geojson) |> json.to_string
}
```

Expand Down
5 changes: 5 additions & 0 deletions birdie_snapshots/feature_encode_decode.accepted
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
version: 1.2.3
title: feature_encode_decode
---
{"id":"feature-id","type":"Feature","geometry":{"type":"Point","coordinates":[1.0,2.0]},"properties":null}
5 changes: 5 additions & 0 deletions birdie_snapshots/featurecollection_encode_decode.accepted
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
version: 1.2.3
title: featurecollection_encode_decode
---
{"type":"FeatureCollection","features":[{"id":"feature-id","type":"Feature","geometry":{"type":"Point","coordinates":[1.0,2.0]},"properties":null}]}
5 changes: 5 additions & 0 deletions birdie_snapshots/geometrycollection_encode_decode.accepted
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
version: 1.2.3
title: geometrycollection_encode_decode
---
{"type":"GeometryCollection","geometries":[{"type":"Point","coordinates":[1.0,2.0]},{"type":"LineString","coordinates":[[3.0,4.0],[5.0,6.0]]}]}
5 changes: 5 additions & 0 deletions birdie_snapshots/linestring_encode_decode.accepted
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
version: 1.2.3
title: linestring_encode_decode
---
{"type":"LineString","coordinates":[[1.0,2.0],[3.0,4.0]]}
5 changes: 5 additions & 0 deletions birdie_snapshots/multipoint_encode_decode.accepted
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
version: 1.2.3
title: multipoint_encode_decode
---
{"type":"MultiPoint","coordinates":[[1.0,2.0],[3.0,4.0]]}
5 changes: 5 additions & 0 deletions birdie_snapshots/multipolygon_encode_decode.accepted
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
version: 1.2.3
title: multipolygon_encode_decode
---
{"type":"MultiPolygon","coordinates":[[[[1.0,2.0],[3.0,4.0],[5.0,6.0],[1.0,2.0]]],[[[7.0,8.0],[9.0,10.0],[11.0,12.0],[7.0,8.0]]]]}
5 changes: 5 additions & 0 deletions birdie_snapshots/point_encode_decode.accepted
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
version: 1.2.3
title: point_encode_decode
---
{"type":"Point","coordinates":[1.0,2.0]}
5 changes: 5 additions & 0 deletions birdie_snapshots/polygon_encode_decode.accepted
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
version: 1.2.3
title: polygon_encode_decode
---
{"type":"Polygon","coordinates":[[[1.0,2.0],[3.0,4.0],[5.0,6.0],[1.0,2.0]]]}
3 changes: 2 additions & 1 deletion gleam.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ links = [{ title = "RFC7946", href = "https://datatracker.ietf.org/doc/html/rfc7

[dependencies]
gleam_stdlib = ">= 0.34.0 and < 2.0.0"
gleam_json = ">= 2.0.0 and < 3.0.0"

[dev-dependencies]
gleeunit = ">= 1.0.0 and < 2.0.0"
gleam_json = ">= 2.0.0 and < 3.0.0"
birdie = ">= 1.2.3 and < 2.0.0"
14 changes: 14 additions & 0 deletions manifest.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,26 @@
# You typically do not need to edit this file

packages = [
{ name = "argv", version = "1.0.2", build_tools = ["gleam"], requirements = [], otp_app = "argv", source = "hex", outer_checksum = "BA1FF0929525DEBA1CE67256E5ADF77A7CDDFE729E3E3F57A5BDCAA031DED09D" },
{ name = "birdie", version = "1.2.3", build_tools = ["gleam"], requirements = ["argv", "edit_distance", "filepath", "glance", "gleam_community_ansi", "gleam_erlang", "gleam_stdlib", "justin", "rank", "simplifile", "trie_again"], otp_app = "birdie", source = "hex", outer_checksum = "AE1207210E9CC8F4170BCE3FB3C23932F314C352C3FD1BCEA44CF4BF8CF60F93" },
{ name = "edit_distance", version = "2.0.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "edit_distance", source = "hex", outer_checksum = "A1E485C69A70210223E46E63985FA1008B8B2DDA9848B7897469171B29020C05" },
{ name = "filepath", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "filepath", source = "hex", outer_checksum = "EFB6FF65C98B2A16378ABC3EE2B14124168C0CE5201553DE652E2644DCFDB594" },
{ name = "glance", version = "0.11.0", build_tools = ["gleam"], requirements = ["gleam_stdlib", "glexer"], otp_app = "glance", source = "hex", outer_checksum = "8F3314D27773B7C3B9FB58D8C02C634290422CE531988C0394FA0DF8676B964D" },
{ name = "gleam_community_ansi", version = "1.4.1", build_tools = ["gleam"], requirements = ["gleam_community_colour", "gleam_stdlib"], otp_app = "gleam_community_ansi", source = "hex", outer_checksum = "4CD513FC62523053E62ED7BAC2F36136EC17D6A8942728250A9A00A15E340E4B" },
{ name = "gleam_community_colour", version = "1.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_community_colour", source = "hex", outer_checksum = "A49A5E3AE8B637A5ACBA80ECB9B1AFE89FD3D5351FF6410A42B84F666D40D7D5" },
{ name = "gleam_erlang", version = "0.27.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_erlang", source = "hex", outer_checksum = "DE468F676D71B313C6C8C5334425CFCF827837333F8AB47B64D8A6D7AA40185D" },
{ name = "gleam_json", version = "2.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_json", source = "hex", outer_checksum = "CB10B0E7BF44282FB25162F1A24C1A025F6B93E777CCF238C4017E4EEF2CDE97" },
{ name = "gleam_stdlib", version = "0.40.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "86606B75A600BBD05E539EB59FABC6E307EEEA7B1E5865AFB6D980A93BCB2181" },
{ name = "gleeunit", version = "1.2.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "F7A7228925D3EE7D0813C922E062BFD6D7E9310F0BEE585D3A42F3307E3CFD13" },
{ name = "glexer", version = "1.0.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "glexer", source = "hex", outer_checksum = "BD477AD657C2B637FEF75F2405FAEFFA533F277A74EF1A5E17B55B1178C228FB" },
{ name = "justin", version = "1.0.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "justin", source = "hex", outer_checksum = "7FA0C6DB78640C6DC5FBFD59BF3456009F3F8B485BF6825E97E1EB44E9A1E2CD" },
{ name = "rank", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "rank", source = "hex", outer_checksum = "5660E361F0E49CBB714CC57CC4C89C63415D8986F05B2DA0C719D5642FAD91C9" },
{ name = "simplifile", version = "2.2.0", build_tools = ["gleam"], requirements = ["filepath", "gleam_stdlib"], otp_app = "simplifile", source = "hex", outer_checksum = "0DFABEF7DC7A9E2FF4BB27B108034E60C81BEBFCB7AB816B9E7E18ED4503ACD8" },
{ name = "trie_again", version = "1.1.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "trie_again", source = "hex", outer_checksum = "5B19176F52B1BD98831B57FDC97BD1F88C8A403D6D8C63471407E78598E27184" },
]

[requirements]
birdie = { version = ">= 1.2.3 and < 2.0.0" }
gleam_json = { version = ">= 2.0.0 and < 3.0.0" }
gleam_stdlib = { version = ">= 0.34.0 and < 2.0.0" }
gleeunit = { version = ">= 1.0.0 and < 2.0.0" }
126 changes: 74 additions & 52 deletions src/gleojson.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

import gleam/dict
import gleam/dynamic
import gleam/list
import gleam/json
import gleam/option
import gleam/result

Expand Down Expand Up @@ -64,90 +64,112 @@ pub type GeoJSON {

// Encoding Functions

/// Encodes a geometry into a dynamic value.
fn encode_geometry(geometry: Geometry) -> dynamic.Dynamic {
/// Encodes a geometry into a JSON object.
fn encode_geometry(geometry: Geometry) -> json.Json {
case geometry {
Point(coordinates) -> {
dict.from_list([
#("type", dynamic.from("Point")),
#("coordinates", dynamic.from(coordinates)),
json.object([
#("type", json.string("Point")),
#("coordinates", json.array(coordinates, of: json.float)),
])
}
MultiPoint(multipoint) -> {
dict.from_list([
#("type", dynamic.from("MultiPoint")),
#("coordinates", dynamic.from(multipoint)),
json.object([
#("type", json.string("MultiPoint")),
#(
"coordinates",
json.array(multipoint, of: json.array(_, of: json.float)),
),
])
}
LineString(linestring) -> {
dict.from_list([
#("type", dynamic.from("LineString")),
#("coordinates", dynamic.from(linestring)),
json.object([
#("type", json.string("LineString")),
#(
"coordinates",
json.array(linestring, of: json.array(_, of: json.float)),
),
])
}
MultiLineString(multilinestring) -> {
dict.from_list([
#("type", dynamic.from("MultiLineString")),
#("coordinates", dynamic.from(multilinestring)),
json.object([
#("type", json.string("MultiLineString")),
#(
"coordinates",
json.array(multilinestring, of: json.array(_, of: json.array(
_,
of: json.float,
))),
),
])
}
Polygon(polygon) -> {
dict.from_list([
#("type", dynamic.from("Polygon")),
#("coordinates", dynamic.from(polygon)),
json.object([
#("type", json.string("Polygon")),
#(
"coordinates",
json.array(polygon, of: json.array(_, of: json.array(
_,
of: json.float,
))),
),
])
}
MultiPolygon(multipolygon) -> {
dict.from_list([
#("type", dynamic.from("MultiPolygon")),
#("coordinates", dynamic.from(multipolygon)),
json.object([
#("type", json.string("MultiPolygon")),
#(
"coordinates",
json.array(
multipolygon,
of: json.array(_, of: json.array(_, of: json.array(
_,
of: json.float,
))),
),
),
])
}
GeometryCollection(collection) -> {
let geometries_dyn_list = list.map(collection, encode_geometry)
dict.from_list([
#("type", dynamic.from("GeometryCollection")),
#("geometries", dynamic.from(geometries_dyn_list)),
json.object([
#("type", json.string("GeometryCollection")),
#("geometries", json.array(collection, of: encode_geometry)),
])
}
}
|> dynamic.from
}

/// Encodes a feature into a dynamic value.
fn encode_feature(feature: Feature) -> dynamic.Dynamic {
let Feature(geometry_opt, properties_opt, id_opt) = feature
let geometry_dyn = case geometry_opt {
/// Encodes a feature into a JSON object.
fn encode_feature(feature: Feature) -> json.Json {
let Feature(geometry_opt, _properties_opt, id_opt) = feature
let geometry_json = case geometry_opt {
option.Some(geometry) -> encode_geometry(geometry)
option.None -> dynamic.from(Nil)
}
let properties_dyn = case properties_opt {
option.Some(props) -> dynamic.from(props)
option.None -> dynamic.from(Nil)
option.None -> json.null()
}
let base_obj =
dict.from_list([
#("type", dynamic.from("Feature")),
#("geometry", geometry_dyn),
#("properties", properties_dyn),
])
// let properties_json = case properties_opt {
// option.Some(props) -> json.object(props)
// option.None -> json.object([])
// }
let base_obj = [
#("type", json.string("Feature")),
#("geometry", geometry_json),
#("properties", json.null()),
]
case id_opt {
option.Some(StringId(id)) -> dict.insert(base_obj, "id", dynamic.from(id))
option.Some(NumberId(id)) -> dict.insert(base_obj, "id", dynamic.from(id))
option.Some(StringId(id)) -> [#("id", json.string(id)), ..base_obj]
option.Some(NumberId(id)) -> [#("id", json.float(id)), ..base_obj]
option.None -> base_obj
}
|> dynamic.from
|> json.object
}

/// Encodes a feature collection into a dynamic value.
fn encode_featurecollection(collection: FeatureCollection) -> dynamic.Dynamic {
/// Encodes a feature collection into a JSON object.
fn encode_featurecollection(collection: FeatureCollection) -> json.Json {
let FeatureCollection(features) = collection
let features_dyn_list = list.map(features, encode_feature)
dict.from_list([
#("type", dynamic.from("FeatureCollection")),
#("features", dynamic.from(features_dyn_list)),
json.object([
#("type", json.string("FeatureCollection")),
#("features", json.array(features, of: encode_feature)),
])
|> dynamic.from
}

/// Encodes a GeoJSON object into a dynamic value.
Expand All @@ -159,7 +181,7 @@ fn encode_featurecollection(collection: FeatureCollection) -> dynamic.Dynamic {
/// let encoded = encode_geojson(point)
/// // encoded will be a dynamic representation of the GeoJSON object
/// ```
pub fn encode_geojson(geojson: GeoJSON) -> dynamic.Dynamic {
pub fn encode_geojson(geojson: GeoJSON) -> json.Json {
case geojson {
GeoJSONGeometry(geometry) -> encode_geometry(geometry)
GeoJSONFeature(feature) -> encode_feature(feature)
Expand Down
Loading

0 comments on commit b5c19b4

Please sign in to comment.