Skip to content
This repository was archived by the owner on Jun 23, 2025. It is now read-only.

Commit fa454ed

Browse files
committed
Added GEOPOS & GEOHASH as well
1 parent 84d9048 commit fa454ed

File tree

7 files changed

+245
-13
lines changed

7 files changed

+245
-13
lines changed

internal/cmd/cmd_geoadd.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ func evalGEOADD(c *Cmd, s *dsstore.Store) (*CmdRes, error) {
8383
if obj == nil {
8484
gr = types.NewGeoRegistry()
8585
} else {
86-
if obj.Type != object.ObjTypeSortedSet {
86+
if obj.Type != object.ObjTypeGeoRegistry {
8787
return GEOADDResNilRes, errors.ErrWrongTypeOperation
8888
}
8989
gr = obj.Value.(*types.GeoRegistry)
@@ -109,7 +109,7 @@ func evalGEOADD(c *Cmd, s *dsstore.Store) (*CmdRes, error) {
109109
return GEOADDResNilRes, err
110110
}
111111

112-
s.Put(key, s.NewObj(gr, -1, object.ObjTypeSortedSet), dsstore.WithPutCmd(dsstore.ZAdd))
112+
s.Put(key, s.NewObj(gr, -1, object.ObjTypeGeoRegistry), dsstore.WithPutCmd(dsstore.ZAdd))
113113
return newGEOADDRes(count), nil
114114
}
115115

internal/cmd/cmd_geodist.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ localhost:7379> GEOADD Delhi 77.2096 28.6145 centralDelhi 77.2167 28.6315 CP 77.
3131
OK 3
3232
localhost:7379> GEODIST Delhi CP IndiaGate km
3333
OK 2.416700
34-
3534
`,
3635
Eval: evalGEODIST,
3736
Execute: executeGEODIST,
@@ -79,7 +78,7 @@ func evalGEODIST(c *Cmd, s *dsstore.Store) (*CmdRes, error) {
7978
if obj == nil {
8079
return GEODISTResNilRes, nil
8180
}
82-
if obj.Type != object.ObjTypeSortedSet {
81+
if obj.Type != object.ObjTypeGeoRegistry {
8382
return GEODISTResNilRes, errors.ErrWrongTypeOperation
8483
}
8584
gr = obj.Value.(*types.GeoRegistry)

internal/cmd/cmd_geohash.go

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
// Copyright (c) 2022-present, DiceDB contributors
2+
// All rights reserved. Licensed under the BSD 3-Clause License. See LICENSE file in the project root for full license information.
3+
4+
package cmd
5+
6+
import (
7+
"github.com/dicedb/dice/internal/errors"
8+
"github.com/dicedb/dice/internal/object"
9+
"github.com/dicedb/dice/internal/shardmanager"
10+
dsstore "github.com/dicedb/dice/internal/store"
11+
"github.com/dicedb/dice/internal/types"
12+
"github.com/dicedb/dicedb-go/wire"
13+
)
14+
15+
var cGEOHASH = &CommandMeta{
16+
Name: "GEOHASH",
17+
Syntax: "GEOHASH key [member [member ...]]",
18+
HelpShort: "Returns valid Geohash strings representing the position of one or more elements in a sorted set value representing a geospatial index",
19+
HelpLong: `
20+
The command returns 11 characters Geohash strings, so no precision is lost compared to the Redis internal 52 bit representation
21+
The returned Geohashes have the following properties:
22+
1. They can be shortened removing characters from the right. It will lose precision but will still point to the same area.
23+
2. Strings with a similar prefix are nearby, but the contrary is not true, it is possible that strings with different prefixes are nearby too.
24+
3. It is possible to use them in geohash.org URLs such as http://geohash.org/<geohash-string>.
25+
`,
26+
Examples: `
27+
localhost:7379> GEOADD Delhi 77.2167 28.6315 CP 77.2295 28.6129 IndiaGate 77.1197 28.6412 Rajouri 77.1000 28.5562 Airport 77.1900 28.6517 KarolBagh
28+
OK 5
29+
localhost:7379> GEOHASh Delhi CP IndiaGate
30+
OK
31+
0) ttnfvh5qxd0
32+
1) ttnfv2uf1z0
33+
`,
34+
Eval: evalGEOHASH,
35+
Execute: executeGEOHASH,
36+
}
37+
38+
func init() {
39+
CommandRegistry.AddCommand(cGEOHASH)
40+
}
41+
42+
func newGEOHASHRes(hashArr []string) *CmdRes {
43+
return &CmdRes{
44+
Rs: &wire.Result{
45+
Message: "OK",
46+
Status: wire.Status_OK,
47+
Response: &wire.Result_GEOHASHRes{
48+
GEOHASHRes: &wire.GEOHASHRes{
49+
Hashes: hashArr,
50+
},
51+
},
52+
},
53+
}
54+
}
55+
56+
var (
57+
GEOHASHResNilRes = newGEOHASHRes([]string{})
58+
)
59+
60+
func evalGEOHASH(c *Cmd, s *dsstore.Store) (*CmdRes, error) {
61+
if len(c.C.Args) < 2 {
62+
return GEOHASHResNilRes, errors.ErrWrongArgumentCount("GEOHASH")
63+
}
64+
65+
key := c.C.Args[0]
66+
var gr *types.GeoRegistry
67+
obj := s.Get(key)
68+
if obj == nil {
69+
return GEOHASHResNilRes, nil
70+
}
71+
if obj.Type != object.ObjTypeGeoRegistry {
72+
return GEOHASHResNilRes, errors.ErrWrongTypeOperation
73+
}
74+
gr = obj.Value.(*types.GeoRegistry)
75+
76+
hashArr := gr.Get11BytesHash(c.C.Args[1:])
77+
78+
return newGEOHASHRes(hashArr), nil
79+
80+
}
81+
82+
func executeGEOHASH(c *Cmd, sm *shardmanager.ShardManager) (*CmdRes, error) {
83+
if len(c.C.Args) < 2 {
84+
return GEOHASHResNilRes, errors.ErrWrongArgumentCount("GEOHASH")
85+
}
86+
87+
shard := sm.GetShardForKey(c.C.Args[0])
88+
return evalGEOHASH(c, shard.Thread.Store())
89+
}

internal/cmd/cmd_geopos.go

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
// Copyright (c) 2022-present, DiceDB contributors
2+
// All rights reserved. Licensed under the BSD 3-Clause License. See LICENSE file in the project root for full license information.
3+
4+
package cmd
5+
6+
import (
7+
"github.com/dicedb/dice/internal/errors"
8+
"github.com/dicedb/dice/internal/object"
9+
"github.com/dicedb/dice/internal/shardmanager"
10+
dsstore "github.com/dicedb/dice/internal/store"
11+
"github.com/dicedb/dice/internal/types"
12+
"github.com/dicedb/dicedb-go/wire"
13+
)
14+
15+
var cGEOPOS = &CommandMeta{
16+
Name: "GEOPOS",
17+
Syntax: "GEOPOS key [member [member ...]]",
18+
HelpShort: "Return the positions (longitude,latitude) of all the specified members of the geospatial index.",
19+
HelpLong: `
20+
Given a sorted set representing a geospatial index, populated using the GEOADD command, it is often useful to obtain back the coordinates of specified member.
21+
When the geospatial index is populated via GEOADD the coordinates are converted into a 52 bit geohash,
22+
so the coordinates returned may not be exactly the ones used in order to add the elements, but small errors may be introduced.
23+
`,
24+
Examples: `
25+
localhost:7379> GEOADD Delhi 77.2167 28.6315 CP 77.2295 28.6129 IndiaGate 77.1197 28.6412 Rajouri 77.1000 28.5562 Airport 77.1900 28.6517 KarolBagh
26+
OK 5
27+
localhost:7379> GEOPOS Delhi CP nonEx
28+
OK
29+
0) 77.216700, 28.631498
30+
1) (nil)
31+
`,
32+
Eval: evalGEOPOS,
33+
Execute: executeGEOPOS,
34+
}
35+
36+
func init() {
37+
CommandRegistry.AddCommand(cGEOPOS)
38+
}
39+
40+
func newGEOPOSRes(coordinates []*types.GeoCoordinate) *CmdRes {
41+
reponseCoordinates := make([]*wire.GEOCoordinates, len(coordinates))
42+
for i, coord := range coordinates {
43+
if coord == nil {
44+
continue
45+
}
46+
reponseCoordinates[i] = &wire.GEOCoordinates{
47+
Longitude: coord.Longitude,
48+
Latitude: coord.Latitude,
49+
}
50+
}
51+
return &CmdRes{
52+
Rs: &wire.Result{
53+
Message: "OK",
54+
Status: wire.Status_OK,
55+
Response: &wire.Result_GEOPOSRes{
56+
GEOPOSRes: &wire.GEOPOSRes{
57+
Coordinates: reponseCoordinates,
58+
},
59+
},
60+
},
61+
}
62+
}
63+
64+
var (
65+
GEOPOSResNilRes = newGEOPOSRes([]*types.GeoCoordinate{})
66+
)
67+
68+
func evalGEOPOS(c *Cmd, s *dsstore.Store) (*CmdRes, error) {
69+
if len(c.C.Args) < 2 {
70+
return GEOPOSResNilRes, errors.ErrWrongArgumentCount("GEOPOS")
71+
}
72+
73+
key := c.C.Args[0]
74+
var gr *types.GeoRegistry
75+
obj := s.Get(key)
76+
if obj == nil {
77+
return GEOPOSResNilRes, nil
78+
}
79+
if obj.Type != object.ObjTypeGeoRegistry {
80+
return GEOPOSResNilRes, errors.ErrWrongTypeOperation
81+
}
82+
gr = obj.Value.(*types.GeoRegistry)
83+
coordinates := gr.GetCoordinates(c.C.Args[1:])
84+
return newGEOPOSRes(coordinates), nil
85+
}
86+
87+
func executeGEOPOS(c *Cmd, sm *shardmanager.ShardManager) (*CmdRes, error) {
88+
if len(c.C.Args) < 2 {
89+
return GEOPOSResNilRes, errors.ErrWrongArgumentCount("GEOPOS")
90+
}
91+
92+
shard := sm.GetShardForKey(c.C.Args[0])
93+
return evalGEOPOS(c, shard.Thread.Store())
94+
}

internal/cmd/cmd_geosearch.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,6 @@ func evalGEOSEARCH(c *Cmd, s *dsstore.Store) (*CmdRes, error) {
110110
key := c.C.Args[0]
111111
params, nonParams := parseParams(c.C.Args[1:])
112112

113-
// Validate all the parameters
114113
if len(nonParams) < 2 {
115114
return GEOSEARCHResNilRes, errors.ErrInvalidSyntax("GEOSEARCH")
116115
}
@@ -121,12 +120,12 @@ func evalGEOSEARCH(c *Cmd, s *dsstore.Store) (*CmdRes, error) {
121120
if obj == nil {
122121
return GEODISTResNilRes, nil
123122
}
124-
if obj.Type != object.ObjTypeSortedSet {
123+
if obj.Type != object.ObjTypeGeoRegistry {
125124
return GEOSEARCHResNilRes, errors.ErrWrongTypeOperation
126125
}
127126
gr = obj.Value.(*types.GeoRegistry)
128127

129-
geoElements, err := gr.GeoSearchElementsWithinShape(params, nonParams)
128+
geoElements, err := gr.SearchElementsWithinShape(params, nonParams)
130129

131130
if err != nil {
132131
return GEOSEARCHResNilRes, err

internal/object/object.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ const (
8989
ObjTypeSet
9090
ObjTypeSSMap
9191
ObjTypeSortedSet
92+
ObjTypeGeoRegistry
9293
ObjTypeCountMinSketch
9394
ObjTypeBF
9495
ObjTypeDequeue

internal/types/geo.go

Lines changed: 56 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ func (geoReg *GeoRegistry) GetDistanceBetweenMembers(member1 string, member2 str
181181
}
182182

183183
// This returns all the nodes which are in the given shape
184-
func (geoReg *GeoRegistry) GeoSearchElementsWithinShape(params map[Param]string, nonParams []string) ([]*wire.GEOElement, error) {
184+
func (geoReg *GeoRegistry) SearchElementsWithinShape(params map[Param]string, nonParams []string) ([]*wire.GEOElement, error) {
185185
unit := GetUnitTypeFromParsedParams(params)
186186
if len(unit) == 0 {
187187
return nil, errors.ErrInvalidUnit(string(unit))
@@ -490,6 +490,60 @@ func (geoReg *GeoRegistry) GeoSearchElementsWithinShape(params map[Param]string,
490490
return resultGeoElements, nil
491491
}
492492

493+
// This returns 11 characters geohash representation of the position of the specified elements.
494+
func (geoReg *GeoRegistry) Get11BytesHash(members []string) []string {
495+
496+
result := make([]string, len(members))
497+
geoAlphabet := []rune("0123456789bcdefghjkmnpqrstuvwxyz")
498+
499+
for idx, member := range members {
500+
501+
// Get GEOHASH of the member
502+
hashNode := geoReg.GetByKey(member)
503+
if hashNode == nil {
504+
continue
505+
}
506+
hash := uint64(hashNode.Score())
507+
508+
// Convert the hash to 11 character string (base32)
509+
hashRune := make([]rune, 11)
510+
for i := 0; i < 11; i++ {
511+
var idx uint64
512+
if i == 10 {
513+
idx = 0 // pad last char due to only 52 bits
514+
} else {
515+
shift := 52 - ((i + 1) * 5)
516+
idx = (hash >> shift) & 0x1F
517+
}
518+
hashRune[i] = geoAlphabet[idx]
519+
}
520+
result[idx] = string(hashRune)
521+
522+
}
523+
return result
524+
}
525+
526+
// This returns coordinates (longitute, latitude) of all the members
527+
func (geoReg *GeoRegistry) GetCoordinates(members []string) []*GeoCoordinate {
528+
result := make([]*GeoCoordinate, len(members))
529+
for i, member := range members {
530+
// Get GEOHASH of the member
531+
hashNode := geoReg.GetByKey(member)
532+
if hashNode == nil {
533+
continue
534+
}
535+
hash := uint64(hashNode.Score())
536+
coordinate, _ := NewGeoCoordinateFromHash(hash)
537+
result[i] = coordinate
538+
}
539+
return result
540+
}
541+
542+
// /////////////////////////////////////////////////////
543+
// //////////// Utility Functions //////////////////////
544+
// /////////////////////////////////////////////////////
545+
546+
// Filter dimensions based on flags for GEO Search
493547
func filterDimensionsBasedOnFlags(geoElements []*wire.GEOElement, withCoord, withDist, withHash bool) {
494548
for _, ele := range geoElements {
495549
if !withCoord {
@@ -506,10 +560,6 @@ func filterDimensionsBasedOnFlags(geoElements []*wire.GEOElement, withCoord, wit
506560
}
507561
}
508562

509-
// /////////////////////////////////////////////////////
510-
// //////////// Utility Functions //////////////////////
511-
// /////////////////////////////////////////////////////
512-
513563
// Encode given Lon and Lat to GEOHASH
514564
func EncodeHash(longitude, latitude float64) uint64 {
515565
return geohash.EncodeIntWithPrecision(latitude, longitude, BIT_PRECISION)
@@ -587,7 +637,7 @@ func ConvertToMeter(distance float64, unit Param) (float64, error) {
587637
default:
588638
return 0, errors.ErrInvalidUnit(string(unit))
589639
}
590-
// Round to 4 decimal places
640+
// Round to 5 decimal places
591641
return math.Round(result*10000) / 10000, nil
592642
}
593643

0 commit comments

Comments
 (0)