198 lines
4.9 KiB
Go
198 lines
4.9 KiB
Go
/*
|
|
Package geojson is a library for encoding and decoding GeoJSON into Go structs
|
|
using the geometries in the orb package. Supports both the json.Marshaler and
|
|
json.Unmarshaler interfaces as well as helper functions such as
|
|
`UnmarshalFeatureCollection` and `UnmarshalFeature`.
|
|
*/
|
|
package geojson
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
|
|
"go.mongodb.org/mongo-driver/bson"
|
|
)
|
|
|
|
const featureCollection = "FeatureCollection"
|
|
|
|
// A FeatureCollection correlates to a GeoJSON feature collection.
|
|
type FeatureCollection struct {
|
|
Type string `json:"type"`
|
|
BBox BBox `json:"bbox,omitempty"`
|
|
Features []*Feature `json:"features"`
|
|
|
|
// ExtraMembers can be used to encoded/decode extra key/members in
|
|
// the base of the feature collection. Note that keys of "type", "bbox"
|
|
// and "features" will not work as those are reserved by the GeoJSON spec.
|
|
ExtraMembers Properties `json:"-"`
|
|
}
|
|
|
|
// NewFeatureCollection creates and initializes a new feature collection.
|
|
func NewFeatureCollection() *FeatureCollection {
|
|
return &FeatureCollection{
|
|
Type: featureCollection,
|
|
Features: []*Feature{},
|
|
}
|
|
}
|
|
|
|
// Append appends a feature to the collection.
|
|
func (fc *FeatureCollection) Append(feature *Feature) *FeatureCollection {
|
|
fc.Features = append(fc.Features, feature)
|
|
return fc
|
|
}
|
|
|
|
// MarshalJSON converts the feature collection object into the proper JSON.
|
|
// It will handle the encoding of all the child features and geometries.
|
|
// Alternately one can call json.Marshal(fc) directly for the same result.
|
|
// 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()
|
|
} else {
|
|
tmp = make(map[string]interface{}, 3)
|
|
}
|
|
|
|
tmp["type"] = featureCollection
|
|
delete(tmp, "bbox")
|
|
if fc.BBox != nil {
|
|
tmp["bbox"] = fc.BBox
|
|
}
|
|
if fc.Features == nil {
|
|
tmp["features"] = []*Feature{}
|
|
} else {
|
|
tmp["features"] = fc.Features
|
|
}
|
|
|
|
return tmp
|
|
}
|
|
|
|
// UnmarshalJSON decodes the data into a GeoJSON feature collection.
|
|
// Extra/foreign members will be put into the `ExtraMembers` attribute.
|
|
func (fc *FeatureCollection) UnmarshalJSON(data []byte) error {
|
|
if bytes.Equal(data, []byte(`null`)) {
|
|
*fc = FeatureCollection{}
|
|
return nil
|
|
}
|
|
|
|
tmp := make(map[string]nocopyRawMessage, 4)
|
|
|
|
err := unmarshalJSON(data, &tmp)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
*fc = FeatureCollection{}
|
|
for key, value := range tmp {
|
|
switch key {
|
|
case "type":
|
|
err := unmarshalJSON(value, &fc.Type)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
case "bbox":
|
|
err := unmarshalJSON(value, &fc.BBox)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
case "features":
|
|
err := unmarshalJSON(value, &fc.Features)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
default:
|
|
if fc.ExtraMembers == nil {
|
|
fc.ExtraMembers = Properties{}
|
|
}
|
|
|
|
var val interface{}
|
|
err := unmarshalJSON(value, &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
|
|
}
|
|
|
|
// 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) {
|
|
fc := &FeatureCollection{}
|
|
|
|
err := fc.UnmarshalJSON(data)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return fc, nil
|
|
}
|