diff --git a/Makefile b/Makefile index 1ab5838538..809c2f2f18 100644 --- a/Makefile +++ b/Makefile @@ -377,8 +377,8 @@ endif endif endif - [ ! -e work/redis-git ] && git clone https://github.com/antirez/redis.git --branch 3.0 --single-branch work/redis-git && cd work/redis-git|| true - [ -e work/redis-git ] && cd work/redis-git && git reset --hard && git pull && git checkout 3.0 || true + [ ! -e work/redis-git ] && git clone https://github.com/antirez/redis.git --branch geo --single-branch work/redis-git && cd work/redis-git|| true + [ -e work/redis-git ] && cd work/redis-git && git reset --hard && git pull && git checkout geo || true make -C work/redis-git clean make -C work/redis-git -j4 diff --git a/src/main/java/com/lambdaworks/redis/GeoArgs.java b/src/main/java/com/lambdaworks/redis/GeoArgs.java new file mode 100644 index 0000000000..93c68a301b --- /dev/null +++ b/src/main/java/com/lambdaworks/redis/GeoArgs.java @@ -0,0 +1,79 @@ +package com.lambdaworks.redis; + +import com.lambdaworks.redis.protocol.CommandArgs; + +/** + * @author Mark Paluch + */ +public class GeoArgs { + + private boolean withdistance; + private boolean withcoordinates; + private boolean withhash; + private boolean noproperties; + private Sort sort = Sort.none; + + public GeoArgs withDistance() { + withdistance = true; + return this; + } + + public GeoArgs withCoordinates() { + withcoordinates = true; + return this; + } + + public GeoArgs withHash() { + withhash = true; + return this; + } + + public GeoArgs noProperties() { + noproperties = true; + return this; + } + + public GeoArgs asc() { + return sort(Sort.asc); + } + + public GeoArgs desc() { + return sort(Sort.desc); + } + + public GeoArgs sort(Sort sort) { + this.sort = sort; + return this; + } + + public enum Sort { + asc, desc, none; + } + + public enum Unit { + meter, kilometer, feet, mile; + } + + public void build(CommandArgs args) { + if (withdistance) { + args.add("withdistance"); + } + + if (withcoordinates) { + args.add("withcoordinates"); + } + + if (withhash) { + args.add("withhash"); + } + + if (noproperties) { + args.add("noproperties"); + } + + if (sort != null && sort != Sort.none) { + args.add(sort.name()); + } + + } +} diff --git a/src/main/java/com/lambdaworks/redis/RedisAsyncConnection.java b/src/main/java/com/lambdaworks/redis/RedisAsyncConnection.java index 5a489b9c5a..f2fd03bc1e 100644 --- a/src/main/java/com/lambdaworks/redis/RedisAsyncConnection.java +++ b/src/main/java/com/lambdaworks/redis/RedisAsyncConnection.java @@ -13,7 +13,8 @@ public interface RedisAsyncConnection extends RedisHashesAsyncConnection, RedisKeysAsyncConnection, RedisStringsAsyncConnection, RedisListsAsyncConnection, RedisSetsAsyncConnection, RedisSortedSetsAsyncConnection, RedisScriptingAsyncConnection, RedisServerAsyncConnection, - RedisHLLAsyncConnection, BaseRedisAsyncConnection, RedisClusterAsyncConnection { + RedisHLLAsyncConnection, RedisGeoAsyncConnection, BaseRedisAsyncConnection, + RedisClusterAsyncConnection { /** * Set the default timeout for operations. diff --git a/src/main/java/com/lambdaworks/redis/RedisAsyncConnectionImpl.java b/src/main/java/com/lambdaworks/redis/RedisAsyncConnectionImpl.java index d2b9fb029e..6776a6e292 100644 --- a/src/main/java/com/lambdaworks/redis/RedisAsyncConnectionImpl.java +++ b/src/main/java/com/lambdaworks/redis/RedisAsyncConnectionImpl.java @@ -2,7 +2,7 @@ package com.lambdaworks.redis; -import static com.lambdaworks.redis.protocol.CommandType.*; +import static com.lambdaworks.redis.protocol.CommandType.EXEC; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; @@ -14,8 +14,18 @@ import com.lambdaworks.codec.Base16; import com.lambdaworks.redis.codec.RedisCodec; -import com.lambdaworks.redis.output.*; -import com.lambdaworks.redis.protocol.*; +import com.lambdaworks.redis.output.KeyStreamingChannel; +import com.lambdaworks.redis.output.KeyValueStreamingChannel; +import com.lambdaworks.redis.output.MultiOutput; +import com.lambdaworks.redis.output.ScoredValueStreamingChannel; +import com.lambdaworks.redis.output.ValueStreamingChannel; +import com.lambdaworks.redis.protocol.Command; +import com.lambdaworks.redis.protocol.CommandArgs; +import com.lambdaworks.redis.protocol.CommandOutput; +import com.lambdaworks.redis.protocol.CommandType; +import com.lambdaworks.redis.protocol.ConnectionWatchdog; +import com.lambdaworks.redis.protocol.RedisCommand; +import com.lambdaworks.redis.protocol.SetArgs; import io.netty.channel.ChannelHandler; /** @@ -1590,6 +1600,52 @@ public RedisFuture> zrangebylex(K key, String min, String max, long offs return dispatch(commandBuilder.zrangebylex(key, min, max, offset, count)); } + @Override + public RedisFuture geoadd(K key, double latitude, double longitude, V member) { + return dispatch(commandBuilder.geoadd(key, latitude, longitude, member)); + } + + @Override + public RedisFuture geoadd(K key, Object... latLongMember) { + return dispatch(commandBuilder.geoadd(key, latLongMember)); + } + + @Override + public RedisFuture> georadius(K key, double latitude, double longitude, double distance, GeoArgs.Unit unit) { + return dispatch(commandBuilder.georadius(key, latitude, longitude, distance, unit.name())); + } + + @Override + public RedisFuture> georadius(K key, double latitude, double longitude, double distance, GeoArgs.Unit unit, + GeoArgs geoArgs) { + return dispatch(commandBuilder.georadius(key, latitude, longitude, distance, unit.name(), geoArgs)); + } + + @Override + public RedisFuture> georadiusbymember(K key, V member, double distance, GeoArgs.Unit unit) { + return dispatch(commandBuilder.georadiusbymember(key, member, distance, unit.name())); + } + + @Override + public RedisFuture> georadiusbymember(K key, V member, double distance, GeoArgs.Unit unit, GeoArgs geoArgs) { + return dispatch(commandBuilder.georadiusbymember(key, member, distance, unit.name(), geoArgs)); + } + + @Override + public RedisFuture> geoencode(double latitude, double longitude) { + return dispatch(commandBuilder.geoencode(latitude, longitude, null, null)); + } + + @Override + public RedisFuture> geoencode(double latitude, double longitude, double distance, GeoArgs.Unit unit) { + return dispatch(commandBuilder.geoencode(latitude, longitude, distance, unit.name())); + } + + @Override + public RedisFuture> geodecode(long geohash) { + return dispatch(commandBuilder.geodecode(geohash)); + } + protected RedisCommand dispatch(CommandType type, CommandOutput output) { return dispatch(type, output, null); } diff --git a/src/main/java/com/lambdaworks/redis/RedisClusterAsyncConnection.java b/src/main/java/com/lambdaworks/redis/RedisClusterAsyncConnection.java index 4a468574b4..583aa1edde 100644 --- a/src/main/java/com/lambdaworks/redis/RedisClusterAsyncConnection.java +++ b/src/main/java/com/lambdaworks/redis/RedisClusterAsyncConnection.java @@ -13,7 +13,7 @@ public interface RedisClusterAsyncConnection extends RedisHashesAsyncConnection, RedisKeysAsyncConnection, RedisStringsAsyncConnection, RedisListsAsyncConnection, RedisSetsAsyncConnection, RedisSortedSetsAsyncConnection, RedisScriptingAsyncConnection, RedisServerAsyncConnection, - RedisHLLAsyncConnection, BaseRedisAsyncConnection { + RedisHLLAsyncConnection, RedisGeoAsyncConnection, BaseRedisAsyncConnection { /** * Close the connection. The connection will become not usable anymore as soon as this method was called. diff --git a/src/main/java/com/lambdaworks/redis/RedisClusterConnection.java b/src/main/java/com/lambdaworks/redis/RedisClusterConnection.java index a2ae47e16f..c9a66b2e0b 100644 --- a/src/main/java/com/lambdaworks/redis/RedisClusterConnection.java +++ b/src/main/java/com/lambdaworks/redis/RedisClusterConnection.java @@ -12,7 +12,7 @@ */ public interface RedisClusterConnection extends RedisHashesConnection, RedisKeysConnection, RedisStringsConnection, RedisListsConnection, RedisSetsConnection, RedisSortedSetsConnection, - RedisScriptingConnection, RedisServerConnection, RedisHLLConnection { + RedisScriptingConnection, RedisServerConnection, RedisHLLConnection, RedisGeoConnection { /** * Close the connection. The connection will become not usable anymore as soon as this method was called. diff --git a/src/main/java/com/lambdaworks/redis/RedisCommandBuilder.java b/src/main/java/com/lambdaworks/redis/RedisCommandBuilder.java index bd87bce501..87140772e1 100644 --- a/src/main/java/com/lambdaworks/redis/RedisCommandBuilder.java +++ b/src/main/java/com/lambdaworks/redis/RedisCommandBuilder.java @@ -1,23 +1,105 @@ package com.lambdaworks.redis; -import static com.lambdaworks.redis.protocol.CommandKeyword.*; +import static com.lambdaworks.redis.protocol.CommandKeyword.ADDSLOTS; +import static com.lambdaworks.redis.protocol.CommandKeyword.AFTER; +import static com.lambdaworks.redis.protocol.CommandKeyword.AND; +import static com.lambdaworks.redis.protocol.CommandKeyword.BEFORE; +import static com.lambdaworks.redis.protocol.CommandKeyword.CHANNELS; +import static com.lambdaworks.redis.protocol.CommandKeyword.COUNT; +import static com.lambdaworks.redis.protocol.CommandKeyword.DELSLOTS; +import static com.lambdaworks.redis.protocol.CommandKeyword.ENCODING; +import static com.lambdaworks.redis.protocol.CommandKeyword.FAILOVER; +import static com.lambdaworks.redis.protocol.CommandKeyword.FLUSH; +import static com.lambdaworks.redis.protocol.CommandKeyword.FLUSHSLOTS; +import static com.lambdaworks.redis.protocol.CommandKeyword.FORCE; +import static com.lambdaworks.redis.protocol.CommandKeyword.FORGET; +import static com.lambdaworks.redis.protocol.CommandKeyword.GETKEYSINSLOT; +import static com.lambdaworks.redis.protocol.CommandKeyword.GETNAME; +import static com.lambdaworks.redis.protocol.CommandKeyword.HARD; +import static com.lambdaworks.redis.protocol.CommandKeyword.IDLETIME; +import static com.lambdaworks.redis.protocol.CommandKeyword.IMPORTING; +import static com.lambdaworks.redis.protocol.CommandKeyword.KILL; +import static com.lambdaworks.redis.protocol.CommandKeyword.LEN; +import static com.lambdaworks.redis.protocol.CommandKeyword.LIMIT; +import static com.lambdaworks.redis.protocol.CommandKeyword.LIST; +import static com.lambdaworks.redis.protocol.CommandKeyword.LOAD; +import static com.lambdaworks.redis.protocol.CommandKeyword.MEET; +import static com.lambdaworks.redis.protocol.CommandKeyword.MIGRATING; +import static com.lambdaworks.redis.protocol.CommandKeyword.NO; +import static com.lambdaworks.redis.protocol.CommandKeyword.NODE; +import static com.lambdaworks.redis.protocol.CommandKeyword.NODES; +import static com.lambdaworks.redis.protocol.CommandKeyword.NOSAVE; +import static com.lambdaworks.redis.protocol.CommandKeyword.NOT; +import static com.lambdaworks.redis.protocol.CommandKeyword.NUMPAT; +import static com.lambdaworks.redis.protocol.CommandKeyword.NUMSUB; +import static com.lambdaworks.redis.protocol.CommandKeyword.ONE; +import static com.lambdaworks.redis.protocol.CommandKeyword.OR; +import static com.lambdaworks.redis.protocol.CommandKeyword.PAUSE; +import static com.lambdaworks.redis.protocol.CommandKeyword.REFCOUNT; +import static com.lambdaworks.redis.protocol.CommandKeyword.REPLICATE; +import static com.lambdaworks.redis.protocol.CommandKeyword.RESET; +import static com.lambdaworks.redis.protocol.CommandKeyword.RESETSTAT; +import static com.lambdaworks.redis.protocol.CommandKeyword.REWRITE; +import static com.lambdaworks.redis.protocol.CommandKeyword.SEGFAULT; +import static com.lambdaworks.redis.protocol.CommandKeyword.SETNAME; +import static com.lambdaworks.redis.protocol.CommandKeyword.SETSLOT; +import static com.lambdaworks.redis.protocol.CommandKeyword.SLAVES; +import static com.lambdaworks.redis.protocol.CommandKeyword.SLOTS; +import static com.lambdaworks.redis.protocol.CommandKeyword.SOFT; +import static com.lambdaworks.redis.protocol.CommandKeyword.WITHSCORES; +import static com.lambdaworks.redis.protocol.CommandKeyword.XOR; import static com.lambdaworks.redis.protocol.CommandType.*; -import java.io.Serializable; import java.util.Date; import java.util.List; import java.util.Map; import java.util.Set; import com.lambdaworks.redis.codec.RedisCodec; -import com.lambdaworks.redis.output.*; -import com.lambdaworks.redis.protocol.*; +import com.lambdaworks.redis.output.ArrayOutput; +import com.lambdaworks.redis.output.BooleanListOutput; +import com.lambdaworks.redis.output.BooleanOutput; +import com.lambdaworks.redis.output.ByteArrayOutput; +import com.lambdaworks.redis.output.DateOutput; +import com.lambdaworks.redis.output.DoubleOutput; +import com.lambdaworks.redis.output.IntegerOutput; +import com.lambdaworks.redis.output.KeyListOutput; +import com.lambdaworks.redis.output.KeyOutput; +import com.lambdaworks.redis.output.KeyScanOutput; +import com.lambdaworks.redis.output.KeyScanStreamingOutput; +import com.lambdaworks.redis.output.KeyStreamingChannel; +import com.lambdaworks.redis.output.KeyStreamingOutput; +import com.lambdaworks.redis.output.KeyValueOutput; +import com.lambdaworks.redis.output.KeyValueScanStreamingOutput; +import com.lambdaworks.redis.output.KeyValueStreamingChannel; +import com.lambdaworks.redis.output.KeyValueStreamingOutput; +import com.lambdaworks.redis.output.MapOutput; +import com.lambdaworks.redis.output.MapScanOutput; +import com.lambdaworks.redis.output.NestedMultiOutput; +import com.lambdaworks.redis.output.ScoredValueListOutput; +import com.lambdaworks.redis.output.ScoredValueScanOutput; +import com.lambdaworks.redis.output.ScoredValueScanStreamingOutput; +import com.lambdaworks.redis.output.ScoredValueStreamingChannel; +import com.lambdaworks.redis.output.ScoredValueStreamingOutput; +import com.lambdaworks.redis.output.StatusOutput; +import com.lambdaworks.redis.output.StringListOutput; +import com.lambdaworks.redis.output.ValueListOutput; +import com.lambdaworks.redis.output.ValueOutput; +import com.lambdaworks.redis.output.ValueScanOutput; +import com.lambdaworks.redis.output.ValueScanStreamingOutput; +import com.lambdaworks.redis.output.ValueSetOutput; +import com.lambdaworks.redis.output.ValueStreamingChannel; +import com.lambdaworks.redis.output.ValueStreamingOutput; +import com.lambdaworks.redis.protocol.Command; +import com.lambdaworks.redis.protocol.CommandArgs; +import com.lambdaworks.redis.protocol.CommandOutput; +import com.lambdaworks.redis.protocol.RedisCommand; +import com.lambdaworks.redis.protocol.SetArgs; /** - * - * @author Mark Paluch * @param * @param + * @author Mark Paluch */ class RedisCommandBuilder extends BaseRedisCommandBuilder { @@ -1638,6 +1720,78 @@ public Command clusterReset(boolean hard) { return createCommand(CLUSTER, new StatusOutput(codec), args); } + public Command geoadd(K key, double latitude, double longitude, V member) { + CommandArgs args = new CommandArgs(codec).addKey(key).add(latitude).add(longitude).addValue(member); + return createCommand(GEOADD, new IntegerOutput(codec), args); + } + + public Command geoadd(K key, Object[] latLongMember) { + + assertNotEmpty(latLongMember, "latLongMember " + MUST_NOT_BE_EMPTY); + assertNoNullElements(latLongMember, "latLongMember " + MUST_NOT_CONTAIN_NULL_ELEMENTS); + assertTrue( + latLongMember.length % 3 == 0, + "latLongMember.length must be a multiple of 3 and contain a " + + "sequence of latitude1, longitude1, member1, latitude2, longitude2, member2, ... latitudeN, longitudeN, memberN"); + + CommandArgs args = new CommandArgs(codec).addKey(key); + + for (int i = 0; i < latLongMember.length; i += 3) { + args.add((Double) latLongMember[i]); + args.add((Double) latLongMember[i + 1]); + args.addValue((V) latLongMember[i + 2]); + } + + return createCommand(GEOADD, new IntegerOutput(codec), args); + } + + public Command> georadius(K key, double latitude, double longitude, double distance, String unit) { + CommandArgs args = new CommandArgs(codec).addKey(key).add(latitude).add(longitude).add(distance).add(unit); + return createCommand(GEORADIUS, new ValueSetOutput(codec), args); + } + + public Command> georadius(K key, double latitude, double longitude, double distance, String unit, + GeoArgs geoArgs) { + + CommandArgs args = new CommandArgs(codec).addKey(key).add(latitude).add(longitude).add(distance).add(unit); + + if (geoArgs != null) { + geoArgs.build(args); + } + + return createCommand(GEORADIUS, new NestedMultiOutput(codec), args); + } + + public Command> georadiusbymember(K key, V member, double distance, String unit) { + CommandArgs args = new CommandArgs(codec).addKey(key).addValue(member).add(distance).add(unit); + return createCommand(GEORADIUSBYMEMBER, new ValueSetOutput(codec), args); + } + + public Command> georadiusbymember(K key, V member, double distance, String unit, GeoArgs geoArgs) { + CommandArgs args = new CommandArgs(codec).addKey(key).addValue(member).add(distance).add(unit); + + if (geoArgs != null) { + geoArgs.build(args); + } + + return createCommand(GEORADIUSBYMEMBER, new NestedMultiOutput(codec), args); + } + + public Command> geoencode(double latitude, double longitude, Double distance, String unit) { + CommandArgs args = new CommandArgs(codec).add(latitude).add(longitude); + + if (distance != null && unit != null) { + args.add(distance).add(unit); + } + return createCommand(GEOENCODE, new NestedMultiOutput(codec), args); + } + + public Command> geodecode(long geohash) { + CommandArgs args = new CommandArgs(codec).add(geohash); + + return createCommand(GEODECODE, new NestedMultiOutput(codec), args); + } + /** * Assert that a string is not empty, it must not be {@code null} and it must not be empty. * @@ -1653,7 +1807,7 @@ protected static void assertNotEmpty(String string, String message) { /** * Assert that an object is not {@code null} . - * + * * @param object the object to check * @param message the exception message to use if the assertion fails * @throws IllegalArgumentException if the object is {@code null} @@ -1666,7 +1820,7 @@ public static void assertNotNull(Object object, String message) { /** * Assert that an array has elements; that is, it must not be {@code null} and must have at least one element. - * + * * @param array the array to check * @param message the exception message to use if the assertion fails * @throws IllegalArgumentException if the object array is {@code null} or has no elements @@ -1692,7 +1846,7 @@ protected static void assertNotEmpty(int[] array, String message) { /** * Assert that an array has no null elements. - * + * * @param array the array to check * @param message the exception message to use if the assertion fails * @throws IllegalArgumentException if the object array contains a {@code null} element @@ -1709,7 +1863,7 @@ public static void assertNoNullElements(Object[] array, String message) { /** * Assert that {@code value} is {@literal true}. - * + * * @param value the value to check * @param message the exception message to use if the assertion fails * @throws IllegalArgumentException if the object array contains a {@code null} element diff --git a/src/main/java/com/lambdaworks/redis/RedisConnection.java b/src/main/java/com/lambdaworks/redis/RedisConnection.java index a8dc7d856c..670c8af4a0 100644 --- a/src/main/java/com/lambdaworks/redis/RedisConnection.java +++ b/src/main/java/com/lambdaworks/redis/RedisConnection.java @@ -13,8 +13,8 @@ */ public interface RedisConnection extends RedisHashesConnection, RedisKeysConnection, RedisStringsConnection, RedisListsConnection, RedisSetsConnection, RedisSortedSetsConnection, - RedisScriptingConnection, RedisServerConnection, RedisHLLConnection, BaseRedisConnection, - RedisClusterConnection { + RedisScriptingConnection, RedisServerConnection, RedisHLLConnection, RedisGeoConnection, + BaseRedisConnection, RedisClusterConnection { /** * Set the default timeout for operations. diff --git a/src/main/java/com/lambdaworks/redis/RedisGeoAsyncConnection.java b/src/main/java/com/lambdaworks/redis/RedisGeoAsyncConnection.java new file mode 100644 index 0000000000..aa78fb7aea --- /dev/null +++ b/src/main/java/com/lambdaworks/redis/RedisGeoAsyncConnection.java @@ -0,0 +1,114 @@ +package com.lambdaworks.redis; + +import java.util.List; +import java.util.Set; + +/** + * Synchronous executed commands for Geo-Commands. + * + * @author Mark Paluch + * @since 3.3 + */ +public interface RedisGeoAsyncConnection { + + /** + * Single geo add. + * + * @param key + * @param latitude + * @param longitude + * @param member + * @return 1 if member is new or 0 if member is updated + */ + RedisFuture geoadd(K key, double latitude, double longitude, V member); + + /** + * Multi geo add + * + * @param key + * @param latLonMember triplets of double latitude, double longitude and V member + * @return count of members submitted + */ + RedisFuture geoadd(K key, Object... latLonMember); + + /** + * Retrieve members selected by distance with the center of {@code latitude} and {@code longitude}. + * + * @param key + * @param latitude + * @param longitude + * @param distance + * @param unit + * @return + */ + RedisFuture> georadius(K key, double latitude, double longitude, double distance, GeoArgs.Unit unit); + + /** + * Retrieve members selected by distance with the center of {@code latitude} and {@code longitude}. + * + * @param key + * @param latitude + * @param longitude + * @param distance + * @param unit + * @return + */ + RedisFuture> georadius(K key, double latitude, double longitude, double distance, GeoArgs.Unit unit, + GeoArgs geoArgs); + + /** + * Retrieve members selected by distance with the center of {@code member}. + * + * @param key + * @param member + * @param distance + * @param unit + * @return + */ + RedisFuture> georadiusbymember(K key, V member, double distance, GeoArgs.Unit unit); + + /** + * + * Retrieve members selected by distance with the center of {@code member}. + * + * @param key + * @param member + * @param distance + * @param unit + * @return + */ + RedisFuture> georadiusbymember(K key, V member, double distance, GeoArgs.Unit unit, GeoArgs geoArgs); + + /** + * + * Encode latitude and longitude to highest geohash accuracy. + * + * @param latitude + * @param longitude + * @return nested multi-bulk reply with 1: the 52-bit geohash integer for your latitude longitude, 2: The minimum corner of + * your geohash, 3: The maximum corner of your geohash, 4: The averaged center of your geohash. + */ + RedisFuture> geoencode(double latitude, double longitude); + + /** + * + * Encode latitude and longitude to highest geohash accuracy. + * + * @param latitude + * @param longitude + * @param distance + * @param unit + * @return nested multi-bulk reply with 1: the 52-bit geohash integer for your latitude longitude, 2: The minimum corner of + * your geohash, 3: The maximum corner of your geohash, 4: The averaged center of your geohash. + */ + RedisFuture> geoencode(double latitude, double longitude, double distance, GeoArgs.Unit unit); + + /** + * + * Decode geohash. + * + * @param geohash + * @return nested multi-bulk with 1: minimum decoded corner, 2: maximum decoded corner, 3: averaged center of bounding box. + */ + RedisFuture> geodecode(long geohash); +} diff --git a/src/main/java/com/lambdaworks/redis/RedisGeoConnection.java b/src/main/java/com/lambdaworks/redis/RedisGeoConnection.java new file mode 100644 index 0000000000..d1d44cb3cc --- /dev/null +++ b/src/main/java/com/lambdaworks/redis/RedisGeoConnection.java @@ -0,0 +1,113 @@ +package com.lambdaworks.redis; + +import java.util.List; +import java.util.Set; + +/** + * Synchronous executed commands for Geo-Commands. + * + * @author Mark Paluch + * @since 3.3 + */ +public interface RedisGeoConnection { + + /** + * Single geo add. + * + * @param key + * @param latitude + * @param longitude + * @param member + * @return + */ + Long geoadd(K key, double latitude, double longitude, V member); + + /** + * Multi geo add + * + * @param key + * @param latLonMember triplets of double latitude, double longitude and V member + * @return + */ + Long geoadd(K key, Object... latLonMember); + + /** + * Retrieve members selected by distance with the center of {@code latitude} and {@code longitude}. + * + * @param key + * @param latitude + * @param longitude + * @param distance + * @param unit + * @return + */ + Set georadius(K key, double latitude, double longitude, double distance, GeoArgs.Unit unit); + + /** + * Retrieve members selected by distance with the center of {@code latitude} and {@code longitude}. + * + * @param key + * @param latitude + * @param longitude + * @param distance + * @param unit + * @return + */ + List georadius(K key, double latitude, double longitude, double distance, GeoArgs.Unit unit, GeoArgs geoArgs); + + /** + * Retrieve members selected by distance with the center of {@code member}. + * + * @param key + * @param member + * @param distance + * @param unit + * @return + */ + Set georadiusbymember(K key, V member, double distance, GeoArgs.Unit unit); + + /** + * + * Retrieve members selected by distance with the center of {@code member}. + * + * @param key + * @param member + * @param distance + * @param unit + * @return + */ + List georadiusbymember(K key, V member, double distance, GeoArgs.Unit unit, GeoArgs geoArgs); + + /** + * + * Encode latitude and longitude to highest geohash accuracy. + * + * @param latitude + * @param longitude + * @return nested multi-bulk reply with 1: the 52-bit geohash integer for your latitude longitude, 2: The minimum corner of + * your geohash, 3: The maximum corner of your geohash, 4: The averaged center of your geohash. + */ + List geoencode(double latitude, double longitude); + + /** + * + * Encode latitude and longitude to highest geohash accuracy. + * + * @param latitude + * @param longitude + * @param distance + * @param unit + * @return nested multi-bulk reply with 1: the 52-bit geohash integer for your latitude longitude, 2: The minimum corner of + * your geohash, 3: The maximum corner of your geohash, 4: The averaged center of your geohash. + */ + List geoencode(double latitude, double longitude, double distance, GeoArgs.Unit unit); + + /** + * + * Decode geohash. + * + * @param geohash + * @return nested multi-bulk with 1: minimum decoded corner, 2: maximum decoded corner, 3: averaged center of bounding box. + */ + List geodecode(long geohash); +} diff --git a/src/main/java/com/lambdaworks/redis/protocol/CommandType.java b/src/main/java/com/lambdaworks/redis/protocol/CommandType.java index e4c63e145f..e4af9b2c9a 100644 --- a/src/main/java/com/lambdaworks/redis/protocol/CommandType.java +++ b/src/main/java/com/lambdaworks/redis/protocol/CommandType.java @@ -64,6 +64,9 @@ public enum CommandType implements ProtocolKeyword { BITCOUNT, BITOP, GETBIT, SETBIT, BITPOS, + // Geo + GEOADD, GEORADIUS, GEORADIUSBYMEMBER, GEOENCODE, GEODECODE, + // Others TIME, WAIT, diff --git a/src/test/java/com/lambdaworks/redis/GeoCommandTest.java b/src/test/java/com/lambdaworks/redis/GeoCommandTest.java new file mode 100644 index 0000000000..1625a12d93 --- /dev/null +++ b/src/test/java/com/lambdaworks/redis/GeoCommandTest.java @@ -0,0 +1,146 @@ +package com.lambdaworks.redis; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.List; +import java.util.Set; + +import org.junit.Ignore; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +public class GeoCommandTest extends AbstractCommandTest { + + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + @Test + public void geoadd() throws Exception { + + Long result = redis.geoadd(key, 40.747533, -73.9454966, "lic market"); + assertThat(result).isEqualTo(1); + + Long readd = redis.geoadd(key, 40.747533, -73.9454966, "lic market"); + assertThat(readd).isEqualTo(0); + } + + @Test + public void geoaddMulti() throws Exception { + + Long result = redis.geoadd(key, 49.5282537, 8.6638775, "Weinheim", 48.9978127, 8.3796281, "EFS9", 49.553302, 8.665351, + "Bahn"); + + assertThat(result).isEqualTo(3); + } + + @Test(expected = IllegalArgumentException.class) + public void geoaddMultiWrongArgument() throws Exception { + redis.geoadd(key, 49.528253); + } + + protected void prepareGeo() { + redis.geoadd(key, 49.5282537, 8.6638775, "Weinheim", 48.9978127, 8.3796281, "EFS9", 49.553302, 8.665351, "Bahn"); + } + + @Test + public void georadius() throws Exception { + + prepareGeo(); + + Set georadius = redis.georadius(key, 49.5285695, 8.6582861, 1, GeoArgs.Unit.kilometer); + assertThat(georadius).hasSize(1).contains("Weinheim"); + + Set largerGeoradius = redis.georadius(key, 49.5285695, 8.6582861, 5, GeoArgs.Unit.kilometer); + assertThat(largerGeoradius).hasSize(2).contains("Weinheim").contains("Bahn"); + + } + + @Test + public void georadiusWithArgs() throws Exception { + + prepareGeo(); + + GeoArgs geoArgs = new GeoArgs().withHash().withCoordinates().withDistance().noProperties().asc(); + + List result = redis.georadius(key, 49.5285695, 8.6582861, 1, GeoArgs.Unit.kilometer, geoArgs); + assertThat(result).hasSize(1); + + List response = (List) result.get(0); + assertThat(response).hasSize(4); + + result = redis.georadius(key, 49.5285695, 8.6582861, 1, GeoArgs.Unit.kilometer, null); + assertThat(result).hasSize(1); + } + + @Test + public void georadiusbymember() throws Exception { + + // looks like a bug in redis + expectedException.expect(RedisCommandExecutionException.class); + expectedException.expectMessage("ERR could not decode requested zset member"); + + prepareGeo(); + + Set empty = redis.georadiusbymember(key, "Bahn", 1, GeoArgs.Unit.kilometer); + assertThat(empty).isEmpty(); + + Set georadiusbymember = redis.georadiusbymember(key, "Bahn", 5, GeoArgs.Unit.kilometer); + assertThat(georadiusbymember).hasSize(1).contains("Weinheim"); + } + + @Test + @Ignore("ERR could not decode requested zset member") + public void georadiusbymemberWithArgs() throws Exception { + + prepareGeo(); + + GeoArgs geoArgs = new GeoArgs().withHash().withCoordinates().withDistance().desc(); + + List empty = redis.georadiusbymember(key, "Bahn", 1, GeoArgs.Unit.kilometer, geoArgs); + assertThat(empty).isNotEmpty(); + + List georadiusbymember = redis.georadiusbymember(key, "Bahn", 5, GeoArgs.Unit.kilometer, geoArgs); + assertThat(georadiusbymember).hasSize(1); + + List response = (List) georadiusbymember.get(0); + assertThat(response).hasSize(4); + } + + @Test + public void geoencode() throws Exception { + + List geoencode = redis.geoencode(49.5282537, 8.6638775); + + assertThat(geoencode).hasSize(4); + assertThat(geoencode.get(0)).isEqualTo(3666615932941099L); + assertThat(geoencode.get(1)).isInstanceOf(List.class); + assertThat(geoencode.get(2)).isInstanceOf(List.class); + assertThat(geoencode.get(3)).isInstanceOf(List.class); + + } + + @Test + public void geoencodeWithDistance() throws Exception { + + List result = redis.geoencode(49.5282537, 8.6638775, 1, GeoArgs.Unit.kilometer); + + assertThat(result).hasSize(4); + assertThat(result.get(0)).isEqualTo(3666615929405440L); + assertThat(result.get(1)).isInstanceOf(List.class); + assertThat(result.get(2)).isInstanceOf(List.class); + assertThat(result.get(3)).isInstanceOf(List.class); + } + + @Test + public void geodecode() throws Exception { + + List result = redis.geodecode(3666615932941099L); + + assertThat(result).hasSize(3); + assertThat(result.get(0)).isInstanceOf(List.class); + assertThat(result.get(1)).isInstanceOf(List.class); + assertThat(result.get(2)).isInstanceOf(List.class); + + } +}