@@ -2,19 +2,24 @@ package geojson
22
33import (
44 "bytes"
5+ "encoding/json"
56 "fmt"
67
78 "github.com/paulmach/orb"
89 "go.mongodb.org/mongo-driver/v2/bson"
910)
1011
11- // A Feature corresponds to GeoJSON feature object
12- type Feature struct {
12+ // A FeatureOf corresponds to GeoJSON feature object but allows for a generic type for the properties.
13+ // This allows users to unmarshal into a struct instead of a map if they choose.
14+ //
15+ // The code assumes type of P is a struct, or map as the GeoJSON spec requires it
16+ // marshal into the a json object.
17+ type FeatureOf [P any ] struct {
1318 ID any `json:"id,omitempty"`
1419 Type string `json:"type"`
1520 BBox BBox `json:"bbox,omitempty"`
1621 Geometry orb.Geometry `json:"geometry"`
17- Properties Properties `json:"properties"`
22+ Properties P `json:"properties"`
1823
1924 // ExtraMembers can be used to encoded/decode extra key/members in
2025 // the base of the feature object. Note that keys of "id", "type", "bbox"
@@ -23,6 +28,9 @@ type Feature struct {
2328 ExtraMembers Properties `json:"-"`
2429}
2530
31+ // A Feature corresponds to GeoJSON feature object.
32+ type Feature = FeatureOf [Properties ]
33+
2634// NewFeature creates and initializes a GeoJSON feature given the required attributes.
2735func NewFeature (geometry orb.Geometry ) * Feature {
2836 return & Feature {
@@ -35,45 +43,75 @@ func NewFeature(geometry orb.Geometry) *Feature {
3543// Point implements the orb.Pointer interface so that Features can be used
3644// with quadtrees. The point returned is the center of the Bound of the geometry.
3745// To represent the geometry with another point you must create a wrapper type.
38- func (f * Feature ) Point () orb.Point {
46+ func (f * FeatureOf [ P ] ) Point () orb.Point {
3947 return f .Geometry .Bound ().Center ()
4048}
4149
42- var _ orb.Pointer = & Feature {}
50+ var _ orb.Pointer = & FeatureOf [ any ] {}
4351
4452// MarshalJSON converts the feature object into the proper JSON.
4553// It will handle the encoding of all the child geometries.
4654// Alternately one can call json.Marshal(f) directly for the same result.
4755// Items in the ExtraMembers map will be included in the base of the
4856// feature object.
49- func (f Feature ) MarshalJSON () ([]byte , error ) {
50- return marshalJSON (newFeatureDoc (& f ))
57+ func (f FeatureOf [P ]) MarshalJSON () ([]byte , error ) {
58+ jProperties , err := f .jsonProperties ()
59+ if err != nil {
60+ return nil , err
61+ }
62+ return marshalJSON (f .newFeatureDoc (jProperties ))
5163}
5264
5365// MarshalBSON converts the feature object into the proper JSON.
5466// It will handle the encoding of all the child geometries.
5567// Alternately one can call json.Marshal(f) directly for the same result.
5668// Items in the ExtraMembers map will be included in the base of the
5769// feature object.
58- func (f Feature ) MarshalBSON () ([]byte , error ) {
59- return bson .Marshal (newFeatureDoc (& f ))
70+ func (f FeatureOf [P ]) MarshalBSON () ([]byte , error ) {
71+ properties , err := f .bsonProperties ()
72+ if err != nil {
73+ return nil , err
74+ }
75+ return bson .Marshal (f .newFeatureDoc (properties ))
76+ }
77+
78+ func (f FeatureOf [P ]) jsonProperties () (json.RawMessage , error ) {
79+ jProperties , err := json .Marshal (f .Properties )
80+ if err != nil {
81+ return nil , err
82+ }
83+
84+ if len (jProperties ) <= 2 { // empty
85+ // we assume it's an object so an empty {} is 2 bytes
86+ // in that case the properties should be nil according to the geojson spec
87+ jProperties = nil
88+ }
89+
90+ return jProperties , nil
6091}
6192
62- func newFeatureDoc (f * Feature ) any {
93+ func (f FeatureOf [P ]) bsonProperties () (any , error ) {
94+ t , value , err := bson .MarshalValue (f .Properties )
95+ if err != nil {
96+ return nil , err
97+ }
98+
99+ if t == bson .TypeEmbeddedDocument && bytes .Equal (value , []byte {5 , 0 , 0 , 0 , 0 }) {
100+ return nil , nil
101+ }
102+
103+ return bson.RawValue {Type : t , Value : value }, nil
104+ }
105+
106+ func (f FeatureOf [P ]) newFeatureDoc (properties any ) any {
63107 if len (f .ExtraMembers ) == 0 {
64- doc := & featureDoc {
108+ return & featureDoc [ any ] {
65109 ID : f .ID ,
66110 Type : "Feature" ,
67- Properties : f .Properties ,
68111 BBox : f .BBox ,
69112 Geometry : NewGeometry (f .Geometry ),
113+ Properties : properties ,
70114 }
71-
72- if len (doc .Properties ) == 0 {
73- doc .Properties = nil
74- }
75-
76- return doc
77115 }
78116
79117 var tmp map [string ]any
@@ -95,12 +133,7 @@ func newFeatureDoc(f *Feature) any {
95133 }
96134
97135 tmp ["geometry" ] = NewGeometry (f .Geometry )
98-
99- if len (f .Properties ) == 0 {
100- tmp ["properties" ] = nil
101- } else {
102- tmp ["properties" ] = f .Properties
103- }
136+ tmp ["properties" ] = properties
104137
105138 return tmp
106139}
@@ -119,9 +152,9 @@ func UnmarshalFeature(data []byte) (*Feature, error) {
119152
120153// UnmarshalJSON handles the correct unmarshalling of the data
121154// into the orb.Geometry types.
122- func (f * Feature ) UnmarshalJSON (data []byte ) error {
155+ func (f * FeatureOf [ P ] ) UnmarshalJSON (data []byte ) error {
123156 if bytes .Equal (data , []byte (`null` )) {
124- * f = Feature {}
157+ * f = FeatureOf [ P ] {}
125158 return nil
126159 }
127160
@@ -132,7 +165,7 @@ func (f *Feature) UnmarshalJSON(data []byte) error {
132165 return err
133166 }
134167
135- * f = Feature {}
168+ * f = FeatureOf [ P ] {}
136169 for key , value := range tmp {
137170 switch key {
138171 case "id" :
@@ -187,15 +220,15 @@ func (f *Feature) UnmarshalJSON(data []byte) error {
187220}
188221
189222// UnmarshalBSON will unmarshal a BSON document created with bson.Marshal.
190- func (f * Feature ) UnmarshalBSON (data []byte ) error {
223+ func (f * FeatureOf [ P ] ) UnmarshalBSON (data []byte ) error {
191224 tmp := make (map [string ]bson.RawValue , 4 )
192225
193226 err := bson .Unmarshal (data , & tmp )
194227 if err != nil {
195228 return err
196229 }
197230
198- * f = Feature {}
231+ * f = FeatureOf [ P ] {}
199232 for key , value := range tmp {
200233 switch key {
201234 case "id" :
@@ -246,10 +279,10 @@ func (f *Feature) UnmarshalBSON(data []byte) error {
246279 return nil
247280}
248281
249- type featureDoc struct {
250- ID any `json:"id,omitempty" bson:"id"`
251- Type string `json:"type" bson:"type"`
252- BBox BBox `json:"bbox,omitempty" bson:"bbox,omitempty"`
253- Geometry * Geometry `json:"geometry" bson:"geometry"`
254- Properties Properties `json:"properties" bson:"properties"`
282+ type featureDoc [ P any ] struct {
283+ ID any `json:"id,omitempty" bson:"id"`
284+ Type string `json:"type" bson:"type"`
285+ BBox BBox `json:"bbox,omitempty" bson:"bbox,omitempty"`
286+ Geometry * Geometry `json:"geometry" bson:"geometry"`
287+ Properties P `json:"properties" bson:"properties"`
255288}
0 commit comments