Commit 4e4bcae6 authored by mgravell's avatar mgravell

merge GEO PR (incomplete)

parents 786e946d d0d108bf
......@@ -117,7 +117,9 @@ public void CheckFailureRecovered()
ClearAmbientFailures();
}
}
#endif
#endif
[Test]
public void TryGetAzureRoleInstanceIdNoThrow()
{
......
using System.Linq;
using NUnit.Framework;
namespace StackExchange.Redis.Tests
{
[TestFixture]
public class GeoTests : TestBase
{
[Test]
public void GeoAddEveryWay()
{
using (var conn = Create())
{
var db = conn.GetDatabase(3);
var added1 = db.GeoAdd("Sicily", 14.361389, 39.115556, "PalermoPlusOne");
var geo1 = new GeoEntry(13.361389, 38.115556, "Palermo");
var geo2 = new GeoEntry(15.087269, 37.502669, "Catania");
var added2 = db.GeoAdd("Sicily",new GeoEntry[] {geo1,geo2});
Assert.IsTrue(added1 & (added2==2));
}
}
[Test]
public void GetGeoDist()
{
using (var conn = Create())
{
var db = conn.GetDatabase(3);
var geo1 = new GeoEntry(13.361389, 38.115556, "Palermo");
var geo2 = new GeoEntry(15.087269, 37.502669, "Catania");
var added2 = db.GeoAdd("Sicily", new GeoEntry[] { geo1, geo2 });
var val = db.GeoDistance("Sicily", "Palermo", "Catania",GeoUnit.Meters);
Assert.Equals(166274.15156960039, (double) val);
}
}
}
}
......@@ -2,7 +2,7 @@
namespace StackExchange.Redis
{
/// <summary>
/// <summary>
/// Behaviour markers associated with a given command
/// </summary>
[Flags]
......
using System;
namespace StackExchange.Redis
{
/// <summary>
/// GeoRadius command options.
/// </summary>
[Flags]
public enum GeoRadiusOptions
{
/// <summary>
/// No Options
/// </summary>
None = 0,
/// <summary>
/// Redis will return the coordinates of any results.
/// </summary>
WithCoordinates = 1,
/// <summary>
/// Redis will return the distance from center for all results.
/// </summary>
WithDistance = 2,
/// <summary>
/// Redis will return the geo hash value as an integer. (This is the score in the sorted set)
/// </summary>
WithGeoHash = 4
}
/// <summary>
/// The result of a GeoRadius command.
/// </summary>
public struct GeoRadiusResult
{
/// <summary>
/// The matched member.
/// </summary>
public RedisValue Member { get; }
/// <summary>
/// The original GeoRadius command
/// </summary>
public GeoRadius Command { get; }
/// <summary>
/// The distance of the matched member from the center of the geo radius command.
/// </summary>
public double? DistanceFromCenter { get; }
/// <summary>
/// The Geo Hash of the matched member as an integer. (The key in the sorted set)
/// </summary>
public long? GeoHash { get; }
/// <summary>
/// The coordinates of the matched member.
/// </summary>
public GeoPosition? GeoPosition { get; }
/// <summary>
/// Returns a new GeoRadiusResult
/// </summary>
public GeoRadiusResult(RedisValue member,GeoRadius command,double? distanceFromCenter,long? geoHash,GeoPosition? geoPosition)
{
Member = member;
Command = command;
DistanceFromCenter = distanceFromCenter;
GeoHash = geoHash;
GeoPosition = geoPosition;
}
}
/// <summary>
/// Represents a GeoRadius command and its options.
/// </summary>
public class GeoRadius
{
/// <summary>
/// The Radius size of this GeoRadius command.
/// </summary>
public double Radius { get; }
/// <summary>
/// The center point to base the search.
/// </summary>
public GeoPosition GeoPosition { get; }
/// <summary>
/// The key to use.
/// </summary>
public RedisKey Key { get; }
/// <summary>
/// The unit to return distance measurments in.
/// </summary>
public GeoUnit Unit { get; }
/// <summary>
/// The possible options for the GeoRadius command
/// </summary>
public GeoRadiusOptions GeoRadiusOptions { get; }
/// <summary>
/// The maximum number of results to return.
/// However note that internally the command needs to perform an effort proportional to the number of items matching the specified area, so to query very large areas with a very small COUNT option may be slow even if just a few results are returned.
/// </summary>
public int MaxReturnCount { get; }
/// <summary>
/// Creates a new GeoRadius
/// </summary>
public GeoRadius(RedisKey key,GeoPosition geoPosition,double radius,int maxReturnCount =-1,GeoUnit unit = GeoUnit.Meters,GeoRadiusOptions geoRadiusOptions = (GeoRadiusOptions.WithCoordinates | GeoRadiusOptions.WithDistance | GeoRadiusOptions.WithGeoHash))
{
Key = key;
GeoPosition = geoPosition;
Radius = radius;
Unit = unit;
GeoRadiusOptions = geoRadiusOptions;
MaxReturnCount = maxReturnCount;
}
public bool HasFlag(GeoRadiusOptions flag)
{
return (GeoRadiusOptions & flag) != 0;
}
}
/// <summary>
/// Describes the longitude and latitude of a GeoEntry
/// </summary>
public struct GeoPosition : IEquatable<GeoPosition>
{
/// <summary>
/// The Latitude of the GeoPosition
/// </summary>
public double Latitude { get; }
/// <summary>
/// The Logitude of the GeoPosition
/// </summary>
public double Longitude { get; }
/// <summary>
/// Creates a new GeoPosition
/// </summary>
/// <param name="longitude"></param>
/// <param name="latitude"></param>
public GeoPosition(double longitude,double latitude)
{
Longitude = longitude;
Latitude = latitude;
}
/// <summary>
/// See Object.ToString()
/// </summary>
public override string ToString()
{
return string.Format("{0} {1}", Longitude, Latitude);
}
/// <summary>
/// See Object.GetHashCode()
/// Diagonals not an issue in the case of lat/long
/// </summary>
public override int GetHashCode()
{
// diagonals not an issue in the case of lat/long
return Longitude.GetHashCode() ^ Latitude.GetHashCode();
}
/// <summary>
/// Compares two values for equality
/// </summary>
public override bool Equals(object obj)
{
return obj is GeoPosition && Equals((GeoPosition)obj);
}
/// <summary>
/// Compares two values for equality
/// </summary>
public bool Equals(GeoPosition value)
{
return this == value;
}
/// <summary>
/// Compares two values for equality
/// </summary>
public static bool operator ==(GeoPosition x, GeoPosition y)
{
return x.Longitude == y.Longitude && x.Latitude == y.Latitude;
}
/// <summary>
/// Compares two values for non-equality
/// </summary>
public static bool operator !=(GeoPosition x, GeoPosition y)
{
return x.Longitude!= y.Longitude || x.Latitude != y.Latitude;
}
}
/// <summary>
/// Describes a GeoEntry element with the corresponding value
/// GeoEntries are stored in redis as SortedSetEntries
/// </summary>
public struct GeoEntry : IEquatable<GeoEntry>
{
/// <summary>
/// The name of the geo entry
/// </summary>
public string Member { get; }
/// <summary>
/// Describes the longitude and latitude of a GeoEntry
/// </summary>
public GeoPosition Point { get; }
/// <summary>
/// Initializes a GeoEntry value
/// </summary>
public GeoEntry(double longitude,double latitude,RedisValue member)
{
Member = member;
Point = new GeoPosition(longitude, latitude);
}
/// <summary>
/// The longitude of the geo entry
/// </summary>
public double Longitude => Point.Longitude;
/// <summary>
/// The latitude of the geo entry
/// </summary>
public double Latitude => Point.Latitude;
/// <summary>
/// See Object.ToString()
/// </summary>
public override string ToString()
{
return $"({Longitude},{Latitude})={Member}";
}
/// <summary>
/// See Object.GetHashCode()
/// </summary>
public override int GetHashCode()
{
return Point.GetHashCode() ^ Member.GetHashCode();
}
/// <summary>
/// Compares two values for equality
/// </summary>
public override bool Equals(object obj)
{
return obj is GeoEntry && Equals((GeoEntry)obj);
}
/// <summary>
/// Compares two values for equality
/// </summary>
public bool Equals(GeoEntry value)
{
return this == value;
}
/// <summary>
/// Compares two values for equality
/// </summary>
public static bool operator ==(GeoEntry x, GeoEntry y)
{
return Equals(x.Point, y.Point) && x.Member == y.Member;
}
/// <summary>
/// Compares two values for non-equality
/// </summary>
public static bool operator !=(GeoEntry x, GeoEntry y)
{
return !Equals(x.Point, y.Point) || x.Member != y.Member;
}
}
}
\ No newline at end of file
using System;
using System.ComponentModel;
namespace StackExchange.Redis
{
/// <summary>
/// Units associated with Geo Commands
/// </summary>
public enum GeoUnit
{
/// <summary>
/// Meters/Metres
/// </summary>
Meters,
/// <summary>
/// Kilometers/Kilometres
/// </summary>
Kilometers,
/// <summary>
/// Miles
/// </summary>
Miles,
/// <summary>
/// Feet
/// </summary>
Feet
}
}
\ No newline at end of file
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -27,6 +27,55 @@ public RedisValue DebugObject(RedisKey key, CommandFlags flags = CommandFlags.No
return Inner.DebugObject(ToInner(key), flags);
}
public bool GeoAdd(RedisKey key, double longitude, double latitude, RedisValue member, CommandFlags flags = CommandFlags.None)
{
return Inner.GeoAdd(ToInner(key), longitude, latitude, member, flags);
}
public long GeoAdd(RedisKey key, GeoEntry[] geoEntries, CommandFlags flags = CommandFlags.None)
{
return Inner.GeoAdd(ToInner(key), geoEntries, flags);
}
public bool GeoAdd(RedisKey key, GeoEntry geoEntry, RedisValue member, CommandFlags flags = CommandFlags.None)
{
return Inner.GeoAdd(key, geoEntry, member, flags);
}
public bool GeoRemove(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None)
{
return Inner.GeoRemove(key, member, flags);
}
public double GeoDistance(RedisKey key, RedisValue value0, RedisValue value1, GeoUnit geoUnit = GeoUnit.Meters,CommandFlags flags = CommandFlags.None)
{
return Inner.GeoDistance(ToInner(key), value0, value1, geoUnit, flags);
}
public string[] GeoHash(RedisKey key, string[] members, CommandFlags flags = CommandFlags.None)
{
return Inner.GeoHash(key, members, flags);
}
public string[] GeoHash(RedisKey key, string member, CommandFlags flags = CommandFlags.None)
{
return Inner.GeoHash(key, member, flags);
}
public GeoPosition?[] GeoPos(RedisKey key, string[] members, CommandFlags flags = CommandFlags.None)
{
return Inner.GeoPos(key, members, flags);
}
public GeoPosition? GeoPos(RedisKey key, string member, CommandFlags flags = CommandFlags.None)
{
return Inner.GeoPos(key, member, flags);
}
public GeoRadiusResult[] GeoRadius(RedisKey key, GeoRadius geoRadius, CommandFlags flags = CommandFlags.None)
{
return Inner.GeoRadius(key, geoRadius, flags);
}
public double HashDecrement(RedisKey key, RedisValue hashField, double value, CommandFlags flags = CommandFlags.None)
{
return Inner.HashDecrement(ToInner(key), hashField, value, flags);
......
......@@ -230,6 +230,42 @@ internal RedisValue[] GetItemsAsValues()
return arr;
}
}
internal GeoPosition?[] GetItemsAsGeoPositionArray()
{
RawResult[] items = GetItems();
if (items == null)
{
return null;
}
else if (items.Length == 0)
{
return new GeoPosition?[0];
}
else
{
var arr = new GeoPosition?[items.Length];
for (int i = 0; i < arr.Length; i++)
{
RawResult[] item = items[i].GetArrayOfRawResults();
if (item == null)
{
arr[i] = null;
}
else
{
arr[i] = new GeoPosition((double)item[0].AsRedisValue(), (double)item[1].AsRedisValue());
}
}
return arr;
}
}
internal RawResult[] GetItemsAsRawResults()
{
return GetItems();
}
// returns an array of RawResults
internal RawResult[] GetArrayOfRawResults()
......
......@@ -38,6 +38,13 @@ enum RedisCommand
FLUSHALL,
FLUSHDB,
GEOADD,
GEODIST,
GEOHASH,
GEOPOS,
GEORADIUS,
GEORADIUSBYMEMBER,
GET,
GETBIT,
GETRANGE,
......
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
......@@ -13,6 +14,7 @@ internal RedisDatabase(ConnectionMultiplexer multiplexer, int db, object asyncSt
Database = db;
}
private static string[] _redisUnits = new[] {"m", "km", "mi", "ft"};
public object AsyncState => asyncState;
public int Database { get; }
......@@ -45,6 +47,134 @@ public RedisValue DebugObject(RedisKey key, CommandFlags flags = CommandFlags.No
return ExecuteSync(msg, ResultProcessor.RedisValue);
}
public bool GeoAdd(RedisKey key, double longitude,double latitude,RedisValue member,CommandFlags flags = CommandFlags.None)
{
return GeoAdd(key, new GeoEntry(longitude, latitude,member), member, flags);
}
public bool GeoAdd(RedisKey key, GeoEntry geoPosition, RedisValue member, CommandFlags flags = CommandFlags.None)
{
var msg = Message.Create(Database, flags, RedisCommand.GEOADD, key, geoPosition.Longitude, geoPosition.Latitude, member);
return ExecuteSync(msg, ResultProcessor.Boolean);
}
public long GeoAdd(RedisKey key, GeoEntry[] geoEntries, CommandFlags flags = CommandFlags.None)
{
if(geoEntries == null) throw new ArgumentNullException(nameof(geoEntries));
var msg = Message.Create(Database,flags, RedisCommand.GEOADD, key, geoEntries);
return ExecuteSync(msg, ResultProcessor.Int64);
}
public bool GeoRemove(RedisKey key, RedisValue member, CommandFlags flags = CommandFlags.None)
{
return SortedSetRemove(key, member, flags);
}
public double GeoDistance(RedisKey key, RedisValue value0, RedisValue value1, GeoUnit geoUnit = GeoUnit.Meters,CommandFlags flags = CommandFlags.None)
{
var msg = Message.Create(Database, flags, RedisCommand.GEODIST, key,value0,value1,_redisUnits[(int)geoUnit]);
return (double)ExecuteSync(msg, ResultProcessor.RedisValue);
}
public string[] GeoHash(RedisKey key, string[] members, CommandFlags flags = CommandFlags.None)
{
if(members == null)throw new ArgumentNullException(nameof(members));
var redisValues = new RedisValue[members.Length];
for (var i = 0; i < members.Length; i++) redisValues[i] = members[i];
var msg = Message.Create(Database, flags, RedisCommand.GEOHASH, key, redisValues);
var results = ExecuteSync(msg, ResultProcessor.RedisValueArray);
var retArray = new string[results.Length];
for (int i = 0; i < results.Length; i++)
{
retArray[i] = (string)results[i];
}
return retArray;
}
public string[] GeoHash(RedisKey key, string member, CommandFlags flags = CommandFlags.None)
{
return GeoHash(key, new[] {member}, flags);
}
public GeoPosition?[] GeoPos(RedisKey key, string[] members, CommandFlags flags = CommandFlags.None)
{
if (members == null) throw new ArgumentNullException(nameof(members));
var redisValues = new RedisValue[members.Length];
for (var i = 0; i < members.Length; i++) redisValues[i] = members[i];
var msg = Message.Create(Database, flags, RedisCommand.GEOPOS, key, redisValues);
return ExecuteSync(msg, ResultProcessor.RedisGeoPosition);
}
public GeoPosition? GeoPos(RedisKey key, string member, CommandFlags flags = CommandFlags.None)
{
return GeoPos(key, new string[] {member}, flags).FirstOrDefault();
}
public GeoRadiusResult[] GeoRadius(RedisKey key, GeoRadius geoRadius,CommandFlags flags = CommandFlags.None)
{
var redisValues = new List<RedisValue>();
redisValues.Add(geoRadius.GeoPosition.Longitude);
redisValues.Add(geoRadius.GeoPosition.Latitude);
redisValues.Add(geoRadius.Radius);
redisValues.Add(_redisUnits[(int)geoRadius.Unit]);
if(geoRadius.HasFlag(GeoRadiusOptions.WithCoordinates))
redisValues.Add("WITHCOORD");
if (geoRadius.HasFlag(GeoRadiusOptions.WithDistance))
redisValues.Add("WITHDIST");
if (geoRadius.HasFlag(GeoRadiusOptions.WithGeoHash))
redisValues.Add("WITHHASH");
if(geoRadius.MaxReturnCount >0)
redisValues.Add(geoRadius.MaxReturnCount);
var msg = Message.Create(Database, flags, RedisCommand.GEORADIUS, key, redisValues.ToArray());
var items = ExecuteSync(msg, ResultProcessor.RedisGeoRadius);
var arr = new GeoRadiusResult[items.Length];
for (int i = 0; i < arr.Length; i++)
{
RawResult[] item = items[i].GetArrayOfRawResults();
if (item == null)
{
continue;
}
else
{
var x = 0;
var member = item[x++].AsRedisValue();
var distance = geoRadius.HasFlag(GeoRadiusOptions.WithDistance) ? new double?((double)item[x++].AsRedisValue()) : null;
GeoPosition? geoPosition;
long? geoHash;
if (geoRadius.HasFlag(GeoRadiusOptions.WithGeoHash))
{
long tempL;
if (item[x++].TryGetInt64(out tempL))
geoHash = new long?(tempL);
else
geoHash = new long?();
}
else
{
geoHash =null;
}
if (geoRadius.HasFlag(GeoRadiusOptions.WithCoordinates))
{
var radIem = item[x++].GetItemsAsRawResults();
var longitude = (double)radIem[0].AsRedisValue();
var latitude = (double)radIem[1].AsRedisValue();
geoPosition = new GeoPosition?(new GeoPosition(longitude, latitude));
}
else
{
geoPosition = null;
}
var newRadiusResult = new GeoRadiusResult(member, geoRadius, distance, geoHash, geoPosition);
arr[i] = newRadiusResult;
}
}
return arr;
}
public Task<RedisValue> DebugObjectAsync(RedisKey key, CommandFlags flags = CommandFlags.None)
{
var msg = Message.Create(Database, flags, RedisCommand.DEBUG, RedisLiterals.OBJECT, key);
......@@ -926,7 +1056,6 @@ public Task<RedisResult> ScriptEvaluateAsync(LoadedLuaScript script, object para
{
return script.EvaluateAsync(this, parameters, null, flags);
}
public bool SetAdd(RedisKey key, RedisValue value, CommandFlags flags = CommandFlags.None)
{
var msg = Message.Create(Database, flags, RedisCommand.SADD, key, value);
......@@ -2431,4 +2560,4 @@ protected override bool SetResultCore(PhysicalConnection connection, Message mes
}
}
}
}
}
......@@ -66,6 +66,13 @@ abstract class ResultProcessor
public static readonly ResultProcessor<RedisValue[]>
RedisValueArray = new RedisValueArrayProcessor();
public static readonly ResultProcessor<GeoPosition?[]>
RedisGeoPosition = new RedisValueGeoPositionProcessor();
public static readonly ResultProcessor<RawResult[]>
RedisGeoRadius = new RedisGeoRadiusProcessor();
public static readonly ResultProcessor<TimeSpan>
ResponseTimer = new TimingProcessor();
......@@ -1088,6 +1095,39 @@ protected override bool SetResultCore(PhysicalConnection connection, Message mes
return false;
}
}
sealed class RedisValueGeoPositionProcessor : ResultProcessor<GeoPosition?[]>
{
protected override bool SetResultCore(PhysicalConnection connection, Message message, RawResult result)
{
switch (result.Type)
{
case ResultType.MultiBulk:
var arr = result.GetItemsAsGeoPositionArray();
SetResult(message, arr);
return true;
}
return false;
}
}
sealed class RedisGeoRadiusProcessor : ResultProcessor<RawResult[]>
{
protected override bool SetResultCore(PhysicalConnection connection, Message message, RawResult result)
{
switch (result.Type)
{
case ResultType.MultiBulk:
var arr = result.GetItemsAsRawResults();
SetResult(message, arr);
return true;
}
return false;
}
}
sealed class RedisValueProcessor : ResultProcessor<RedisValue>
{
protected override bool SetResultCore(PhysicalConnection connection, Message message, RawResult result)
......
......@@ -4,6 +4,10 @@
namespace StackExchange.Redis
{
/// <summary>
/// Describes a sorted-set element with the corresponding value
/// </summary>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment