Giter VIP home page Giter VIP logo

redis-geo's Introduction

Redis GEO Example App

Redis GEO is a simple example showing how to make use of Redis 3.2.0 new GEO capabilities:

Live Demo: https://redis.netcore.io

If Redis hasn't already cemented itself as the venerable Swiss-Army-Knife component present in many high-performance server solutions, the latest 3.2.0 release has made it even more versatile and enhanced it with new GEO powers.

Aiming for the simplest possible useful demonstration of this new functionality, Redis GEO App lets you click on anywhere in the U.S. to find the list of nearest cities within a given radius.

Install Redis 3.2.0

In order to use the new GEO operations you'll need the latest stable 3.2.0 release of redis which you can install in your preferred *NIX system with:

$ wget http://download.redis.io/releases/redis-3.2.0.tar.gz
$ tar xzf redis-3.2.0.tar.gz
$ cd redis-3.2.0
$ make

This will build the redis-server binaries that can be run locally. To also install it as a service that's globally available and automatically started on each boot, run:

$ sudo make install
$ cd utils
$ sudo ./install_server.sh

Create Empty ServiceStack Project

Redis GEO was created from the ServiceStack ASP.NET Empty project template.

Import Geonames dataset

To populate Redis with useful GEO data we'll import the geonames.org postal data which provides the zip codes of all US cities as well as their useful longitude and latitude coordinates.

The dataset is maintained in a tab-delimited US.txt text file which we do a fresh import of using the ServiceStack.Redis C# Client when the AppHost first starts up:

public class AppHost : AppHostBase
{
    public AppHost()
        : base("RedisGeo", typeof(RedisGeoServices).Assembly) {}

    public override void Configure(Container container)
    {
        JsConfig.EmitCamelCaseNames = true;

        container.Register<IRedisClientsManager>(c => 
            new RedisManagerPool(AppSettings.Get("RedisHost", defaultValue:"localhost")));

        ImportCountry(container.Resolve<IRedisClientsManager>(), "US");
    }

    public static void ImportCountry(IRedisClientsManager redisManager, string countryCode)
    {
        using (var redis = redisManager.GetClient())
        using (var reader = new StreamReader(
            File.OpenRead("~/App_Data/{0}.txt".Fmt(countryCode).MapHostAbsolutePath())))
        {
            string line, lastState = null, lastCity = null;
            var results = new List<ServiceStack.Redis.RedisGeo>();
            while ((line = reader.ReadLine()) != null)
            {
                var parts = line.Split('\t');
                var city = parts[2];
                var state = parts[4];
                var latitude = double.Parse(parts[9]);
                var longitude = double.Parse(parts[10]);

                if (city == lastCity) //Skip duplicate entries
                    continue;
                else
                    lastCity = city;

                if (lastState == null)
                    lastState = state;

                if (state != lastState)
                {
                    redis.AddGeoMembers(lastState, results.ToArray());
                    lastState = state;
                    results.Clear();
                }

                results.Add(new ServiceStack.Redis.RedisGeo(longitude, latitude, city));
            }
        }
    }
}

This just parses the US.txt file in our Web Applications /App_Data folder and extracts the state which we'll use as the key for our Redis GEO sorted set and populate it with the longitude and latitude of each city, skipping any duplicates. The script also imports the dataset for each state in separate batches using GEOADD multi argument API.

Implement the FindGeoResults Service

Our App only needs a single Service which we define the contract with using the FindGeoResults Request DTO:

[Route("/georesults/{State}")]
public class FindGeoResults : IReturn<List<RedisGeoResult>>
{
    public string State { get; set; }
    public long? WithinKm { get; set; }
    public double Lng { get; set; }
    public double Lat { get; set; }
}

That's the only DTO our App needs which returns a List<RedisGeoResult>. Implementing the RedisGeoServices is then just a matter fulfilling the above contract by delegating our populated Request DTO properties to the IRedisClient.FindGeoResultsInRadius() Redis Client API which itself just calls GEORADIUS and returns its results:

public class RedisGeoServices : Service
{
    public object Any(FindGeoResults request)
    {
        var results = Redis.FindGeoResultsInRadius(request.State, 
            longitude: request.Lng, latitude: request.Lat,
            radius: request.WithinKm.GetValueOrDefault(20), unit: RedisGeoUnit.Kilometers,
            sortByNearest: true);

        return results;
    }
}

Implement the Client

The entire client App is implemented in the static default.html which is just a jQuery App that just consists of the following markup:

<div id="sidebar">
    <div class="inner">
        <h3>Redis GEO Example</h3>

        <div id="instructions">
            Click on Map to find nearest cities using
            <a href="https://redis.io/commands/georadius">Redis GEO</a>
        </div>

        <div id="info">
            Find cities in <b id="state"></b> within <input id="km" type="text" value="20" /> km
        </div>

        <ol id="results"></ol>
    </div>
</div>
<div id="map"></div>

To show our results from our GEORADIUS query and a <div id="map"/> placeholder used by Google Maps JavaScript API to render a our interactive map of the US in.

The JavaScript below just listens to every click on the map then uses the Geocoder API to find out which state the user clicked on at which point it adds a custom Marker and a Circle with the radius that's specified in the km textbox.

It then calls our /georesults/{State} Service with the Lat/Lng of where the user clicked as well as the distance that it should search within, then displays all the cities within that radius in the Sidebar:

var map;
function initMap() {
    map = new google.maps.Map(document.getElementById('map'), {
        center: { lat: 37.09024, lng: -95.7128917 },
        zoom: 5
    });
    var geocoder = new google.maps.Geocoder();
    var lastMarker, lastRadius;

    google.maps.event.addListener(map, "click", function(e) {
        geocoder.geocode({ 'location': e.latLng }, function(results, status) {
            if (status === google.maps.GeocoderStatus.OK) {
                map.setCenter(e.latLng);

                if (lastMarker != null)
                    lastMarker.setMap(null);

                var marker = lastMarker = new google.maps.Marker({
                    map: map,
                    position: e.latLng
                });

                if (lastRadius != null)
                    lastRadius.setMap(null);

                var km = parseInt($("#km").val());
                var radius = lastRadius = new google.maps.Circle({
                    strokeColor: "#c3fc49",
                    strokeOpacity: 0.8,
                    strokeWeight: 2,
                    fillColor: "#c3fc49",
                    fillOpacity: 0.35,
                    map: map,
                    center: e.latLng,
                    radius: km * 1000
                });
                radius.bindTo('center', marker, 'position');

                var state = getStateAbbr(results);
                $("#state").html(state);
                $("#instructions").hide();
                $("#info").show();

                $.getJSON("/georesults/" + state,
                    { lat: e.latLng.lat(), lng: e.latLng.lng(), withinKm: km },
                    function (r) {
                        var html = $.map(r, function(x) {
                            return "<li>" + x.member + " (" + x.distance.toFixed(2) + "km)</li>";
                        }).join('');
                        $("#results").html(html);
                    });
            }});
        });

    function getStateAbbr(results) {
        for (var i = 0; i < results.length; i++) {
            for (var j = 0; j < results[i].address_components.length; j++) {
                var addr = results[i].address_components[j];
                if (addr.types.indexOf("administrative_area_level_1") >= 0)
                    return addr.short_name;
            }
        }
        return null;
    }
}

The result is a quick demonstration where the user can click on anywhere in the U.S. to return the nearest points of interest.

We hope this simple example piques your interest in Redis new GEO features and highlights some potential use-cases possible with these new capabilities.

Importing different country datasets

Whilst this example just imports US cities, you can change it to import your preferred country instead by extracting the Geonames dataset and copying it into the /App_Data folder then calling ImportCountry() with its country code.

E.g. we can import Ausrtalian Suburbs instead with:

//ImportCountry(container.Resolve<IRedisClientsManager>(), "US");
ImportCountry(container.Resolve<IRedisClientsManager>(), "AU");

Human friendly and convenient versions of each Redis GEO API is available in IRedisClient below:

public interface IRedisClient
{
    //...
    long AddGeoMember(string key, double longitude, double latitude, string member);
    long AddGeoMembers(string key, params RedisGeo[] geoPoints);
    double CalculateDistanceBetweenGeoMembers(string key, string fromMember, string toMember, string unit=null);
    string[] GetGeohashes(string key, params string[] members);
    List<RedisGeo> GetGeoCoordinates(string key, params string[] members);
    string[] FindGeoMembersInRadius(string key, double longitude, double latitude, double radius, string unit);
    List<RedisGeoResult> FindGeoResultsInRadius(string key, double longitude, double latitude, double radius, 
        string unit, int? count = null, bool? sortByNearest = null);
    string[] FindGeoMembersInRadius(string key, string member, double radius, string unit);
    List<RedisGeoResult> FindGeoResultsInRadius(string key, string member, double radius, string unit, 
        int? count = null, bool? sortByNearest = null);
}

Whilst lower-level API's which map 1:1 with Redis server operations are available in IRedisNativeClient:

public interface IRedisNativeClient
{
    //...
    long GeoAdd(string key, double longitude, double latitude, string member);
    long GeoAdd(string key, params RedisGeo[] geoPoints);
    double GeoDist(string key, string fromMember, string toMember, string unit = null);
    string[] GeoHash(string key, params string[] members);
    List<RedisGeo> GeoPos(string key, params string[] members);
    List<RedisGeoResult> GeoRadius(string key, double longitude, double latitude, double radius, string unit,
        bool withCoords=false, bool withDist=false, bool withHash=false, int? count=null, bool? asc=null);
    List<RedisGeoResult> GeoRadiusByMember(string key, string member, double radius, string unit,
        bool withCoords=false, bool withDist=false, bool withHash=false, int? count=null, bool? asc=null);
}

redis-geo's People

Contributors

mythz avatar layoric avatar xplicit avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.