Search by radius in Redis
Since version 3.2.0 Redis that was released on May 6, 2016, the database introduces geospatial commands. Simply said it means that you can ask database like this: I would like to get list of nearest airports in selected location. Let’s extend this use case with pagination and implement it with Ruby.
Sample data
As an example data, we used a list of all airports. You can download csv the file from openflights.org.
Excerpt from the file:
[code language=”text”] 3697,”La Guardia Airport”,”New York”,”United States”,”LGA”,”KLGA”,40.77719879,-73.87259674,21,-5,”A”,”America/New_York”,”airport”,”OurAirports” 3698,”Tallahassee Regional Airport”,”Tallahassee”,”United States”,”TLH”,”KTLH”,30.396499633789062,-84.35030364990234,81,-5,”A”,”America/New_York”,”airport”,”OurAirports” [/code]
We skip the import of South Pole Station Airport because Redis documentation says that valid latitudes are from -85.05112878 to 85.05112878 degrees.
[code language=”text”] 2033,”South Pole Station Airport”,”Stephen’s Island”,”Antarctica”,\N,”NZSP”,-90,0,9300,12,”U”,”Antarctica/South_Pole”,”airport”,”OurAirports” [/code]
Commands overview
We’ll need the following Redis commands to import data, query closest airports and do the pagination.
Command | Description |
---|---|
GEOADD | Adds the specified geospatial items (latitude, longitude, name) to the specified key |
GEORADIUS | Query a sorted set representing a geospatial index to fetch members matching a given maximum distance from a point |
ZRANGE | Return a range of members in a sorted set, by index |
Import airports
Let’s import all data from the CSV into the Redis database. Airports will be stored under key airports.
[code language=”ruby”] # import.rb require ‘redis’ require ‘hippie_csv’
# set path to your csv file airports_csv_path = ‘data/airports.dat’
# initialize redis database redis = Redis.new
# load data from csv file airports = HippieCSV.read(airports_csv_path) # csv columns indexes airport_name = 1 latitude = 6 longitude = 7
# remove South Pole Station Airport from array airports.reject!{ |row| row[airport_name] == ‘South Pole Station Airport’ }
# go through airports and import each row separately airports.each do |row| # use geoadd command for importing redis.call(:geoadd, ‘airports’, row[longitude], row[latitude], row[airport_name]) end [/code]
Search with pagination
The full implementation in Ruby with comments will look like this.
[code language=”ruby”] # search.rb require ‘redis’
# initialize redis database redis = Redis.new
# New York city coordinates – center of search area latitude = 40.705311 longitude = -74.25819
radius = 500 # radius in kilometers page = 1 # selected page perpage = 5 # items per page
# search and save all results under airports_result key redis.call(:georadius, ‘airports’, longitude, latitude, radius, ‘km’, ‘asc’, ‘storedist’, ‘airports_result’)
# calculate limits for zrange command start = (page – 1) * perpage stop = start + perpage – 1
# use zrange to extract page from airports_result result = redis.call(:zrange, ‘airports_result’, start, stop, ‘withscores’)
# print result to the standard output result.each_slice(2).with_index do |(name, distance), i| position = start + i + 1 distance = distance.to_f.round(2) puts “#{position}: #{name}, #{distance} km” end [/code]
The script will print airports ordered by their distance from New York.
[code language=”text”] 1: Newark Liberty International Airport, 7.68 km 2: Linden Airport, 9.84 km 3: Morristown Municipal Airport, 16.85 km 4: Essex County Airport, 19.0 km 5: One Police Plaza Heliport, 21.82 km [/code]
You’ll get a result like this if you set page = 2.
[code language=”text”] 6: Teterboro Airport, 23.15 km 7: La Guardia Airport, 33.46 km 8: Somerset Airport, 35.86 km 9: John F Kennedy International Airport, 41.08 km 10: Westchester County Airport, 61.33 km [/code]
Conclusion
We used the new Redis georadius command to search for the nearest location. According to Google, the results look correct. I didn’t make any performance test but on my machine, the queries to Redis were super fast. So if you are familiar with Redis database you can give it a try, instead of traditional solutions like PostGIS or geocoder.
Great One