Skip to content

Commit

Permalink
geojson: marshal/unmarshal BSON
Browse files Browse the repository at this point in the history
  • Loading branch information
paulmach committed Feb 15, 2023
1 parent ddd8769 commit ad0dea9
Show file tree
Hide file tree
Showing 8 changed files with 799 additions and 51 deletions.
69 changes: 48 additions & 21 deletions geojson/feature.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"fmt"

"github.com/paulmach/orb"
"go.mongodb.org/mongo-driver/bson"
)

// A Feature corresponds to GeoJSON feature object
Expand Down Expand Up @@ -37,19 +38,30 @@ var _ orb.Pointer = &Feature{}
// It will handle the encoding of all the child geometries.
// Alternately one can call json.Marshal(f) directly for the same result.
func (f Feature) MarshalJSON() ([]byte, error) {
jf := &jsonFeature{
return marshalJSON(newFeatureDoc(&f))
}

// MarshalBSON converts the feature object into the proper JSON.
// It will handle the encoding of all the child geometries.
// Alternately one can call json.Marshal(f) directly for the same result.
func (f Feature) MarshalBSON() ([]byte, error) {
return bson.Marshal(newFeatureDoc(&f))
}

func newFeatureDoc(f *Feature) *featureDoc {
doc := &featureDoc{
ID: f.ID,
Type: "Feature",
Properties: f.Properties,
BBox: f.BBox,
Geometry: NewGeometry(f.Geometry),
}

if len(jf.Properties) == 0 {
jf.Properties = nil
if len(doc.Properties) == 0 {
doc.Properties = nil
}

return marshalJSON(jf)
return doc
}

// UnmarshalFeature decodes the data into a GeoJSON feature.
Expand All @@ -67,39 +79,54 @@ func UnmarshalFeature(data []byte) (*Feature, error) {
// UnmarshalJSON handles the correct unmarshalling of the data
// into the orb.Geometry types.
func (f *Feature) UnmarshalJSON(data []byte) error {
jf := &jsonFeature{}
err := unmarshalJSON(data, &jf)
doc := &featureDoc{}
err := unmarshalJSON(data, &doc)
if err != nil {
return err
}

return featureUnmarshalFinish(doc, f)
}

// UnmarshalBSON will unmarshal a BSON document created with bson.Marshal.
func (f *Feature) UnmarshalBSON(data []byte) error {
doc := &featureDoc{}
err := bson.Unmarshal(data, &doc)
if err != nil {
return err
}

if jf.Type != "Feature" {
return fmt.Errorf("geojson: not a feature: type=%s", jf.Type)
return featureUnmarshalFinish(doc, f)
}

func featureUnmarshalFinish(doc *featureDoc, f *Feature) error {
if doc.Type != "Feature" {
return fmt.Errorf("geojson: not a feature: type=%s", doc.Type)
}

var g orb.Geometry
if jf.Geometry != nil {
if jf.Geometry.Coordinates == nil && jf.Geometry.Geometries == nil {
if doc.Geometry != nil {
if doc.Geometry.Coordinates == nil && doc.Geometry.Geometries == nil {
return ErrInvalidGeometry
}
g = jf.Geometry.Geometry()
g = doc.Geometry.Geometry()
}

*f = Feature{
ID: jf.ID,
Type: jf.Type,
Properties: jf.Properties,
BBox: jf.BBox,
ID: doc.ID,
Type: doc.Type,
Properties: doc.Properties,
BBox: doc.BBox,
Geometry: g,
}

return nil
}

type jsonFeature struct {
ID interface{} `json:"id,omitempty"`
Type string `json:"type"`
BBox BBox `json:"bbox,omitempty"`
Geometry *Geometry `json:"geometry"`
Properties Properties `json:"properties"`
type featureDoc struct {
ID interface{} `json:"id,omitempty" bson:"id"`
Type string `json:"type" bson:"type"`
BBox BBox `json:"bbox,omitempty" bson:"bbox,omitempty"`
Geometry *Geometry `json:"geometry" bson:"geometry"`
Properties Properties `json:"properties" bson:"properties"`
}
65 changes: 64 additions & 1 deletion geojson/feature_collection.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ package geojson

import (
"fmt"

"go.mongodb.org/mongo-driver/bson"
)

const featureCollection = "FeatureCollection"
Expand Down Expand Up @@ -44,6 +46,21 @@ func (fc *FeatureCollection) Append(feature *Feature) *FeatureCollection {
// Items in the ExtraMembers map will be included in the base of the
// feature collection object.
func (fc FeatureCollection) MarshalJSON() ([]byte, error) {
m := newFeatureCollectionDoc(fc)
return marshalJSON(m)
}

// MarshalBSON converts the feature collection object into a BSON document
// represented by bytes. It will handle the encoding of all the child features
// and geometries.
// Items in the ExtraMembers map will be included in the base of the
// feature collection object.
func (fc FeatureCollection) MarshalBSON() ([]byte, error) {
m := newFeatureCollectionDoc(fc)
return bson.Marshal(m)
}

func newFeatureCollectionDoc(fc FeatureCollection) map[string]interface{} {
var tmp map[string]interface{}
if fc.ExtraMembers != nil {
tmp = fc.ExtraMembers.Clone()
Expand All @@ -62,7 +79,7 @@ func (fc FeatureCollection) MarshalJSON() ([]byte, error) {
tmp["features"] = fc.Features
}

return marshalJSON(tmp)
return tmp
}

// UnmarshalJSON decodes the data into a GeoJSON feature collection.
Expand Down Expand Up @@ -114,6 +131,52 @@ func (fc *FeatureCollection) UnmarshalJSON(data []byte) error {
return nil
}

// UnmarshalBSON will unmarshal a BSON document created with bson.Marshal.
// Extra/foreign members will be put into the `ExtraMembers` attribute.
func (fc *FeatureCollection) UnmarshalBSON(data []byte) error {
tmp := make(map[string]bson.RawValue, 4)

err := bson.Unmarshal(data, &tmp)
if err != nil {
return err
}

*fc = FeatureCollection{}
for key, value := range tmp {
switch key {
case "type":
fc.Type, _ = bson.RawValue(value).StringValueOK()
case "bbox":
err := value.Unmarshal(&fc.BBox)
if err != nil {
return err
}
case "features":
err := value.Unmarshal(&fc.Features)
if err != nil {
return err
}
default:
if fc.ExtraMembers == nil {
fc.ExtraMembers = Properties{}
}

var val interface{}
err := value.Unmarshal(&val)
if err != nil {
return err
}
fc.ExtraMembers[key] = val
}
}

if fc.Type != featureCollection {
return fmt.Errorf("geojson: not a feature collection: type=%s", fc.Type)
}

return nil
}

// UnmarshalFeatureCollection decodes the data into a GeoJSON feature collection.
// Alternately one can call json.Unmarshal(fc) directly for the same result.
func UnmarshalFeatureCollection(data []byte) (*FeatureCollection, error) {
Expand Down
Loading

0 comments on commit ad0dea9

Please sign in to comment.