14
14
////
15
15
//// Then you can use the provided functions to encode and decode GeoJSON data.
16
16
17
- import gleam/dict
18
17
import gleam/dynamic
19
18
import gleam/json
19
+
20
+ // import gleam/list
20
21
import gleam/option
21
22
import gleam/result
22
23
@@ -42,56 +43,53 @@ pub type FeatureId {
42
43
}
43
44
44
45
/// A feature in a GeoJSON object, consisting of a geometry, properties, and an optional id.
45
- pub type Feature {
46
+ pub type Feature ( properties ) {
46
47
Feature (
47
48
geometry : option . Option ( Geometry ) ,
48
- properties : option . Option ( dict . Dict ( String , dynamic . Dynamic ) ) ,
49
+ properties : option . Option ( properties ) ,
49
50
id : option . Option ( FeatureId ) ,
50
51
)
51
52
}
52
53
53
54
/// A collection of features in a GeoJSON object.
54
- pub type FeatureCollection {
55
- FeatureCollection ( features : List ( Feature ) )
55
+ pub type FeatureCollection ( properties ) {
56
+ FeatureCollection ( features : List ( Feature ( properties ) ) )
56
57
}
57
58
58
59
/// A GeoJSON object.
59
- pub type GeoJSON {
60
+ pub type GeoJSON ( properties ) {
60
61
GeoJSONGeometry ( Geometry )
61
- GeoJSONFeature ( Feature )
62
- GeoJSONFeatureCollection ( FeatureCollection )
62
+ GeoJSONFeature ( Feature ( properties ) )
63
+ GeoJSONFeatureCollection ( FeatureCollection ( properties ) )
63
64
}
64
65
65
66
// Encoding Functions
66
67
67
68
/// Encodes a geometry into a JSON object.
68
69
fn encode_geometry ( geometry : Geometry ) -> json . Json {
69
70
case geometry {
70
- Point ( coordinates ) -> {
71
+ Point ( coordinates ) ->
71
72
json . object ( [
72
73
# ( "type" , json . string ( "Point" ) ) ,
73
74
# ( "coordinates" , json . array ( coordinates , of : json . float ) ) ,
74
75
] )
75
- }
76
- MultiPoint ( multipoint ) -> {
76
+ MultiPoint ( multipoint ) ->
77
77
json . object ( [
78
78
# ( "type" , json . string ( "MultiPoint" ) ) ,
79
79
# (
80
80
"coordinates" ,
81
81
json . array ( multipoint , of : json . array ( _, of : json . float ) ) ,
82
82
) ,
83
83
] )
84
- }
85
- LineString ( linestring ) -> {
84
+ LineString ( linestring ) ->
86
85
json . object ( [
87
86
# ( "type" , json . string ( "LineString" ) ) ,
88
87
# (
89
88
"coordinates" ,
90
89
json . array ( linestring , of : json . array ( _, of : json . float ) ) ,
91
90
) ,
92
91
] )
93
- }
94
- MultiLineString ( multilinestring ) -> {
92
+ MultiLineString ( multilinestring ) ->
95
93
json . object ( [
96
94
# ( "type" , json . string ( "MultiLineString" ) ) ,
97
95
# (
@@ -102,8 +100,7 @@ fn encode_geometry(geometry: Geometry) -> json.Json {
102
100
) ) ) ,
103
101
) ,
104
102
] )
105
- }
106
- Polygon ( polygon ) -> {
103
+ Polygon ( polygon ) ->
107
104
json . object ( [
108
105
# ( "type" , json . string ( "Polygon" ) ) ,
109
106
# (
@@ -114,8 +111,7 @@ fn encode_geometry(geometry: Geometry) -> json.Json {
114
111
) ) ) ,
115
112
) ,
116
113
] )
117
- }
118
- MultiPolygon ( multipolygon ) -> {
114
+ MultiPolygon ( multipolygon ) ->
119
115
json . object ( [
120
116
# ( "type" , json . string ( "MultiPolygon" ) ) ,
121
117
# (
@@ -129,63 +125,77 @@ fn encode_geometry(geometry: Geometry) -> json.Json {
129
125
) ,
130
126
) ,
131
127
] )
132
- }
133
- GeometryCollection ( collection ) -> {
128
+ GeometryCollection ( collection ) ->
134
129
json . object ( [
135
130
# ( "type" , json . string ( "GeometryCollection" ) ) ,
136
131
# ( "geometries" , json . array ( collection , of : encode_geometry ) ) ,
137
132
] )
138
- }
139
133
}
140
134
}
141
135
142
136
/// Encodes a feature into a JSON object.
143
- fn encode_feature ( feature : Feature ) -> json . Json {
144
- let Feature ( geometry_opt , _properties_opt , id_opt ) = feature
137
+ fn encode_feature (
138
+ properties_encoder : fn ( properties) -> json . Json ,
139
+ feature : Feature ( properties) ,
140
+ ) -> json . Json {
141
+ let Feature ( geometry_opt , properties_opt , id_opt ) = feature
145
142
let geometry_json = case geometry_opt {
146
143
option . Some ( geometry ) -> encode_geometry ( geometry )
147
144
option . None -> json . null ( )
148
145
}
149
- // let properties_json = case properties_opt {
150
- // option.Some(props) -> json.object(props)
151
- // option.None -> json.object([])
152
- // }
146
+ let properties_json = case properties_opt {
147
+ option . Some ( props ) -> properties_encoder ( props )
148
+ option . None -> json . null ( )
149
+ }
150
+
153
151
let base_obj = [
154
152
# ( "type" , json . string ( "Feature" ) ) ,
155
153
# ( "geometry" , geometry_json ) ,
156
- # ( "properties" , json . null ( ) ) ,
154
+ # ( "properties" , properties_json ) ,
157
155
]
158
- case id_opt {
156
+ let full_obj = case id_opt {
159
157
option . Some ( StringId ( id ) ) -> [ # ( "id" , json . string ( id ) ) , .. base_obj ]
160
158
option . Some ( NumberId ( id ) ) -> [ # ( "id" , json . float ( id ) ) , .. base_obj ]
161
159
option . None -> base_obj
162
160
}
163
- |> json . object
161
+ json . object ( full_obj )
164
162
}
165
163
166
164
/// Encodes a feature collection into a JSON object.
167
- fn encode_featurecollection ( collection : FeatureCollection ) -> json . Json {
165
+ fn encode_featurecollection (
166
+ properties_encoder : fn ( properties) -> json . Json ,
167
+ collection : FeatureCollection ( properties) ,
168
+ ) -> json . Json {
168
169
let FeatureCollection ( features ) = collection
169
170
json . object ( [
170
171
# ( "type" , json . string ( "FeatureCollection" ) ) ,
171
- # ( "features" , json . array ( features , of : encode_feature ) ) ,
172
+ # (
173
+ "features" ,
174
+ json . array ( features , of : fn ( feature ) {
175
+ encode_feature ( properties_encoder , feature )
176
+ } ) ,
177
+ ) ,
172
178
] )
173
179
}
174
180
175
- /// Encodes a GeoJSON object into a dynamic value.
181
+ /// Encodes a GeoJSON object into a JSON value.
176
182
///
177
183
/// ## Example
178
184
///
179
185
/// ```gleam
180
186
/// let point = GeoJSONGeometry(Point([0.0, 0.0]))
181
- /// let encoded = encode_geojson(point)
182
- /// // encoded will be a dynamic representation of the GeoJSON object
187
+ /// let encoded = encode_geojson(point, properties_encoder )
188
+ /// // encoded will be a JSON representation of the GeoJSON object
183
189
/// ```
184
- pub fn encode_geojson ( geojson : GeoJSON ) -> json . Json {
190
+ pub fn encode_geojson (
191
+ geojson : GeoJSON ( properties) ,
192
+ properties_encoder : fn ( properties) -> json . Json ,
193
+ ) -> json . Json {
185
194
case geojson {
186
195
GeoJSONGeometry ( geometry ) -> encode_geometry ( geometry )
187
- GeoJSONFeature ( feature ) -> encode_feature ( feature )
188
- GeoJSONFeatureCollection ( collection ) -> encode_featurecollection ( collection )
196
+ GeoJSONFeature ( feature ) -> encode_feature ( properties_encoder , feature )
197
+ GeoJSONFeatureCollection ( collection ) ->
198
+ encode_featurecollection ( properties_encoder , collection )
189
199
}
190
200
}
191
201
@@ -219,6 +229,12 @@ fn positions_list_list_decoder(
219
229
dynamic . list ( of : positions_list_decoder ) ( dyn_value )
220
230
}
221
231
232
+ fn decode_type_field (
233
+ dyn_value : dynamic . Dynamic ,
234
+ ) -> Result ( String , List ( dynamic . DecodeError ) ) {
235
+ dynamic . field ( named : "type" , of : dynamic . string ) ( dyn_value )
236
+ }
237
+
222
238
/// Decodes a geometry from a dynamic value.
223
239
fn geometry_decoder (
224
240
dyn_value : dynamic . Dynamic ,
@@ -272,8 +288,9 @@ fn feature_id_decoder(
272
288
273
289
/// Decodes a feature from a dynamic value.
274
290
fn feature_decoder (
291
+ properties_decoder : dynamic . Decoder ( properties) ,
275
292
dyn_value : dynamic . Dynamic ,
276
- ) -> Result ( Feature , List ( dynamic . DecodeError ) ) {
293
+ ) -> Result ( Feature ( properties ) , List ( dynamic . DecodeError ) ) {
277
294
use type_str <- result . try ( decode_type_field ( dyn_value ) )
278
295
case type_str {
279
296
"Feature" -> {
@@ -285,7 +302,7 @@ fn feature_decoder(
285
302
let properties_result =
286
303
dynamic . field (
287
304
named : "properties" ,
288
- of : dynamic . optional ( dynamic . dict ( dynamic . string , dynamic . dynamic ) ) ,
305
+ of : dynamic . optional ( properties_decoder ) ,
289
306
) ( dyn_value )
290
307
|> result . map_error ( fn ( _errs ) {
291
308
[
@@ -326,14 +343,18 @@ fn feature_decoder(
326
343
327
344
/// Decodes a feature collection from a dynamic value.
328
345
fn featurecollection_decoder (
329
- dyn_value ,
330
- ) -> Result ( FeatureCollection , List ( dynamic . DecodeError ) ) {
346
+ properties_decoder : dynamic . Decoder ( properties) ,
347
+ dyn_value : dynamic . Dynamic ,
348
+ ) -> Result ( FeatureCollection ( properties) , List ( dynamic . DecodeError ) ) {
331
349
use type_str <- result . try ( decode_type_field ( dyn_value ) )
332
350
case type_str {
333
351
"FeatureCollection" ->
334
- dynamic . field ( named : "features" , of : dynamic . list ( of : feature_decoder ) ) (
335
- dyn_value ,
336
- )
352
+ dynamic . field (
353
+ named : "features" ,
354
+ of : dynamic . list ( of : fn ( dyn_value ) {
355
+ feature_decoder ( properties_decoder , dyn_value )
356
+ } ) ,
357
+ ) ( dyn_value )
337
358
|> result . map ( FeatureCollection )
338
359
_ ->
339
360
Error ( [
@@ -353,22 +374,25 @@ fn featurecollection_decoder(
353
374
/// ```gleam
354
375
/// let json_string = "{\"type\":\"Point\",\"coordinates\":[0.0,0.0]}"
355
376
/// let decoded = json.decode(json_string)
356
- /// |> result.then(geojson_decoder)
377
+ /// |> result.then(fn dyn_value { geojson_decoder(properties_decoder, dyn_value) } )
357
378
/// // decoded will be Ok(GeoJSONGeometry(Point([0.0, 0.0]))) if successful
358
379
/// ```
359
380
///
360
381
/// Note: This function expects a valid GeoJSON structure. Invalid or incomplete
361
382
/// GeoJSON data will result in a decode error.
362
- pub fn geojson_decoder ( dyn_value ) -> Result ( GeoJSON , List ( dynamic . DecodeError ) ) {
383
+ pub fn geojson_decoder (
384
+ properties_decoder : dynamic . Decoder ( properties) ,
385
+ dyn_value : dynamic . Dynamic ,
386
+ ) -> Result ( GeoJSON ( properties) , List ( dynamic . DecodeError ) ) {
363
387
use type_str <- result . try ( decode_type_field ( dyn_value ) )
364
388
case type_str {
365
- "Feature" -> result . map ( feature_decoder ( dyn_value ) , GeoJSONFeature )
389
+ "Feature" ->
390
+ result . map ( feature_decoder ( properties_decoder , dyn_value ) , GeoJSONFeature )
366
391
"FeatureCollection" ->
367
- result . map ( featurecollection_decoder ( dyn_value ) , GeoJSONFeatureCollection )
392
+ result . map (
393
+ featurecollection_decoder ( properties_decoder , dyn_value ) ,
394
+ GeoJSONFeatureCollection ,
395
+ )
368
396
_ -> result . map ( geometry_decoder ( dyn_value ) , GeoJSONGeometry )
369
397
}
370
398
}
371
-
372
- fn decode_type_field ( dyn_value ) -> Result ( String , List ( dynamic . DecodeError ) ) {
373
- dynamic . field ( named : "type" , of : dynamic . string ) ( dyn_value )
374
- }
0 commit comments