├── .travis.yml ├── .gitignore ├── LICENSE ├── README.md ├── cache_test.go └── cache.go /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | go: 3 | - 1.6 4 | - tip 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | *.prof 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Melih Mucuk 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # geocache [![GoDoc](https://godoc.org/github.com/melihmucuk/geocache?status.svg)](https://godoc.org/github.com/melihmucuk/geocache) [![Go Report Card](https://goreportcard.com/badge/melihmucuk/geocache)](https://goreportcard.com/report/melihmucuk/geocache) [![Build Status](http://img.shields.io/travis/melihmucuk/geocache.svg?style=flat)](https://travis-ci.org/melihmucuk/geocache) 2 | 3 | geocache is an in-memory cache that is suitable for geolocation based applications. It uses geolocation as a key for storing items. You can specify range on initialization and thats it! You can store any object, it uses interface. 4 | 5 | ### Installation 6 | 7 | `go get github.com/melihmucuk/geocache` 8 | 9 | ### Usage 10 | 11 | ![geolocation cache](http://i.imgur.com/O6UzVEW.png "Geolocation Cache") 12 | 13 | ```go 14 | 15 | import ( 16 | "fmt" 17 | "time" 18 | 19 | "github.com/melihmucuk/geocache" 20 | ) 21 | 22 | func main() { 23 | c, err := geocache.NewCache(5*time.Minute, 30*time.Second, geocache.WithIn1KM) 24 | geoPoint := geocache.GeoPoint{Latitude: 40.9887, Longitude: 28.7817} 25 | if err != nil { 26 | fmt.Println("Error: ", err.Error()) 27 | } else { 28 | c.Set(geoPoint, "helloooo", 2*time.Minute) 29 | v1, ok1 := c.Get(geocache.GeoPoint{Latitude: 41.2, Longitude: 29.3}) 30 | v2, ok2 := c.Get(geocache.GeoPoint{Latitude: 41.2142, Longitude: 29.4234}) 31 | v3, ok3 := c.Get(geocache.GeoPoint{Latitude: 40.9858, Longitude: 28.7852}) 32 | v4, ok4 := c.Get(geocache.GeoPoint{Latitude: 40.9827, Longitude: 28.7883}) 33 | fmt.Println(v1, ok1) 34 | fmt.Println(v2, ok2) 35 | fmt.Println(v3, ok3) 36 | fmt.Println(v4, ok4) 37 | } 38 | } 39 | 40 | ``` 41 | 42 | outputs: 43 | ``` 44 | , false 45 | , false 46 | helloooo, true 47 | helloooo, true 48 | ``` 49 | 50 | ### Information 51 | 52 | You can specify 8 different range. More info can be found [here](http://gis.stackexchange.com/questions/8650/how-to-measure-the-accuracy-of-latitude-and-longitude). 53 | 54 | * `WithIn11KM` 55 | 56 | The first decimal place is worth up to 11.1 km `eg: 41.3, 29.6` 57 | 58 | * `WithIn1KM` 59 | 60 | The second decimal place is worth up to 1.1 km `eg: 41.36, 29.63` 61 | 62 | * `WithIn110M` 63 | 64 | The third decimal place is worth up to 110 m `eg: 41.367, 29.631` 65 | 66 | * `WithIn11M` 67 | 68 | The fourth decimal place is worth up to 11 m `eg: 41.3674, 29.6316` 69 | 70 | * `WithIn1M` 71 | 72 | The fifth decimal place is worth up to 1.1 m `eg: 41.36742, 29.63168` 73 | 74 | * `WithIn11CM` 75 | 76 | The sixth decimal place is worth up to 0.11 m `eg: 41.367421, 29.631689` 77 | 78 | * `WithIn11MM` 79 | 80 | The seventh decimal place is worth up to 11 mm `eg: 41.3674211, 29.6316893` 81 | 82 | * `WithIn1MM` 83 | 84 | The eighth decimal place is worth up to 1.1 mm `eg: 41.36742115, 29.63168932` 85 | -------------------------------------------------------------------------------- /cache_test.go: -------------------------------------------------------------------------------- 1 | package geocache 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | ) 7 | 8 | type TestStruct struct { 9 | ID int 10 | Prop1 string 11 | Prop2 float64 12 | } 13 | 14 | func TestCache(t *testing.T) { 15 | cache, err := NewCache(5*time.Minute, 30*time.Second, WithIn1KM) 16 | 17 | if err != nil { 18 | t.Error("An error occured while creating cache: ", err.Error()) 19 | } 20 | 21 | point := GeoPoint{Latitude: 41.234, Longitude: 29.432} 22 | 23 | value, found := cache.Get(point) 24 | 25 | if found { 26 | t.Errorf("Point: %v should be nil and found should be false", value) 27 | } 28 | 29 | item := TestStruct{ID: 1, Prop1: "hello", Prop2: 3.14} 30 | 31 | cache.Set(point, item, 10*time.Minute) 32 | 33 | cachedItem, found := cache.Get(point) 34 | 35 | if !found || cachedItem == nil { 36 | t.Errorf("Item: %v shouln't be nil", item) 37 | t.Errorf("Found expected: %t got: %t", true, found) 38 | } 39 | } 40 | 41 | func TestItemCount(t *testing.T) { 42 | cache, err := NewCache(5*time.Minute, 30*time.Second, WithIn1KM) 43 | 44 | if err != nil { 45 | t.Error("An error occured while creating cache: ", err.Error()) 46 | } 47 | 48 | point := GeoPoint{Latitude: 41.234, Longitude: 29.432} 49 | item := TestStruct{ID: 1, Prop1: "hello", Prop2: 3.14} 50 | cache.Set(point, item, 10*time.Minute) 51 | 52 | itemCount := cache.ItemCount() 53 | 54 | if itemCount != 1 { 55 | t.Errorf("Item count should be: %d , got: %d", 1, itemCount) 56 | } 57 | } 58 | 59 | func TestFlush(t *testing.T) { 60 | cache, err := NewCache(5*time.Minute, 30*time.Second, WithIn1KM) 61 | 62 | if err != nil { 63 | t.Error("An error occured while creating cache: ", err.Error()) 64 | } 65 | 66 | point := GeoPoint{Latitude: 41.234, Longitude: 29.432} 67 | item := TestStruct{ID: 1, Prop1: "hello", Prop2: 3.14} 68 | cache.Set(point, item, 10*time.Minute) 69 | 70 | itemCount := cache.ItemCount() 71 | 72 | if itemCount != 1 { 73 | t.Errorf("Item count should be: %d , got: %d", 1, itemCount) 74 | } 75 | 76 | cache.Flush() 77 | 78 | itemCountAfterFlush := cache.ItemCount() 79 | 80 | if itemCountAfterFlush != 0 { 81 | t.Errorf("Item count should be: %d , got: %d", 0, itemCountAfterFlush) 82 | } 83 | } 84 | 85 | func TestItems(t *testing.T) { 86 | cache, err := NewCache(5*time.Minute, 30*time.Second, WithIn1KM) 87 | 88 | if err != nil { 89 | t.Error("An error occured while creating cache: ", err.Error()) 90 | } 91 | 92 | point1 := GeoPoint{Latitude: 41.234, Longitude: 29.432} 93 | item1 := TestStruct{ID: 1, Prop1: "ping", Prop2: 3.141} 94 | 95 | point2 := GeoPoint{Latitude: 41.25221, Longitude: 29.44421} 96 | item2 := TestStruct{ID: 2, Prop1: "pong", Prop2: 3.142} 97 | cache.Set(point1, item1, 10*time.Minute) 98 | cache.Set(point2, item2, 10*time.Minute) 99 | 100 | items := cache.Items() 101 | 102 | if len(items) != 2 { 103 | t.Errorf("Item count should be: %d, got: %d", 2, len(items)) 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /cache.go: -------------------------------------------------------------------------------- 1 | package geocache 2 | 3 | import ( 4 | "errors" 5 | "sync" 6 | "time" 7 | 8 | "github.com/shopspring/decimal" 9 | ) 10 | 11 | // Range specifies range of cache 12 | type Range int32 13 | 14 | const ( 15 | // WithIn11KM The first decimal place is worth up to 11.1 km 16 | // eg: 41.3, 29.6 17 | WithIn11KM Range = 1 + iota 18 | 19 | // WithIn1KM The second decimal place is worth up to 1.1 km 20 | // eg: 41.36, 29.63 21 | WithIn1KM 22 | 23 | // WithIn110M The third decimal place is worth up to 110 m 24 | // eg: 41.367, 29.631 25 | WithIn110M 26 | 27 | // WithIn11M The fourth decimal place is worth up to 11 m 28 | // eg: 41.3674, 29.6316 29 | WithIn11M 30 | 31 | // WithIn1M The fifth decimal place is worth up to 1.1 m 32 | // eg: 41.36742, 29.63168 33 | WithIn1M 34 | 35 | // WithIn11CM The sixth decimal place is worth up to 0.11 m 36 | // eg: 41.367421, 29.631689 37 | WithIn11CM 38 | 39 | // WithIn11MM The seventh decimal place is worth up to 11 mm 40 | // eg: 41.3674211, 29.6316893 41 | WithIn11MM 42 | 43 | // WithIn1MM The eighth decimal place is worth up to 1.1 mm 44 | // eg: 41.36742115, 29.63168932 45 | WithIn1MM 46 | ) 47 | 48 | // Item struct keeps cache value and expiration time of object 49 | type Item struct { 50 | Object interface{} 51 | Expiration int64 52 | } 53 | 54 | // Cache struct manages items, expirations and clean ups 55 | type Cache struct { 56 | items map[GeoPoint]Item 57 | m sync.RWMutex 58 | expiration time.Duration 59 | cleanUpInterval time.Duration 60 | precision int32 61 | stopCleanUp chan bool 62 | } 63 | 64 | // GeoPoint specifies point that used as key of cache 65 | type GeoPoint struct { 66 | Latitude float64 67 | Longitude float64 68 | } 69 | 70 | // NewCache creates new Cache with params and returns pointer of Cache and error 71 | // cleanUpInterval used for deleting expired objects from cache. 72 | func NewCache(expiration, cleanUpInterval time.Duration, withInRange Range) (*Cache, error) { 73 | if withInRange < 1 || withInRange > 8 { 74 | return nil, errors.New("Range must be within 1-8!") 75 | } 76 | 77 | c := &Cache{items: make(map[GeoPoint]Item), expiration: expiration, cleanUpInterval: cleanUpInterval, precision: int32(withInRange), stopCleanUp: make(chan bool)} 78 | 79 | ticker := time.NewTicker(cleanUpInterval) 80 | go func() { 81 | for { 82 | select { 83 | case <-ticker.C: 84 | c.cleanUp() 85 | case <-c.stopCleanUp: 86 | ticker.Stop() 87 | return 88 | } 89 | } 90 | }() 91 | 92 | return c, nil 93 | } 94 | 95 | // Set adds object to cache with given geopoint 96 | func (c *Cache) Set(position GeoPoint, value interface{}, expiration time.Duration) { 97 | var exp int64 98 | if expiration == 0 { 99 | expiration = c.expiration 100 | } 101 | 102 | if expiration > 0 { 103 | exp = time.Now().Add(expiration).UnixNano() 104 | } 105 | c.m.Lock() 106 | defer c.m.Unlock() 107 | c.items[position.truncate(c.precision)] = Item{Object: value, Expiration: exp} 108 | } 109 | 110 | // Get gets object from cache with given geopoint 111 | func (c *Cache) Get(position GeoPoint) (interface{}, bool) { 112 | c.m.RLock() 113 | defer c.m.RUnlock() 114 | item, found := c.items[position.truncate(c.precision)] 115 | return item.Object, found 116 | } 117 | 118 | // Items returns cached items 119 | func (c *Cache) Items() map[GeoPoint]Item { 120 | c.m.RLock() 121 | defer c.m.RUnlock() 122 | return c.items 123 | } 124 | 125 | // ItemCount returns cached items count 126 | func (c *Cache) ItemCount() int { 127 | c.m.RLock() 128 | defer c.m.RUnlock() 129 | n := len(c.items) 130 | return n 131 | } 132 | 133 | // Flush deletes all cached items 134 | func (c *Cache) Flush() { 135 | c.m.Lock() 136 | defer c.m.Unlock() 137 | c.items = map[GeoPoint]Item{} 138 | } 139 | 140 | // StopCleanUp stops clean up process. 141 | func (c *Cache) StopCleanUp() { 142 | c.stopCleanUp <- true 143 | } 144 | 145 | func (c *Cache) cleanUp() { 146 | c.m.Lock() 147 | defer c.m.Unlock() 148 | for k, v := range c.items { 149 | if v.Expiration < time.Now().UnixNano() { 150 | delete(c.items, k) 151 | } 152 | } 153 | } 154 | 155 | func (g GeoPoint) truncate(precision int32) GeoPoint { 156 | dLat := decimal.NewFromFloat(g.Latitude) 157 | dLong := decimal.NewFromFloat(g.Longitude) 158 | lat, _ := dLat.Truncate(precision).Float64() 159 | long, _ := dLong.Truncate(precision).Float64() 160 | 161 | return GeoPoint{ 162 | Latitude: lat, 163 | Longitude: long, 164 | } 165 | } 166 | --------------------------------------------------------------------------------