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...) }