166 lines
3.6 KiB
Go
166 lines
3.6 KiB
Go
|
package geocache
|
||
|
|
||
|
import (
|
||
|
"errors"
|
||
|
"sync"
|
||
|
"time"
|
||
|
|
||
|
"github.com/shopspring/decimal"
|
||
|
)
|
||
|
|
||
|
// Range specifies range of cache
|
||
|
type Range int32
|
||
|
|
||
|
const (
|
||
|
// WithIn11KM The first decimal place is worth up to 11.1 km
|
||
|
// eg: 41.3, 29.6
|
||
|
WithIn11KM Range = 1 + iota
|
||
|
|
||
|
// WithIn1KM The second decimal place is worth up to 1.1 km
|
||
|
// eg: 41.36, 29.63
|
||
|
WithIn1KM
|
||
|
|
||
|
// WithIn110M The third decimal place is worth up to 110 m
|
||
|
// eg: 41.367, 29.631
|
||
|
WithIn110M
|
||
|
|
||
|
// WithIn11M The fourth decimal place is worth up to 11 m
|
||
|
// eg: 41.3674, 29.6316
|
||
|
WithIn11M
|
||
|
|
||
|
// WithIn1M The fifth decimal place is worth up to 1.1 m
|
||
|
// eg: 41.36742, 29.63168
|
||
|
WithIn1M
|
||
|
|
||
|
// WithIn11CM The sixth decimal place is worth up to 0.11 m
|
||
|
// eg: 41.367421, 29.631689
|
||
|
WithIn11CM
|
||
|
|
||
|
// WithIn11MM The seventh decimal place is worth up to 11 mm
|
||
|
// eg: 41.3674211, 29.6316893
|
||
|
WithIn11MM
|
||
|
|
||
|
// WithIn1MM The eighth decimal place is worth up to 1.1 mm
|
||
|
// eg: 41.36742115, 29.63168932
|
||
|
WithIn1MM
|
||
|
)
|
||
|
|
||
|
// Item struct keeps cache value and expiration time of object
|
||
|
type Item struct {
|
||
|
Object interface{}
|
||
|
Expiration int64
|
||
|
}
|
||
|
|
||
|
// Cache struct manages items, expirations and clean ups
|
||
|
type Cache struct {
|
||
|
items map[GeoPoint]Item
|
||
|
m sync.RWMutex
|
||
|
expiration time.Duration
|
||
|
cleanUpInterval time.Duration
|
||
|
precision int32
|
||
|
stopCleanUp chan bool
|
||
|
}
|
||
|
|
||
|
// GeoPoint specifies point that used as key of cache
|
||
|
type GeoPoint struct {
|
||
|
Latitude float64
|
||
|
Longitude float64
|
||
|
}
|
||
|
|
||
|
// NewCache creates new Cache with params and returns pointer of Cache and error
|
||
|
// cleanUpInterval used for deleting expired objects from cache.
|
||
|
func NewCache(expiration, cleanUpInterval time.Duration, withInRange Range) (*Cache, error) {
|
||
|
if withInRange < 1 || withInRange > 8 {
|
||
|
return nil, errors.New("Range must be within 1-8!")
|
||
|
}
|
||
|
|
||
|
c := &Cache{items: make(map[GeoPoint]Item), expiration: expiration, cleanUpInterval: cleanUpInterval, precision: int32(withInRange), stopCleanUp: make(chan bool)}
|
||
|
|
||
|
ticker := time.NewTicker(cleanUpInterval)
|
||
|
go func() {
|
||
|
for {
|
||
|
select {
|
||
|
case <-ticker.C:
|
||
|
c.cleanUp()
|
||
|
case <-c.stopCleanUp:
|
||
|
ticker.Stop()
|
||
|
return
|
||
|
}
|
||
|
}
|
||
|
}()
|
||
|
|
||
|
return c, nil
|
||
|
}
|
||
|
|
||
|
// Set adds object to cache with given geopoint
|
||
|
func (c *Cache) Set(position GeoPoint, value interface{}, expiration time.Duration) {
|
||
|
var exp int64
|
||
|
if expiration == 0 {
|
||
|
expiration = c.expiration
|
||
|
}
|
||
|
|
||
|
if expiration > 0 {
|
||
|
exp = time.Now().Add(expiration).UnixNano()
|
||
|
}
|
||
|
c.m.Lock()
|
||
|
defer c.m.Unlock()
|
||
|
c.items[position.truncate(c.precision)] = Item{Object: value, Expiration: exp}
|
||
|
}
|
||
|
|
||
|
// Get gets object from cache with given geopoint
|
||
|
func (c *Cache) Get(position GeoPoint) (interface{}, bool) {
|
||
|
c.m.RLock()
|
||
|
defer c.m.RUnlock()
|
||
|
item, found := c.items[position.truncate(c.precision)]
|
||
|
return item.Object, found
|
||
|
}
|
||
|
|
||
|
// Items returns cached items
|
||
|
func (c *Cache) Items() map[GeoPoint]Item {
|
||
|
c.m.RLock()
|
||
|
defer c.m.RUnlock()
|
||
|
return c.items
|
||
|
}
|
||
|
|
||
|
// ItemCount returns cached items count
|
||
|
func (c *Cache) ItemCount() int {
|
||
|
c.m.RLock()
|
||
|
defer c.m.RUnlock()
|
||
|
n := len(c.items)
|
||
|
return n
|
||
|
}
|
||
|
|
||
|
// Flush deletes all cached items
|
||
|
func (c *Cache) Flush() {
|
||
|
c.m.Lock()
|
||
|
defer c.m.Unlock()
|
||
|
c.items = map[GeoPoint]Item{}
|
||
|
}
|
||
|
|
||
|
// StopCleanUp stops clean up process.
|
||
|
func (c *Cache) StopCleanUp() {
|
||
|
c.stopCleanUp <- true
|
||
|
}
|
||
|
|
||
|
func (c *Cache) cleanUp() {
|
||
|
c.m.Lock()
|
||
|
defer c.m.Unlock()
|
||
|
for k, v := range c.items {
|
||
|
if v.Expiration < time.Now().UnixNano() {
|
||
|
delete(c.items, k)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (g GeoPoint) truncate(precision int32) GeoPoint {
|
||
|
dLat := decimal.NewFromFloat(g.Latitude)
|
||
|
dLong := decimal.NewFromFloat(g.Longitude)
|
||
|
lat, _ := dLat.Truncate(precision).Float64()
|
||
|
long, _ := dLong.Truncate(precision).Float64()
|
||
|
|
||
|
return GeoPoint{
|
||
|
Latitude: lat,
|
||
|
Longitude: long,
|
||
|
}
|
||
|
}
|