Skip to content

Commit

Permalink
Remove unused geohash package and update dependencies
Browse files Browse the repository at this point in the history
  • Loading branch information
diiyw committed May 27, 2024
1 parent 843f326 commit 2eed918
Show file tree
Hide file tree
Showing 10 changed files with 881 additions and 148 deletions.
38 changes: 19 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,25 +33,25 @@ Nodis is a Redis implementation using the Golang programming language. This impl

## Supported Commands

| **Client Handling** | **Configuration** | **Key Commands** | **String Commands** | **Set Commands** | **Hash Commands** | **List Commands** | **Sorted Set Commands** |
| ------------------- | ----------------- | ---------------- | ------------------- | ---------------- | ----------------- | ----------------- | ----------------------- |
| CLIENT | FLUSHALL | DEL | GET | SADD | HSET | LPUSH | ZADD |
| PING | FLUSHDB | EXISTS | SET | SSCAN | HGET | RPUSH | ZCARD |
| QUIT | SAVE | EXPIRE | INCR | SCARD | HDEL | LPOP | ZRANK |
| ECHO | INFO | EXPIREAT | DECR | SPOP | HLEN | RPOP | ZREVRANK |
| DBSIZE | | KEYS | SETBIT | SDIFF | HKEYS | LLEN | ZSCORE |
| MULTI | | TTL | GETBIT | SINTER | HEXISTS | LINDEX | ZINCRBY |
| DISCARD | | RENAME | INCR | SISMEMBER | HGETALL | LINSERT | ZRANGE |
| EXEC | | TYPE | DESR | SMEMBERS | HINCRBY | LPUSHX | ZREVRANGE |
| | | SCAN | SETEX | SREM | HICRBYFLOAT | RPUSHX | ZRANGEBYSCORE |
| | | RANDOMKEY | INCRBY | SMOVE | HSETNX | LREM | ZREVRANGEBYSCORE |
| | | RENAMEEX | DECRBY | SRANDMEMBER | HMGET | LSET | ZREM |
| | | PERSIST | SETNX | SINTERSTORE | HMSET | LRANGE | ZREMRANGEBYRANK |
| | | PTTL | INCRBYFLOAT | SUNIONSTORE | HCLEAR | LPOPRPUSH | ZREMRANGEBYSCORE |
| | | UNLINK | APPEND | | HSCAN | RPOPLPUSH | ZCLEAR |
| | | | GETRANGE | | HVALS | BLPOP | ZEXISTS |
| | | | STRLEN | | HSTRLEN | BRPOP | ZUNIONSTORE |
| | | | SETRANGE | | | | ZINTERSTORE |
| **Client Handling** | **Configuration** | **Key Commands** | **String Commands** | **Set Commands** | **Hash Commands** | **List Commands** | **Sorted Set Commands** | **Geo Commands** |
| ------------------- | ----------------- | ---------------- | ------------------- | ---------------- | ----------------- | ----------------- | ----------------------- | ---------------- |
| CLIENT | FLUSHALL | DEL | GET | SADD | HSET | LPUSH | ZADD | GEOADD |
| PING | FLUSHDB | EXISTS | SET | SSCAN | HGET | RPUSH | ZCARD | GEOPOS |
| QUIT | SAVE | EXPIRE | INCR | SCARD | HDEL | LPOP | ZRANK | GEOHASH |
| ECHO | INFO | EXPIREAT | DECR | SPOP | HLEN | RPOP | ZREVRANK | GEODISH |
| DBSIZE | | KEYS | SETBIT | SDIFF | HKEYS | LLEN | ZSCORE | GEORADIUS |
| MULTI | | TTL | GETBIT | SINTER | HEXISTS | LINDEX | ZINCRBY | GEORADIUSBYMEMBER|
| DISCARD | | RENAME | INCR | SISMEMBER | HGETALL | LINSERT | ZRANGE | |
| EXEC | | TYPE | DESR | SMEMBERS | HINCRBY | LPUSHX | ZREVRANGE | |
| | | SCAN | SETEX | SREM | HICRBYFLOAT | RPUSHX | ZRANGEBYSCORE | |
| | | RANDOMKEY | INCRBY | SMOVE | HSETNX | LREM | ZREVRANGEBYSCORE | |
| | | RENAMEEX | DECRBY | SRANDMEMBER | HMGET | LSET | ZREM | |
| | | PERSIST | SETNX | SINTERSTORE | HMSET | LRANGE | ZREMRANGEBYRANK | |
| | | PTTL | INCRBYFLOAT | SUNIONSTORE | HCLEAR | LPOPRPUSH | ZREMRANGEBYSCORE | |
| | | UNLINK | APPEND | | HSCAN | RPOPLPUSH | ZCLEAR | |
| | | | GETRANGE | | HVALS | BLPOP | ZEXISTS | |
| | | | STRLEN | | HSTRLEN | BRPOP | ZUNIONSTORE | |
| | | | SETRANGE | | | | ZINTERSTORE | |

## Get Started

Expand Down
193 changes: 119 additions & 74 deletions geo.go
Original file line number Diff line number Diff line change
@@ -1,56 +1,60 @@
package nodis

import (
"math"

"github.com/diiyw/nodis/ds/zset"
"github.com/diiyw/nodis/geo"
"github.com/mmcloughlin/geohash"
"github.com/diiyw/nodis/internal/geohash"
)

var (
earthRadius = 6372797.560856
dr = math.Pi / 180.0
)
type GeoMember struct {
Longitude float64
Latitude float64
Score float64
Member string
}

func (m *GeoMember) Hash() uint64 {
v, _ := geohash.EncodeWGS84(m.Longitude, m.Latitude)
return v
}

func (n *Nodis) GeoAdd(key string, members ...*geo.Member) int64 {
func (n *Nodis) GeoAdd(key string, members ...*GeoMember) int64 {
var v int64
_ = n.exec(func(tx *Tx) error {
meta := tx.writeKey(key, n.newZSet)
for _, member := range members {
v += meta.value.(*zset.SortedSet).ZAdd(member.Name, float64(member.Hash()))
v += meta.value.(*zset.SortedSet).ZAdd(member.Member, float64(member.Hash()))
}
return nil
})
return v
}

// GeoAddXX adds the specified members to the key only if the member already exists.
func (n *Nodis) GeoAddXX(key string, members ...*geo.Member) int64 {
func (n *Nodis) GeoAddXX(key string, members ...*GeoMember) int64 {
var v int64
_ = n.exec(func(tx *Tx) error {
meta := tx.writeKey(key, nil)
if !meta.isOk() {
return nil
}
for _, member := range members {
v += meta.value.(*zset.SortedSet).ZAddXX(member.Name, float64(member.Hash()))
v += meta.value.(*zset.SortedSet).ZAddXX(member.Member, float64(member.Hash()))
}
return nil
})
return v
}

// GeoAddNX adds the specified members to the key only if the member does not already exist.
func (n *Nodis) GeoAddNX(key string, members ...*geo.Member) int64 {
func (n *Nodis) GeoAddNX(key string, members ...*GeoMember) int64 {
var v int64
_ = n.exec(func(tx *Tx) error {
meta := tx.writeKey(key, n.newZSet)
if meta.isOk() {
return nil
}
for _, member := range members {
v += meta.value.(*zset.SortedSet).ZAddNX(member.Name, float64(member.Hash()))
v += meta.value.(*zset.SortedSet).ZAddNX(member.Member, float64(member.Hash()))
}
return nil
})
Expand All @@ -72,24 +76,12 @@ func (n *Nodis) GeoDist(key string, member1, member2 string) (float64, error) {
if err != nil {
return err
}
lat1, lng1 := geohash.DecodeInt(uint64(score1))
lat2, lng2 := geohash.DecodeInt(uint64(score2))
v = distance(lat1, lng1, lat2, lng2)
v = geohash.DistBetweenGeoHashWGS84(uint64(score1), uint64(score2))
return nil
})
return v, err
}

// distance computes the distance between two given coordinates in meter
func distance(latitude1, longitude1, latitude2, longitude2 float64) float64 {
radLat1 := latitude1 * dr
radLat2 := latitude2 * dr
a := radLat1 - radLat2
b := longitude1*dr - longitude2*dr
return 2 * earthRadius * math.Asin(math.Sqrt(math.Pow(math.Sin(a/2), 2)+
math.Cos(radLat1)*math.Cos(radLat2)*math.Pow(math.Sin(b/2), 2)))
}

func (n *Nodis) GeoHash(key string, members ...string) []string {
var v []string
_ = n.exec(func(tx *Tx) error {
Expand All @@ -102,16 +94,23 @@ func (n *Nodis) GeoHash(key string, members ...string) []string {
if err != nil {
return err
}
lat, lng := geohash.DecodeInt(uint64(score))
v = append(v, geohash.Encode(lat, lng))
latitude, longitude := geohash.DecodeToLongLatWGS84(uint64(score))
code, _ := geohash.Encode(
&geohash.Range{Max: 180, Min: -180},
&geohash.Range{Max: 90, Min: -90},
longitude,
latitude,
geohash.WGS84_GEO_STEP,
)
v = append(v, string(geohash.EncodeToBase32(code.Bits)))
}
return nil
})
return v
}

func (n *Nodis) GeoPos(key string, members ...string) []*geo.Member {
var v = make([]*geo.Member, len(members))
func (n *Nodis) GeoPos(key string, members ...string) []*GeoMember {
var v = make([]*GeoMember, len(members))
_ = n.exec(func(tx *Tx) error {
meta := tx.readKey(key)
if meta == nil {
Expand All @@ -122,69 +121,115 @@ func (n *Nodis) GeoPos(key string, members ...string) []*geo.Member {
if err != nil {
continue
}
lat, lng := geohash.DecodeInt(uint64(score))
v[i] = &geo.Member{Name: member, Latitude: lat, Longitude: lng}
lat, lng := geohash.DecodeToLongLatWGS84(uint64(score))
v[i] = &GeoMember{Member: member, Latitude: lat, Longitude: lng}
}
return nil
})
return v
}

func (n *Nodis) GeoRadius(key string, longitude, latitude, radius float64, count int64, desc bool) (map[string]*geo.Member, error) {
var v = make(map[string]*geo.Member)
func (n *Nodis) GeoRadius(key string, longitude, latitude, radiusMeters float64, count int64, desc bool) ([]*GeoMember, error) {
var v []*GeoMember
err := n.exec(func(tx *Tx) error {
meta := tx.readKey(key)
if !meta.isOk() {
return nil
}
bits := estimatePrecisionByRadius(radius, latitude)
hash := geohash.EncodeInt(latitude, longitude)
neighbors := geohash.NeighborsInt(hash)
for _, lower := range neighbors {
var items []*zset.Item
r := uint64(1 << (64 - bits))
upper := lower + r
if desc {
items = meta.value.(*zset.SortedSet).ZRangeByScore(float64(lower), float64(upper), 0, count, 0)
} else {
items = meta.value.(*zset.SortedSet).ZRevRangeByScore(float64(lower), float64(upper), 0, count, 0)
}
for _, item := range items {
lat, lng := geohash.DecodeInt(uint64(item.Score))
v[item.Member] = &geo.Member{Name: item.Member, Latitude: lat, Longitude: lng}
}
radiusArea, err := geohash.GetAreasByRadiusWGS84(longitude, latitude, radiusMeters)
if err != nil {
return err
}
v = n.geoMembersOfAllNeighbors(meta.value.(*zset.SortedSet), radiusArea, longitude, latitude, radiusMeters, count)
return nil
})
return v, err
}

const (
MercatorMax = 20037726.37
)
func (n *Nodis) GeoRadiusByMember(key, member string, radiusMeters float64, count int64, desc bool) ([]*GeoMember, error) {
var v []*GeoMember
err := n.exec(func(tx *Tx) error {
meta := tx.readKey(key)
if !meta.isOk() {
return nil
}
score, err := meta.value.(*zset.SortedSet).ZScore(member)
if err != nil {
return err
}
lat, lng := geohash.DecodeToLongLatWGS84(uint64(score))
radiusArea, err := geohash.GetAreasByRadiusWGS84(lng, lat, radiusMeters)
if err != nil {
return err
}
v = n.geoMembersOfAllNeighbors(meta.value.(*zset.SortedSet), radiusArea, lng, lat, radiusMeters, count)
return nil
})
return v, err
}

func estimatePrecisionByRadius(radiusMeters float64, lat float64) uint {
if radiusMeters == 0 {
return 63
}
var precision uint = 1
for radiusMeters < MercatorMax {
radiusMeters *= 2
precision++
func (n *Nodis) geoMembersOfAllNeighbors(v *zset.SortedSet, geoRadius *geohash.Radius, lon, lat, radius float64, count int64) []*GeoMember {
neighbors := [9]*geohash.HashBits{
&geoRadius.Hash,
&geoRadius.North,
&geoRadius.South,
&geoRadius.East,
&geoRadius.West,
&geoRadius.NorthEast,
&geoRadius.NorthWest,
&geoRadius.SouthEast,
&geoRadius.SouthWest,
}
/* Make sure range is included in most of the base cases. */
precision -= 2
if lat > 66 || lat < -66 {
precision--
if lat > 80 || lat < -80 {
precision--

var lastProcessed int = 0
plist := make([]*GeoMember, 0, 64)
for i, area := range neighbors {
if area.IsZero() {
continue
}
// When a huge Radius (in the 5000 km range or more) is used,
// adjacent neighbors can be the same, leading to duplicated
// elements. Skip every range which is the same as the one
// processed previously.
if lastProcessed != 0 &&
area.Bits == neighbors[lastProcessed].Bits &&
area.Step == neighbors[lastProcessed].Step {
continue
}
ps := n.membersOfGeoHashBox(v, lon, lat, radius, area, count)
plist = append(plist, ps...)
lastProcessed = i
}
if precision < 1 {
precision = 1
}
if precision > 32 {
precision = 32
return plist
}

// Compute the sorted set scores min (inclusive), max (exclusive) we should
// query in order to retrieve all the elements inside the specified area
// 'hash'. The two scores are returned by reference in *min and *max.
func scoresOfGeoHashBox(hash *geohash.HashBits) (min, max uint64) {
min = hash.Bits << (geohash.WGS84_GEO_STEP*2 - hash.Step*2)
bits := hash.Bits + 1
max = bits << (geohash.WGS84_GEO_STEP*2 - hash.Step*2)
return
}

// Obtain all members between the min/max of this geohash bounding box.
func (n *Nodis) membersOfGeoHashBox(v *zset.SortedSet, longitude, latitude, radius float64, hash *geohash.HashBits, count int64) []*GeoMember {
points := make([]*GeoMember, 0, 32)
min, max := scoresOfGeoHashBox(hash)
vlist := v.ZRangeByScore(float64(min), float64(max), 0, count, 0)
for _, v := range vlist {
x, y := geohash.DecodeToLongLatWGS84(uint64(v.Score))
dist := geohash.GetDistance(x, y, longitude, latitude)
if radius >= dist {
p := &GeoMember{
Longitude: x,
Latitude: y,
Score: v.Score,
Member: v.Member,
}
points = append(points, p)
}
}
return precision*2 - 1
return points
}
37 changes: 0 additions & 37 deletions geo/geo.go

This file was deleted.

1 change: 0 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ go 1.21

require (
github.com/gorilla/websocket v1.5.1
github.com/mmcloughlin/geohash v0.10.0
github.com/tidwall/btree v1.7.0
google.golang.org/protobuf v1.34.0
)
Expand Down
2 changes: 0 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
github.com/mmcloughlin/geohash v0.10.0 h1:9w1HchfDfdeLc+jFEf/04D27KP7E2QmpDu52wPbJWRE=
github.com/mmcloughlin/geohash v0.10.0/go.mod h1:oNZxQo5yWJh0eMQEP/8hwQuVx9Z9tjwFUqcTB1SmG0c=
github.com/tidwall/btree v1.7.0 h1:L1fkJH/AuEh5zBnnBbmTwQ5Lt+bRJ5A8EWecslvo9iI=
github.com/tidwall/btree v1.7.0/go.mod h1:twD9XRA5jj9VUQGELzDO4HPQTNJsoWWfYEL+EUQ2cKY=
golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc=
Expand Down
Loading

0 comments on commit 2eed918

Please sign in to comment.