package geojson import ( "bytes" "fmt" "github.com/paulmach/orb" "go.mongodb.org/mongo-driver/bson" ) // A Feature corresponds to GeoJSON feature object type Feature struct { ID interface{} `json:"id,omitempty"` Type string `json:"type"` BBox BBox `json:"bbox,omitempty"` Geometry orb.Geometry `json:"geometry"` Properties Properties `json:"properties"` } // NewFeature creates and initializes a GeoJSON feature given the required attributes. func NewFeature(geometry orb.Geometry) *Feature { return &Feature{ Type: "Feature", Geometry: geometry, Properties: make(map[string]interface{}), } } // Point implements the orb.Pointer interface so that Features can be used // with quadtrees. The point returned is the center of the Bound of the geometry. // To represent the geometry with another point you must create a wrapper type. func (f *Feature) Point() orb.Point { return f.Geometry.Bound().Center() } var _ orb.Pointer = &Feature{} // MarshalJSON 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) MarshalJSON() ([]byte, error) { 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(doc.Properties) == 0 { doc.Properties = nil } return doc } // UnmarshalFeature decodes the data into a GeoJSON feature. // Alternately one can call json.Unmarshal(f) directly for the same result. func UnmarshalFeature(data []byte) (*Feature, error) { f := &Feature{} err := f.UnmarshalJSON(data) if err != nil { return nil, err } return f, nil } // UnmarshalJSON handles the correct unmarshalling of the data // into the orb.Geometry types. func (f *Feature) UnmarshalJSON(data []byte) error { if bytes.Equal(data, []byte(`null`)) { *f = Feature{} return nil } 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 } 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 doc.Geometry != nil { if doc.Geometry.Coordinates == nil && doc.Geometry.Geometries == nil { return ErrInvalidGeometry } g = doc.Geometry.Geometry() } *f = Feature{ ID: doc.ID, Type: doc.Type, Properties: doc.Properties, BBox: doc.BBox, Geometry: g, } return nil } 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"` }