diff --git a/Makefile b/Makefile index 809c2f2f18..a2e5138e91 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 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 + [ ! -e work/redis-git ] && git clone https://github.com/antirez/redis.git --branch unstable --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 unstable || 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 index 0fda2de987..b751843dd3 100644 --- a/src/main/java/com/lambdaworks/redis/GeoArgs.java +++ b/src/main/java/com/lambdaworks/redis/GeoArgs.java @@ -1,5 +1,7 @@ package com.lambdaworks.redis; +import static com.google.common.base.Preconditions.checkArgument; + import com.lambdaworks.redis.protocol.CommandArgs; import com.lambdaworks.redis.protocol.CommandKeyword; @@ -16,30 +18,76 @@ public class GeoArgs { private Long count; private Sort sort = Sort.none; + /** + * Request distance for results. + * + * @return {@code this} + */ public GeoArgs withDistance() { withdistance = true; return this; } + /** + * Request coordinates for results. + * + * @return {@code this} + */ public GeoArgs withCoordinates() { withcoordinates = true; return this; } + /** + * Request geohash for results. + * + * @return {@code this} + */ public GeoArgs withHash() { withhash = true; return this; } + /** + * Limit results to {@code count} entries. + * + * @param count number greater 0 + * @return {@code this} + */ public GeoArgs withCount(long count) { + checkArgument(count > 0, "count must be greater 0"); this.count = count; return this; } + /** + * + * @return {@literal true} if distance is requested. + */ + public boolean isWithDistance() { + return withdistance; + } + + /** + * + * @return {@literal true} if coordinates are requested. + */ + public boolean isWithCoordinates() { + return withcoordinates; + } + + /** + * + * @return {@literal true} if geohash is requested. + */ + public boolean isWithHash() { + return withhash; + } + /** * Sort results ascending. * - * @return the current geo args. + * @return {@code this} */ public GeoArgs asc() { return sort(Sort.asc); @@ -48,7 +96,7 @@ public GeoArgs asc() { /** * Sort results descending. * - * @return the current geo args. + * @return {@code this} */ public GeoArgs desc() { return sort(Sort.desc); @@ -57,10 +105,12 @@ public GeoArgs desc() { /** * Sort results. * - * @param sort - * @return the current geo args. + * @param sort sort order, must not be {@literal null} + * @return {@code this} */ public GeoArgs sort(Sort sort) { + checkArgument(sort != null, "sort must not be null"); + this.sort = sort; return this; } @@ -69,7 +119,20 @@ public GeoArgs sort(Sort sort) { * Sort order. */ public enum Sort { - asc, desc, none; + /** + * ascending. + */ + asc, + + /** + * descending. + */ + desc, + + /** + * no sort order. + */ + none; } /** @@ -95,6 +158,7 @@ public enum Unit { mi; } + public void build(CommandArgs args) { if (withdistance) { args.add("withdist"); diff --git a/src/main/java/com/lambdaworks/redis/GeoCoordinates.java b/src/main/java/com/lambdaworks/redis/GeoCoordinates.java new file mode 100644 index 0000000000..ef3971de85 --- /dev/null +++ b/src/main/java/com/lambdaworks/redis/GeoCoordinates.java @@ -0,0 +1,45 @@ +package com.lambdaworks.redis; + +/** + * A tuple consisting of numerical geo data points to describe geo coordinates. + * + * @author Mark Paluch + */ +public class GeoCoordinates { + + public final Number x; + public final Number y; + + public GeoCoordinates(Number x, Number y) { + + this.x = x; + this.y = y; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (!(o instanceof GeoCoordinates)) + return false; + + GeoCoordinates geoCoords = (GeoCoordinates) o; + + if (x != null ? !x.equals(geoCoords.x) : geoCoords.x != null) + return false; + return !(y != null ? !y.equals(geoCoords.y) : geoCoords.y != null); + } + + @Override + public int hashCode() { + int result = x != null ? x.hashCode() : 0; + result = 31 * result + (y != null ? y.hashCode() : 0); + return result; + } + + @Override + public String toString() { + return String.format("(%f, %f)", x, y); + } + +} diff --git a/src/main/java/com/lambdaworks/redis/GeoEncoded.java b/src/main/java/com/lambdaworks/redis/GeoEncoded.java new file mode 100644 index 0000000000..ad9e7f0cb7 --- /dev/null +++ b/src/main/java/com/lambdaworks/redis/GeoEncoded.java @@ -0,0 +1,67 @@ +package com.lambdaworks.redis; + +/** + * Geo-encoded object. Contains: + * + * + * + * @author Mark Paluch + */ +public class GeoEncoded { + + public final long geohash; + public final GeoCoordinates min; + public final GeoCoordinates max; + public final GeoCoordinates avg; + + public GeoEncoded(long geohash, GeoCoordinates min, GeoCoordinates max, GeoCoordinates avg) { + this.geohash = geohash; + this.min = min; + this.max = max; + this.avg = avg; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (!(o instanceof GeoEncoded)) + return false; + + GeoEncoded that = (GeoEncoded) o; + + if (geohash != that.geohash) + return false; + if (min != null ? !min.equals(that.min) : that.min != null) + return false; + if (max != null ? !max.equals(that.max) : that.max != null) + return false; + return !(avg != null ? !avg.equals(that.avg) : that.avg != null); + } + + @Override + public int hashCode() { + int result = (int) (geohash ^ (geohash >>> 32)); + result = 31 * result + (min != null ? min.hashCode() : 0); + result = 31 * result + (max != null ? max.hashCode() : 0); + result = 31 * result + (avg != null ? avg.hashCode() : 0); + return result; + } + + @Override + public String toString() { + final StringBuffer sb = new StringBuffer(); + sb.append(getClass().getSimpleName()); + sb.append(" [geohash=").append(geohash); + sb.append(", min=").append(min); + sb.append(", max=").append(max); + sb.append(", avg=").append(avg); + sb.append(']'); + return sb.toString(); + } +} diff --git a/src/main/java/com/lambdaworks/redis/GeoTuple.java b/src/main/java/com/lambdaworks/redis/GeoTuple.java deleted file mode 100644 index 2cba6549cb..0000000000 --- a/src/main/java/com/lambdaworks/redis/GeoTuple.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.lambdaworks.redis; - -/** - * A tuple consisting of two numerical geo data points. - * - * @author Mark Paluch - */ -public class GeoTuple { - - private Number x; - private Number y; - - public GeoTuple() { - } - - public Number getX() { - return x; - } - - public void setX(Number x) { - this.x = x; - } - - public Number getY() { - return y; - } - - public void setY(Number y) { - this.y = y; - } -} diff --git a/src/main/java/com/lambdaworks/redis/GeoWithin.java b/src/main/java/com/lambdaworks/redis/GeoWithin.java new file mode 100644 index 0000000000..ecbb0cc378 --- /dev/null +++ b/src/main/java/com/lambdaworks/redis/GeoWithin.java @@ -0,0 +1,67 @@ +package com.lambdaworks.redis; + +/** + * Geo element within a certain radius. Contains: + * + * + * @param Value type. + * @author Mark Paluch + */ +public class GeoWithin { + + public final V member; + public final Double distance; + public final Long geohash; + public final GeoCoordinates coordinates; + + public GeoWithin(V member, Double distance, Long geohash, GeoCoordinates coordinates) { + this.member = member; + this.distance = distance; + this.geohash = geohash; + this.coordinates = coordinates; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (!(o instanceof GeoWithin)) + return false; + + GeoWithin geoWithin = (GeoWithin) o; + + if (member != null ? !member.equals(geoWithin.member) : geoWithin.member != null) + return false; + if (distance != null ? !distance.equals(geoWithin.distance) : geoWithin.distance != null) + return false; + if (geohash != null ? !geohash.equals(geoWithin.geohash) : geoWithin.geohash != null) + return false; + return !(coordinates != null ? !coordinates.equals(geoWithin.coordinates) : geoWithin.coordinates != null); + } + + @Override + public int hashCode() { + int result = member != null ? member.hashCode() : 0; + result = 31 * result + (distance != null ? distance.hashCode() : 0); + result = 31 * result + (geohash != null ? geohash.hashCode() : 0); + result = 31 * result + (coordinates != null ? coordinates.hashCode() : 0); + return result; + } + + @Override + public String toString() { + final StringBuffer sb = new StringBuffer(); + sb.append(getClass().getSimpleName()); + sb.append(" [member=").append(member); + sb.append(", distance=").append(distance); + sb.append(", geohash=").append(geohash); + sb.append(", coordinates=").append(coordinates); + sb.append(']'); + return sb.toString(); + } +} diff --git a/src/main/java/com/lambdaworks/redis/RedisAsyncConnectionImpl.java b/src/main/java/com/lambdaworks/redis/RedisAsyncConnectionImpl.java index 9a75940d6f..973a085bf6 100644 --- a/src/main/java/com/lambdaworks/redis/RedisAsyncConnectionImpl.java +++ b/src/main/java/com/lambdaworks/redis/RedisAsyncConnectionImpl.java @@ -1616,8 +1616,8 @@ public RedisFuture> georadius(K key, double longitude, double latitude, d } @Override - public RedisFuture> georadius(K key, double longitude, double latitude, double distance, GeoArgs.Unit unit, - GeoArgs geoArgs) { + public RedisFuture>> georadius(K key, double longitude, double latitude, double distance, + GeoArgs.Unit unit, GeoArgs geoArgs) { return dispatch(commandBuilder.georadius(key, longitude, latitude, distance, unit.name(), geoArgs)); } @@ -1627,12 +1627,13 @@ public RedisFuture> georadiusbymember(K key, V member, double distance, G } @Override - public RedisFuture> georadiusbymember(K key, V member, double distance, GeoArgs.Unit unit, GeoArgs geoArgs) { + 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> geopos(K key, V... members) { + public RedisFuture> geopos(K key, V... members) { return dispatch(commandBuilder.geopos(key, members)); } @@ -1642,17 +1643,17 @@ public RedisFuture geodist(K key, V from, V to, GeoArgs.Unit unit) { } @Override - public RedisFuture> geoencode(double longitude, double latitude) { + public RedisFuture geoencode(double longitude, double latitude) { return dispatch(commandBuilder.geoencode(longitude, latitude, null, null)); } @Override - public RedisFuture> geoencode(double longitude, double latitude, double distance, GeoArgs.Unit unit) { + public RedisFuture geoencode(double longitude, double latitude, double distance, GeoArgs.Unit unit) { return dispatch(commandBuilder.geoencode(longitude, latitude, distance, unit.name())); } @Override - public RedisFuture> geodecode(long geohash) { + public RedisFuture geodecode(long geohash) { return dispatch(commandBuilder.geodecode(geohash)); } diff --git a/src/main/java/com/lambdaworks/redis/RedisCommandBuilder.java b/src/main/java/com/lambdaworks/redis/RedisCommandBuilder.java index 3fa809e08c..501ab15764 100644 --- a/src/main/java/com/lambdaworks/redis/RedisCommandBuilder.java +++ b/src/main/java/com/lambdaworks/redis/RedisCommandBuilder.java @@ -62,7 +62,9 @@ import com.lambdaworks.redis.output.ByteArrayOutput; import com.lambdaworks.redis.output.DateOutput; import com.lambdaworks.redis.output.DoubleOutput; -import com.lambdaworks.redis.output.NumericAndGeoTupleListOutput; +import com.lambdaworks.redis.output.GeoCoordinatesListOutput; +import com.lambdaworks.redis.output.GeoEncodedOutput; +import com.lambdaworks.redis.output.GeoWithinListOutput; import com.lambdaworks.redis.output.IntegerOutput; import com.lambdaworks.redis.output.KeyListOutput; import com.lambdaworks.redis.output.KeyOutput; @@ -1751,16 +1753,16 @@ public Command> georadius(K key, double longitude, double latitude, return createCommand(GEORADIUS, new ValueSetOutput(codec), args); } - public Command> georadius(K key, double longitude, double latitude, double distance, String unit, + public Command>> georadius(K key, double longitude, double latitude, double distance, String unit, GeoArgs geoArgs) { + assertNotNull(geoArgs, "geoArgs must not be null"); CommandArgs args = new CommandArgs(codec).addKey(key).add(longitude).add(latitude).add(distance).add(unit); - if (geoArgs != null) { - geoArgs.build(args); - } + geoArgs.build(args); - return createCommand(GEORADIUS, new NestedMultiOutput(codec), args); + return createCommand(GEORADIUS, new GeoWithinListOutput(codec, geoArgs.isWithDistance(), geoArgs.isWithHash(), + geoArgs.isWithCoordinates()), args); } public Command> georadiusbymember(K key, V member, double distance, String unit) { @@ -1768,21 +1770,21 @@ public Command> georadiusbymember(K key, V member, double distance, return createCommand(GEORADIUSBYMEMBER, new ValueSetOutput(codec), args); } - public Command> georadiusbymember(K key, V member, double distance, String unit, GeoArgs geoArgs) { + 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); + assertNotNull(geoArgs, "geoArgs must not be null"); - if (geoArgs != null) { - geoArgs.build(args); - } - - return createCommand(GEORADIUSBYMEMBER, new NestedMultiOutput(codec), args); + return createCommand( + GEORADIUSBYMEMBER, + new GeoWithinListOutput(codec, geoArgs.isWithDistance(), geoArgs.isWithHash(), geoArgs + .isWithCoordinates()), args); } @SuppressWarnings({ "unchecked", "rawtypes" }) - public Command> geopos(K key, V[] members) { + public Command> geopos(K key, V[] members) { CommandArgs args = new CommandArgs(codec).addKey(key).addValues(members); - return (Command) createCommand(GEOPOS, new NumericAndGeoTupleListOutput(codec), args); + return (Command) createCommand(GEOPOS, new GeoCoordinatesListOutput(codec), args); } public Command geodist(K key, V from, V to, GeoArgs.Unit unit) { @@ -1797,21 +1799,21 @@ public Command geodist(K key, V from, V to, GeoArgs.Unit unit) { } @SuppressWarnings({ "unchecked", "rawtypes" }) - public Command> geoencode(double longitude, double latitude, Double distance, String unit) { + public Command geoencode(double longitude, double latitude, Double distance, String unit) { CommandArgs args = new CommandArgs(codec).add(longitude).add(latitude); if (distance != null && unit != null) { args.add(distance).add(unit); } - return (Command) createCommand(GEOENCODE, new NumericAndGeoTupleListOutput(codec), args); + return (Command) createCommand(GEOENCODE, new GeoEncodedOutput(codec, null), args); } @SuppressWarnings({ "unchecked", "rawtypes" }) - public Command> geodecode(long geohash) { + public Command geodecode(long geohash) { CommandArgs args = new CommandArgs(codec).add(geohash); - return (Command) createCommand(GEODECODE, new NumericAndGeoTupleListOutput(codec), args); + return (Command) createCommand(GEODECODE, new GeoEncodedOutput(codec, geohash), args); } /** diff --git a/src/main/java/com/lambdaworks/redis/RedisGeoAsyncConnection.java b/src/main/java/com/lambdaworks/redis/RedisGeoAsyncConnection.java index 36a07318ce..96f2dbf843 100644 --- a/src/main/java/com/lambdaworks/redis/RedisGeoAsyncConnection.java +++ b/src/main/java/com/lambdaworks/redis/RedisGeoAsyncConnection.java @@ -14,10 +14,10 @@ public interface RedisGeoAsyncConnection { /** * Single geo add. * - * @param key - * @param longitude - * @param latitude - * @param member + * @param key the key of the geo set + * @param longitude the longitude coordinate according to WGS84 + * @param latitude the latitude coordinate according to WGS84 + * @param member the member to add * @return Long integer-reply the number of elements that were added to the set */ RedisFuture geoadd(K key, double longitude, double latitude, V member); @@ -25,7 +25,7 @@ public interface RedisGeoAsyncConnection { /** * Multi geo add. * - * @param key + * @param key the key of the geo set * @param lngLatMember triplets of double longitude, double latitude and V member * @return Long integer-reply the number of elements that were added to the set */ @@ -34,11 +34,11 @@ public interface RedisGeoAsyncConnection { /** * Retrieve members selected by distance with the center of {@code longitude} and {@code latitude}. * - * @param key - * @param longitude - * @param latitude - * @param distance - * @param unit + * @param key the key of the geo set + * @param longitude the longitude coordinate according to WGS84 + * @param latitude the latitude coordinate according to WGS84 + * @param distance radius distance + * @param unit distance unit * @return bulk reply */ RedisFuture> georadius(K key, double longitude, double latitude, double distance, GeoArgs.Unit unit); @@ -46,58 +46,63 @@ public interface RedisGeoAsyncConnection { /** * Retrieve members selected by distance with the center of {@code longitude} and {@code latitude}. * - * @param key - * @param longitude - * @param latitude - * @param distance - * @param unit - * @return nested multi-bulk reply + * @param key the key of the geo set + * @param longitude the longitude coordinate according to WGS84 + * @param latitude the latitude coordinate according to WGS84 + * @param distance radius distance + * @param unit distance unit + * @param geoArgs args to control the result + * @return nested multi-bulk reply. The {@link GeoWithin} contains only fields which were requested by {@link GeoArgs} */ - RedisFuture> georadius(K key, double longitude, double latitude, double distance, GeoArgs.Unit unit, + RedisFuture>> georadius(K key, double longitude, double latitude, double distance, GeoArgs.Unit unit, GeoArgs geoArgs); /** - * Retrieve members selected by distance with the center of {@code member}. + * Retrieve members selected by distance with the center of {@code member}. The member itself is always contained in the + * results. * - * @param key - * @param member - * @param distance - * @param unit - * @return bulk reply + * @param key the key of the geo set + * @param member reference member + * @param distance radius distance + * @param unit distance unit + * @return set of members */ RedisFuture> georadiusbymember(K key, V member, double distance, GeoArgs.Unit unit); /** * - * Retrieve members selected by distance with the center of {@code member}. + * Retrieve members selected by distance with the center of {@code member}. The member itself is always contained in the + * results. * - * @param key - * @param member - * @param distance - * @param unit - * @return nested multi-bulk reply + * @param key the key of the geo set + * @param member reference member + * @param distance radius distance + * @param unit distance unit + * @param geoArgs args to control the result + * @return nested multi-bulk reply. The {@link GeoWithin} contains only fields which were requested by {@link GeoArgs} */ - RedisFuture> georadiusbymember(K key, V member, double distance, GeoArgs.Unit unit, GeoArgs geoArgs); + RedisFuture>> georadiusbymember(K key, V member, double distance, GeoArgs.Unit unit, GeoArgs geoArgs); /** * Get geo coordinates for the {@code members}. * - * @param key - * @param members + * @param key the key of the geo set + * @param members the members * - * @return a list of {@link GeoTuple}s representing the x,y position of each element specified in the arguments. For missing - * elements {@literal null} is returned. + * @return a list of {@link GeoCoordinates}s representing the x,y position of each element specified in the arguments. For + * missing elements {@literal null} is returned. */ - RedisFuture> geopos(K key, V... members); + RedisFuture> geopos(K key, V... members); /** * * Retrieve distance between points {@code from} and {@code to}. If one or more elements are missing {@literal null} is * returned. Default in meters by , otherwise according to {@code unit} * - * @param key - * @param from - * @param to + * @param key the key of the geo set + * @param from from member + * @param to to member + * @param unit distance unit * * @return distance between points {@code from} and {@code to}. If one or more elements are missing {@literal null} is * returned. @@ -108,36 +113,35 @@ RedisFuture> georadius(K key, double longitude, double latitude, do * * Encode {@code longitude} and {@code latitude} to highest geohash accuracy. * - * @param longitude - * @param latitude - * @return multi-bulk reply with 4 elements 1: the 52-bit geohash integer for your latitude longitude, 2: The minimum corner - * of your geohash {@link GeoTuple}, 3: The maximum corner of your geohash {@link GeoTuple}, 4: The averaged center - * of your geohash {@link GeoTuple}. + * @param longitude the longitude coordinate according to WGS84 + * @param latitude the latitude coordinate according to WGS84 + * @return multi-bulk reply with 4 elements 1: the 52-bit geohash integer for your longitude/latitude, 2: The minimum corner + * of your geohash {@link GeoCoordinates}, 3: The maximum corner of your geohash {@link GeoCoordinates}, 4: The + * averaged center of your geohash {@link GeoCoordinates}. */ - RedisFuture> geoencode(double longitude, double latitude); + RedisFuture geoencode(double longitude, double latitude); /** * * Encode {@code longitude} and {@code latitude} to highest geohash accuracy. * - * @param longitude - * @param latitude - * @param distance - * @param unit - * @return multi-bulk reply with four components 1: the 52-bit geohash integer for your latitude longitude, 2: The minimum - * corner of your geohash {@link GeoTuple}, 3: The maximum corner of your geohash {@link GeoTuple}, 4: The averaged - * center of your geohash {@link GeoTuple}. + * @param longitude the longitude coordinate according to WGS84 + * @param latitude the latitude coordinate according to WGS84 + * @param distance distance for geohash accuracy + * @param unit the distance unit + * @return multi-bulk reply with four components 1: the 52-bit geohash integer for your longitude/latitude, 2: The minimum + * corner of your geohash {@link GeoCoordinates}, 3: The maximum corner of your geohash {@link GeoCoordinates}, 4: + * The averaged center of your geohash {@link GeoCoordinates}. */ - RedisFuture> geoencode(double longitude, double latitude, double distance, GeoArgs.Unit unit); + RedisFuture geoencode(double longitude, double latitude, double distance, GeoArgs.Unit unit); /** * * Decode geohash. * - * @param geohash - * @return a list of {@link GeoTuple}s (nested multi-bulk) with 3 elements 1: minimum decoded corner, 2: maximum decoded - * corner, 3: averaged center of bounding box. + * @param geohash geohash containing your longitude/latitude + * @return a list of {@link GeoCoordinates}s (nested multi-bulk) with 3 elements 1: minimum decoded corner, 2: maximum + * decoded corner, 3: averaged center of bounding box. */ - RedisFuture> geodecode(long geohash); - + RedisFuture geodecode(long geohash); } diff --git a/src/main/java/com/lambdaworks/redis/RedisGeoConnection.java b/src/main/java/com/lambdaworks/redis/RedisGeoConnection.java index f8d852d292..f63c7726db 100644 --- a/src/main/java/com/lambdaworks/redis/RedisGeoConnection.java +++ b/src/main/java/com/lambdaworks/redis/RedisGeoConnection.java @@ -14,10 +14,10 @@ public interface RedisGeoConnection { /** * Single geo add. * - * @param key - * @param longitude - * @param latitude - * @param member + * @param key the key of the geo set + * @param longitude the longitude coordinate according to WGS84 + * @param latitude the latitude coordinate according to WGS84 + * @param member the member to add * @return Long integer-reply the number of elements that were added to the set */ Long geoadd(K key, double longitude, double latitude, V member); @@ -25,7 +25,7 @@ public interface RedisGeoConnection { /** * Multi geo add. * - * @param key + * @param key the key of the geo set * @param lngLatMember triplets of double longitude, double latitude and V member * @return Long integer-reply the number of elements that were added to the set */ @@ -34,11 +34,11 @@ public interface RedisGeoConnection { /** * Retrieve members selected by distance with the center of {@code longitude} and {@code latitude}. * - * @param key - * @param longitude - * @param latitude - * @param distance - * @param unit + * @param key the key of the geo set + * @param longitude the longitude coordinate according to WGS84 + * @param latitude the latitude coordinate according to WGS84 + * @param distance radius distance + * @param unit distance unit * @return bulk reply */ Set georadius(K key, double longitude, double latitude, double distance, GeoArgs.Unit unit); @@ -46,57 +46,62 @@ public interface RedisGeoConnection { /** * Retrieve members selected by distance with the center of {@code longitude} and {@code latitude}. * - * @param key - * @param longitude - * @param latitude - * @param distance - * @param unit - * @return nested multi-bulk reply + * @param key the key of the geo set + * @param longitude the longitude coordinate according to WGS84 + * @param latitude the latitude coordinate according to WGS84 + * @param distance radius distance + * @param unit distance unit + * @param geoArgs args to control the result + * @return nested multi-bulk reply. The {@link GeoWithin} contains only fields which were requested by {@link GeoArgs} */ - List georadius(K key, double longitude, double latitude, double distance, GeoArgs.Unit unit, GeoArgs geoArgs); + List> georadius(K key, double longitude, double latitude, double distance, GeoArgs.Unit unit, GeoArgs geoArgs); /** - * Retrieve members selected by distance with the center of {@code member}. + * Retrieve members selected by distance with the center of {@code member}. The member itself is always contained in the + * results. * - * @param key - * @param member - * @param distance - * @param unit - * @return bulk reply + * @param key the key of the geo set + * @param member reference member + * @param distance radius distance + * @param unit distance unit + * @return set of members */ Set georadiusbymember(K key, V member, double distance, GeoArgs.Unit unit); /** * - * Retrieve members selected by distance with the center of {@code member}. + * Retrieve members selected by distance with the center of {@code member}. The member itself is always contained in the + * results. * - * @param key - * @param member - * @param distance - * @param unit - * @return nested multi-bulk reply + * @param key the key of the geo set + * @param member reference member + * @param distance radius distance + * @param unit distance unit + * @param geoArgs args to control the result + * @return nested multi-bulk reply. The {@link GeoWithin} contains only fields which were requested by {@link GeoArgs} */ - List georadiusbymember(K key, V member, double distance, GeoArgs.Unit unit, GeoArgs geoArgs); + List> georadiusbymember(K key, V member, double distance, GeoArgs.Unit unit, GeoArgs geoArgs); /** * Get geo coordinates for the {@code members}. * - * @param key - * @param members + * @param key the key of the geo set + * @param members the members * - * @return a list of {@link GeoTuple}s representing the x,y position of each element specified in the arguments. For missing - * elements {@literal null} is returned. + * @return a list of {@link GeoCoordinates}s representing the x,y position of each element specified in the arguments. For + * missing elements {@literal null} is returned. */ - List geopos(K key, V... members); + List geopos(K key, V... members); /** * * Retrieve distance between points {@code from} and {@code to}. If one or more elements are missing {@literal null} is * returned. Default in meters by, otherwise according to {@code unit} * - * @param key - * @param from - * @param to + * @param key the key of the geo set + * @param from from member + * @param to to member + * @param unit distance unit * * @return distance between points {@code from} and {@code to}. If one or more elements are missing {@literal null} is * returned. @@ -107,35 +112,35 @@ public interface RedisGeoConnection { * * Encode {@code longitude} and {@code latitude} to highest geohash accuracy. * - * @param longitude - * @param latitude - * @return multi-bulk reply with 4 elements 1: the 52-bit geohash integer for your latitude longitude, 2: The minimum corner - * of your geohash {@link GeoTuple}, 3: The maximum corner of your geohash {@link GeoTuple}, 4: The averaged center - * of your geohash {@link GeoTuple}. + * @param longitude the longitude coordinate according to WGS84 + * @param latitude the latitude coordinate according to WGS84 + * @return multi-bulk reply with 4 elements 1: the 52-bit geohash integer for your longitude/latitude, 2: The minimum corner + * of your geohash {@link GeoCoordinates}, 3: The maximum corner of your geohash {@link GeoCoordinates}, 4: The + * averaged center of your geohash {@link GeoCoordinates}. */ - List geoencode(double longitude, double latitude); + GeoEncoded geoencode(double longitude, double latitude); /** * * Encode {@code longitude} and {@code latitude} to highest geohash accuracy. * - * @param longitude - * @param latitude - * @param distance - * @param unit - * @return multi-bulk reply with four components 1: the 52-bit geohash integer for your latitude longitude, 2: The minimum - * corner of your geohash {@link GeoTuple}, 3: The maximum corner of your geohash {@link GeoTuple}, 4: The averaged - * center of your geohash {@link GeoTuple}. + * @param longitude the longitude coordinate according to WGS84 + * @param latitude the latitude coordinate according to WGS84 + * @param distance distance for geohash accuracy + * @param unit the distance unit + * @return multi-bulk reply with four components 1: the 52-bit geohash integer for your longitude/latitude, 2: The minimum + * corner of your geohash {@link GeoCoordinates}, 3: The maximum corner of your geohash {@link GeoCoordinates}, 4: + * The averaged center of your geohash {@link GeoCoordinates}. */ - List geoencode(double longitude, double latitude, double distance, GeoArgs.Unit unit); + GeoEncoded geoencode(double longitude, double latitude, double distance, GeoArgs.Unit unit); /** * * Decode geohash. * - * @param geohash - * @return a list of {@link GeoTuple}s (nested multi-bulk) with 3 elements 1: minimum decoded corner, 2: maximum decoded - * corner, 3: averaged center of bounding box. + * @param geohash geohash containing your longitude/latitude + * @return a list of {@link GeoCoordinates}s (nested multi-bulk) with 3 elements 1: minimum decoded corner, 2: maximum + * decoded corner, 3: averaged center of bounding box. */ - List geodecode(long geohash); + GeoEncoded geodecode(long geohash); } diff --git a/src/main/java/com/lambdaworks/redis/output/GeoCoordinatesListOutput.java b/src/main/java/com/lambdaworks/redis/output/GeoCoordinatesListOutput.java new file mode 100644 index 0000000000..8f0843df37 --- /dev/null +++ b/src/main/java/com/lambdaworks/redis/output/GeoCoordinatesListOutput.java @@ -0,0 +1,47 @@ +package com.lambdaworks.redis.output; + +import static java.lang.Double.parseDouble; + +import java.nio.ByteBuffer; +import java.util.List; + +import com.google.common.collect.Lists; +import com.lambdaworks.redis.GeoCoordinates; +import com.lambdaworks.redis.codec.RedisCodec; +import com.lambdaworks.redis.protocol.CommandOutput; + +/** + * A list output that creates a list with {@link GeoCoordinates}'s. + * + * @author Mark Paluch + */ +public class GeoCoordinatesListOutput extends CommandOutput> { + + private Double x; + + public GeoCoordinatesListOutput(RedisCodec codec) { + super(codec, null); + } + + @Override + public void set(ByteBuffer bytes) { + Double value = (bytes == null) ? 0 : parseDouble(decodeAscii(bytes)); + + if (x == null) { + x = value; + return; + } + + output.add(new GeoCoordinates(x, value)); + x = null; + } + + @Override + public void multi(int count) { + if (output == null) { + output = Lists.newArrayListWithCapacity(count); + } else if (count == -1) { + output.add(null); + } + } +} diff --git a/src/main/java/com/lambdaworks/redis/output/GeoEncodedOutput.java b/src/main/java/com/lambdaworks/redis/output/GeoEncodedOutput.java new file mode 100644 index 0000000000..f029796066 --- /dev/null +++ b/src/main/java/com/lambdaworks/redis/output/GeoEncodedOutput.java @@ -0,0 +1,73 @@ +package com.lambdaworks.redis.output; + +import static java.lang.Double.parseDouble; + +import java.nio.ByteBuffer; + +import com.lambdaworks.redis.GeoEncoded; +import com.lambdaworks.redis.GeoCoordinates; +import com.lambdaworks.redis.codec.RedisCodec; +import com.lambdaworks.redis.protocol.CommandOutput; + +/** + * Encodes the command output to retrieve a {@link GeoEncoded} object. + * + * @author Mark Paluch + */ +public class GeoEncodedOutput extends CommandOutput { + + private Long geohash; + private GeoCoordinates min; + private GeoCoordinates max; + private GeoCoordinates avg; + + private Double x; + + public GeoEncodedOutput(RedisCodec codec, Long geohash) { + super(codec, null); + this.geohash = geohash; + } + + @Override + public void set(long integer) { + if (geohash == null) { + geohash = integer; + } + } + + @Override + public void set(ByteBuffer bytes) { + Double value = (bytes == null) ? 0 : parseDouble(decodeAscii(bytes)); + + if (x == null) { + x = value; + return; + } + + try { + if (min == null) { + min = new GeoCoordinates(x, value); + return; + } + + if (max == null) { + max = new GeoCoordinates(x, value); + return; + } + + if (avg == null) { + avg = new GeoCoordinates(x, value); + return; + } + } finally { + x = null; + } + } + + @Override + public void complete(int depth) { + if (depth == 0) { + output = new GeoEncoded(geohash, min, max, avg); + } + } +} diff --git a/src/main/java/com/lambdaworks/redis/output/GeoWithinListOutput.java b/src/main/java/com/lambdaworks/redis/output/GeoWithinListOutput.java new file mode 100644 index 0000000000..860eb67d90 --- /dev/null +++ b/src/main/java/com/lambdaworks/redis/output/GeoWithinListOutput.java @@ -0,0 +1,95 @@ +package com.lambdaworks.redis.output; + +import static java.lang.Double.parseDouble; + +import java.nio.ByteBuffer; +import java.util.List; + +import com.google.common.collect.Lists; +import com.lambdaworks.redis.GeoCoordinates; +import com.lambdaworks.redis.GeoWithin; +import com.lambdaworks.redis.codec.RedisCodec; +import com.lambdaworks.redis.protocol.CommandOutput; + +/** + * A list output that creates a list with either double/long or {@link GeoCoordinates}'s. + * + * @author Mark Paluch + */ +public class GeoWithinListOutput extends CommandOutput>> { + + private V member; + private Double distance; + private Long geohash; + private GeoCoordinates coordinates; + + private Double x; + + private boolean withDistance; + private boolean withHash; + private boolean withCoordinates; + + public GeoWithinListOutput(RedisCodec codec, boolean withDistance, boolean withHash, boolean withCoordinates) { + super(codec, null); + this.withDistance = withDistance; + this.withHash = withHash; + this.withCoordinates = withCoordinates; + } + + @Override + public void set(long integer) { + if (member == null) { + member = (V) (Long) integer; + return; + } + + if (withHash) { + geohash = integer; + } + } + + @Override + public void set(ByteBuffer bytes) { + + if (member == null) { + member = codec.decodeValue(bytes); + return; + } + + Double value = (bytes == null) ? 0 : parseDouble(decodeAscii(bytes)); + if (withDistance) { + if (distance == null) { + distance = value; + return; + } + } + if (withCoordinates) { + if (x == null) { + x = value; + return; + } + + coordinates = new GeoCoordinates(x, value); + return; + } + } + + @Override + public void multi(int count) { + if (output == null) { + output = Lists.newArrayListWithCapacity(count); + } + } + + @Override + public void complete(int depth) { + if (depth == 1) { + output.add(new GeoWithin(member, distance, geohash, coordinates)); + + member = null; + distance = null; + geohash = null; + coordinates = null; + } + } +} diff --git a/src/main/java/com/lambdaworks/redis/output/NumericAndGeoTupleListOutput.java b/src/main/java/com/lambdaworks/redis/output/NumericAndGeoTupleListOutput.java deleted file mode 100644 index 61157ac9c5..0000000000 --- a/src/main/java/com/lambdaworks/redis/output/NumericAndGeoTupleListOutput.java +++ /dev/null @@ -1,76 +0,0 @@ -package com.lambdaworks.redis.output; - -import static java.lang.Double.parseDouble; - -import java.nio.ByteBuffer; -import java.util.List; - -import com.google.common.collect.Lists; -import com.lambdaworks.redis.GeoTuple; -import com.lambdaworks.redis.codec.RedisCodec; -import com.lambdaworks.redis.protocol.CommandOutput; - -/** - * A list output that creates a list with either double/long or {@link GeoTuple}'s. - * - * @author Mark Paluch - */ -public class NumericAndGeoTupleListOutput extends CommandOutput> { - - private GeoTuple geoTuple; - private Integer elementCount; - - public NumericAndGeoTupleListOutput(RedisCodec codec) { - super(codec, null); - } - - @Override - public void set(long integer) { - - if (geoTuple == null) { - output.add(integer); - return; - } - - if (geoTuple.getX() == null) { - geoTuple.setX(integer); - return; - } - - geoTuple.setY(integer); - output.add(geoTuple); - geoTuple = null; - } - - @Override - public void set(ByteBuffer bytes) { - Double value = (bytes == null) ? null : parseDouble(decodeAscii(bytes)); - - if (geoTuple == null) { - output.add(value); - return; - } - - if (geoTuple.getX() == null) { - geoTuple.setX(value); - return; - } - - geoTuple.setY(value); - output.add(geoTuple); - geoTuple = null; - } - - @Override - public void multi(int count) { - geoTuple = null; - if (elementCount == null) { - elementCount = count; - output = Lists.newArrayListWithCapacity(count); - } else if (count == 2) { - geoTuple = new GeoTuple(); - } else if (count == -1) { - output.add(null); - } - } -} diff --git a/src/test/java/com/lambdaworks/redis/GeoCommandTest.java b/src/test/java/com/lambdaworks/redis/GeoCommandTest.java index 448fd1922f..22ccc29de9 100644 --- a/src/test/java/com/lambdaworks/redis/GeoCommandTest.java +++ b/src/test/java/com/lambdaworks/redis/GeoCommandTest.java @@ -71,10 +71,10 @@ public void geopos() throws Exception { prepareGeo(); - List geopos = redis.geopos(key, "Weinheim", "foobar", "Bahn"); + List geopos = redis.geopos(key, "Weinheim", "foobar", "Bahn"); assertThat(geopos).hasSize(3); - assertThat(geopos.get(0).getX().doubleValue()).isEqualTo(8.6638, offset(0.001)); + assertThat(geopos.get(0).x.doubleValue()).isEqualTo(8.6638, offset(0.001)); assertThat(geopos.get(1)).isNull(); assertThat(geopos.get(2)).isNotNull(); } @@ -84,16 +84,35 @@ public void georadiusWithArgs() throws Exception { prepareGeo(); - GeoArgs geoArgs = new GeoArgs().withHash().withCoordinates().withDistance().withCount(1).asc(); + GeoArgs geoArgs = new GeoArgs().withHash().withCoordinates().withDistance().withCount(1).desc(); - List result = redis.georadius(key, 8.6582861, 49.5285695, 1, GeoArgs.Unit.km, geoArgs); + List> result = redis.georadius(key, 8.665351, 49.553302, 5, GeoArgs.Unit.km, geoArgs); assertThat(result).hasSize(1); - List response = (List) result.get(0); - assertThat(response).hasSize(4); + GeoWithin weinheim = result.get(0); - result = redis.georadius(key, 8.6582861, 49.5285695, 1, GeoArgs.Unit.km, null); + assertThat(weinheim.member).isEqualTo("Weinheim"); + assertThat(weinheim.geohash).isEqualTo(3666615932941099L); + + assertThat(weinheim.distance).isEqualTo(2.7882, offset(0.5)); + assertThat(weinheim.coordinates.x.doubleValue()).isEqualTo(8.663875, offset(0.5)); + assertThat(weinheim.coordinates.y.doubleValue()).isEqualTo(49.52825, offset(0.5)); + + result = redis.georadius(key, 8.665351, 49.553302, 1, GeoArgs.Unit.km, new GeoArgs()); assertThat(result).hasSize(1); + + GeoWithin bahn = result.get(0); + + assertThat(bahn.member).isEqualTo("Bahn"); + assertThat(bahn.geohash).isNull(); + + assertThat(bahn.distance).isNull(); + assertThat(bahn.coordinates).isNull(); + } + + @Test(expected = IllegalArgumentException.class) + public void georadiusWithNullArgs() throws Exception { + redis.georadius(key, 8.665351, 49.553302, 5, GeoArgs.Unit.km, null); } @Test @@ -115,55 +134,63 @@ public void georadiusbymemberWithArgs() throws Exception { GeoArgs geoArgs = new GeoArgs().withHash().withCoordinates().withDistance().desc(); - List empty = redis.georadiusbymember(key, "Bahn", 1, GeoArgs.Unit.km, geoArgs); + List> empty = redis.georadiusbymember(key, "Bahn", 1, GeoArgs.Unit.km, geoArgs); assertThat(empty).isNotEmpty(); - List georadiusbymember = redis.georadiusbymember(key, "Bahn", 5, GeoArgs.Unit.km, geoArgs); + List> georadiusbymember = redis.georadiusbymember(key, "Bahn", 5, GeoArgs.Unit.km, geoArgs); assertThat(georadiusbymember).hasSize(2); - List response = (List) georadiusbymember.get(0); - assertThat(response).hasSize(4); + GeoWithin weinheim = georadiusbymember.get(0); + assertThat(weinheim.member).isEqualTo("Weinheim"); + } + + @Test(expected = IllegalArgumentException.class) + public void georadiusbymemberWithNullArgs() throws Exception { + redis.georadiusbymember(key, "Bahn", 1, GeoArgs.Unit.km, null); } @Test public void geoencode() throws Exception { - List geoencode = redis.geoencode(8.6638775, 49.5282537); + GeoEncoded geoencoded = redis.geoencode(8.6638775, 49.5282537); + + assertThat(geoencoded.geohash).isEqualTo(3666615932941099L); + assertThat(geoencoded.min.x.doubleValue()).isEqualTo(8.6638730764389038, offset(1d)); + assertThat(geoencoded.min.y.doubleValue()).isEqualTo(49.528251210511513, offset(1d)); + + assertThat(geoencoded.max.x.doubleValue()).isEqualTo(8.6638784408569336, offset(1d)); + assertThat(geoencoded.max.y.doubleValue()).isEqualTo(49.528253745232675, offset(1d)); - assertThat(geoencode).hasSize(5); - assertThat(geoencode.get(0)).isEqualTo(3666615932941099L); - assertThat(geoencode.get(1)).isInstanceOf(GeoTuple.class); - assertThat(geoencode.get(2)).isInstanceOf(GeoTuple.class); - assertThat(geoencode.get(3)).isInstanceOf(GeoTuple.class); + assertThat(geoencoded.avg.x.doubleValue()).isEqualTo(8.6638757586479187, offset(1d)); + assertThat(geoencoded.avg.y.doubleValue()).isEqualTo(49.528252477872094, offset(1d)); } @Test public void geoencodeWithDistance() throws Exception { - List result = redis.geoencode(8.6638775, 49.5282537, 1, GeoArgs.Unit.km); + GeoEncoded geoencoded = redis.geoencode(8.6638775, 49.5282537, 1, GeoArgs.Unit.km); - assertThat(result).hasSize(5); - assertThat(result.get(0)).isEqualTo(3666615929405440L); - assertThat(result.get(1)).isInstanceOf(GeoTuple.class); - assertThat(result.get(2)).isInstanceOf(GeoTuple.class); - assertThat(result.get(3)).isInstanceOf(GeoTuple.class); + assertThat(geoencoded.geohash).isEqualTo(3666615929405440L); + assertThat(geoencoded.max).isNotNull(); + assertThat(geoencoded.min).isNotNull(); + assertThat(geoencoded.avg).isNotNull(); } @Test - public void geodecode() throws Exception { + public void geoencoded() throws Exception { - List result = redis.geodecode(3666615932941099L); + GeoEncoded geoencoded = redis.geodecode(3666615932941099L); - assertThat(result).hasSize(3); - assertThat(result.get(0).getX().doubleValue()).isEqualTo(8.6638730764389038, offset(1d)); - assertThat(result.get(0).getY().doubleValue()).isEqualTo(49.528251210511513, offset(1d)); + assertThat(geoencoded.geohash).isEqualTo(3666615932941099L); + assertThat(geoencoded.min.x.doubleValue()).isEqualTo(8.6638730764389038, offset(1d)); + assertThat(geoencoded.min.y.doubleValue()).isEqualTo(49.528251210511513, offset(1d)); - assertThat(result.get(1).getX().doubleValue()).isEqualTo(8.6638784408569336, offset(1d)); - assertThat(result.get(1).getY().doubleValue()).isEqualTo(49.528253745232675, offset(1d)); + assertThat(geoencoded.max.x.doubleValue()).isEqualTo(8.6638784408569336, offset(1d)); + assertThat(geoencoded.max.y.doubleValue()).isEqualTo(49.528253745232675, offset(1d)); - assertThat(result.get(2).getX().doubleValue()).isEqualTo(8.6638757586479187, offset(1d)); - assertThat(result.get(2).getY().doubleValue()).isEqualTo(49.528252477872094, offset(1d)); + assertThat(geoencoded.avg.x.doubleValue()).isEqualTo(8.6638757586479187, offset(1d)); + assertThat(geoencoded.avg.y.doubleValue()).isEqualTo(49.528252477872094, offset(1d)); } }