-
Alena Petraki authored2d3c7b54
location.go 3.38 KiB
package field
import (
"context"
"git.perx.ru/perxis/perxis-go/pkg/errors"
"github.com/mitchellh/mapstructure"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
"go.mongodb.org/mongo-driver/x/bsonx"
)
var locationType = &LocationType{}
type LocationParameters struct{}
func (p LocationParameters) Type() Type { return locationType }
func (p LocationParameters) Clone(reset bool) Parameters { return &LocationParameters{} }
func (p LocationParameters) GetMongoIndexes(path string, f *Field) []mongo.IndexModel {
var add, geo mongo.IndexModel
a := path + ".address"
g := path + ".geometry"
add.Options = options.Index().SetName(a)
//add.Options.SetSparse(true)
add.Options.SetPartialFilterExpression(bson.M{a: bson.M{"$exists": true}})
geo.Options = options.Index().SetName(g)
if f.Unique {
add.Options.SetUnique(true)
geo.Options.SetUnique(true)
}
if f.Indexed {
add.Keys = bsonx.Doc{{Key: a, Value: bsonx.Int32(1)}}
geo.Keys = bsonx.Doc{{Key: g, Value: bsonx.String("2dsphere")}}
}
return []mongo.IndexModel{add, geo}
}
type LocationType struct{}
type GeoJSON struct {
Type string `json:"type" bson:"type" mapstructure:"type,omitempty"`
Coordinates []float64 `json:"coordinates" bson:"coordinates" mapstructure:"coordinates"`
}
type GeoObject struct {
Address string `json:"address,omitempty" bson:"address" mapstructure:"address,omitempty"`
Geometry *GeoJSON `json:"geometry,omitempty" bson:"geometry" mapstructure:"geometry,omitempty"`
}
func (LocationType) Name() string {
return "location"
}
func (LocationType) NewParameters() Parameters {
return &LocationParameters{}
}
func (LocationType) IsEmpty(v interface{}) bool {
loc, _ := v.(*GeoObject)
return loc == nil || loc.Address != "" && loc.Geometry != nil
}
func (LocationType) Decode(_ context.Context, _ *Field, v interface{}) (interface{}, error) {
if v == nil {
return nil, nil
}
var g GeoObject
if err := mapstructure.Decode(v, &g); err != nil {
return nil, err
}
if g.Address == "" && g.Geometry == nil {
return nil, errors.New("address or coordinates required")
}
if g.Geometry != nil {
if len(g.Geometry.Coordinates) != 2 {
return nil, errors.New("latitude and longitude required")
}
lat := g.Geometry.Coordinates[0]
lon := g.Geometry.Coordinates[1]
if lat < -180 || lat > 180 {
return nil, errors.New("invalid longitude values, valid are between -180 and 180")
}
if lon < -90 || lon > 90 {
return nil, errors.New("invalid latitude values, valid are between -90 and 90")
}
if g.Geometry.Type != "Point" {
g.Geometry.Type = "Point"
}
}
return &g, nil
}
func (LocationType) Encode(_ context.Context, _ *Field, v interface{}) (interface{}, error) {
if v == nil {
return nil, nil
}
g, ok := v.(*GeoObject)
if !ok {
return nil, errors.New("couldn't encode GeoObject")
}
res := make(map[string]interface{})
if g.Address != "" {
res["address"] = g.Address
}
if g.Geometry != nil {
if len(g.Geometry.Coordinates) != 2 {
return nil, errors.New("latitude and longitude required")
}
lat := g.Geometry.Coordinates[0]
lon := g.Geometry.Coordinates[1]
res["geometry"] = map[string]interface{}{"type": g.Geometry.Type, "coordinates": []interface{}{lat, lon}}
}
return res, nil
}
func Location(o ...interface{}) *Field {
return NewField(&LocationParameters{}, o...)
}