Skip to content

Commit

Permalink
Add support for GEOSEARCH and GEOSEARCHSTORE #1561
Browse files Browse the repository at this point in the history
  • Loading branch information
mp911de committed Jan 22, 2021
1 parent c3337f5 commit 2c75bb6
Show file tree
Hide file tree
Showing 16 changed files with 743 additions and 27 deletions.
17 changes: 17 additions & 0 deletions src/main/java/io/lettuce/core/AbstractRedisAsyncCommands.java
Original file line number Diff line number Diff line change
Expand Up @@ -703,6 +703,23 @@ protected RedisFuture<List<GeoWithin<V>>> georadiusbymember_ro(K key, V member,
return dispatch(commandBuilder.georadiusbymember(GEORADIUSBYMEMBER_RO, key, member, distance, unit.name(), geoArgs));
}

@Override
public RedisFuture<Set<V>> geosearch(K key, GeoSearch.GeoRef<K> reference, GeoSearch.GeoPredicate predicate) {
return dispatch(commandBuilder.geosearch(key, reference, predicate));
}

@Override
public RedisFuture<List<GeoWithin<V>>> geosearch(K key, GeoSearch.GeoRef<K> reference, GeoSearch.GeoPredicate predicate,
GeoArgs geoArgs) {
return dispatch(commandBuilder.geosearch(key, reference, predicate, geoArgs));
}

@Override
public RedisFuture<Long> geosearchstore(K destination, K key, GeoSearch.GeoRef<K> reference,
GeoSearch.GeoPredicate predicate, GeoArgs geoArgs, boolean storeDist) {
return dispatch(commandBuilder.geosearchstore(destination, key, reference, predicate, geoArgs, storeDist));
}

@Override
public RedisFuture<V> get(K key) {
return dispatch(commandBuilder.get(key));
Expand Down
17 changes: 17 additions & 0 deletions src/main/java/io/lettuce/core/AbstractRedisReactiveCommands.java
Original file line number Diff line number Diff line change
Expand Up @@ -764,6 +764,23 @@ protected Flux<GeoWithin<V>> georadiusbymember_ro(K key, V member, double distan
() -> commandBuilder.georadiusbymember(GEORADIUSBYMEMBER_RO, key, member, distance, unit.name(), geoArgs));
}

@Override
public Flux<V> geosearch(K key, GeoSearch.GeoRef<K> reference, GeoSearch.GeoPredicate predicate) {
return createDissolvingFlux(() -> commandBuilder.geosearch(key, reference, predicate));
}

@Override
public Flux<GeoWithin<V>> geosearch(K key, GeoSearch.GeoRef<K> reference, GeoSearch.GeoPredicate predicate,
GeoArgs geoArgs) {
return createDissolvingFlux(() -> commandBuilder.geosearch(key, reference, predicate, geoArgs));
}

@Override
public Mono<Long> geosearchstore(K destination, K key, GeoSearch.GeoRef<K> reference, GeoSearch.GeoPredicate predicate,
GeoArgs geoArgs, boolean storeDist) {
return createMono(() -> commandBuilder.geosearchstore(destination, key, reference, predicate, geoArgs, storeDist));
}

@Override
public Mono<V> get(K key) {
return createMono(() -> commandBuilder.get(key));
Expand Down
34 changes: 33 additions & 1 deletion src/main/java/io/lettuce/core/GeoArgs.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import io.lettuce.core.internal.LettuceAssert;
import io.lettuce.core.protocol.CommandArgs;
import io.lettuce.core.protocol.CommandKeyword;
import io.lettuce.core.protocol.ProtocolKeyword;

/**
*
Expand All @@ -38,6 +39,8 @@ public class GeoArgs implements CompositeArgument {

private Long count;

private boolean any;

private Sort sort = Sort.none;

/**
Expand Down Expand Up @@ -145,10 +148,24 @@ public GeoArgs withHash() {
* @return {@code this} {@link GeoArgs}.
*/
public GeoArgs withCount(long count) {
return withCount(count, false);
}

/**
* Limit results to {@code count} entries.
*
* @param count number greater 0.
* @param any whether to complete the command as soon as enough matches are found, so the results may not be the ones
* closest to the specified point.
* @return {@code this} {@link GeoArgs}.
* @since 6.1
*/
public GeoArgs withCount(long count, boolean any) {

LettuceAssert.isTrue(count > 0, "Count must be greater 0");

this.count = count;
this.any = any;
return this;
}

Expand Down Expand Up @@ -232,7 +249,7 @@ public enum Sort {
/**
* Supported geo unit.
*/
public enum Unit {
public enum Unit implements ProtocolKeyword {

/**
* meter.
Expand All @@ -253,6 +270,17 @@ public enum Unit {
* mile.
*/
mi;

private final byte[] asBytes;

Unit() {
asBytes = name().getBytes();
}

@Override
public byte[] getBytes() {
return asBytes;
}
}

public <K, V> void build(CommandArgs<K, V> args) {
Expand All @@ -275,6 +303,10 @@ public <K, V> void build(CommandArgs<K, V> args) {

if (count != null) {
args.add(CommandKeyword.COUNT).add(count);

if (any) {
args.add("ANY");
}
}
}

Expand Down
159 changes: 159 additions & 0 deletions src/main/java/io/lettuce/core/GeoSearch.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
/*
* Copyright 2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.lettuce.core;

import io.lettuce.core.internal.LettuceAssert;
import io.lettuce.core.protocol.CommandArgs;

/**
* Utility to create {@link GeoPredicate} and {@link GeoRef} objects to be used with {@code GEOSEARCH}.
*
* @author Mark Paluch
* @since 6.1
*/
public final class GeoSearch {

/**
* Create a {@link GeoRef} from a Geo set {@code member}.
*
* @param member the Geo set member to use as search reference starting point.
* @return the {@link GeoRef}.
*/
public static <K> GeoRef<K> fromMember(K member) {
LettuceAssert.notNull(member, "Reference member must not be null");
return new FromMember<>(member);
}

/**
* Create a {@link GeoRef} from WGS84 coordinates {@code longitude} and {@code latitude}.
*
* @param longitude the longitude coordinate according to WGS84.
* @param latitude the latitude coordinate according to WGS84.
* @return the {@link GeoRef}.
*/
public static <K> GeoRef<K> fromCoordinates(double longitude, double latitude) {
return (GeoRef<K>) new FromCoordinates(longitude, latitude);
}

/**
* Create a {@link GeoPredicate} by specifying a radius {@code distance} and {@link GeoArgs.Unit}.
*
* @param distance the radius.
* @param unit size unit.
* @return the {@link GeoPredicate} for the specified radius.
*/
public static GeoPredicate byRadius(double distance, GeoArgs.Unit unit) {
return new Radius(distance, unit);
}

/**
* Create a {@link GeoPredicate} by specifying a box of the size {@code width}, {@code height} and {@link GeoArgs.Unit}.
*
* @param width box width.
* @param height box height.
* @param unit size unit.
* @return the {@link GeoPredicate} for the specified box.
*/
public static GeoPredicate byBox(double width, double height, GeoArgs.Unit unit) {
return new Box(width, height, unit);
}

/**
* Geo reference specifying a search starting point.
*
* @param <K>
*/
public interface GeoRef<K> extends CompositeArgument {

}

static class FromMember<K> implements GeoRef<K> {

final K member;

public FromMember(K member) {
this.member = member;
}

@Override
@SuppressWarnings("unchecked")
public <K, V> void build(CommandArgs<K, V> args) {
args.add("FROMMEMBER").addKey((K) member);
}

}

static class FromCoordinates implements GeoRef<Object> {

final double longitude, latitude;

public FromCoordinates(double longitude, double latitude) {
this.longitude = longitude;
this.latitude = latitude;
}

@Override
public <K, V> void build(CommandArgs<K, V> args) {
args.add("FROMLONLAT").add(longitude).add(latitude);
}

}

/**
* Geo predicate specifying a search scope.
*/
public interface GeoPredicate extends CompositeArgument {

}

static class Radius implements GeoPredicate {

final double distance;

final GeoArgs.Unit unit;

public Radius(double distance, GeoArgs.Unit unit) {
this.distance = distance;
this.unit = unit;
}

@Override
public <K, V> void build(CommandArgs<K, V> args) {
args.add("BYRADIUS").add(distance).add(unit);
}

}

static class Box implements GeoPredicate {

final double width, height;

final GeoArgs.Unit unit;

public Box(double width, double height, GeoArgs.Unit unit) {
this.width = width;
this.height = height;
this.unit = unit;
}

@Override
public <K, V> void build(CommandArgs<K, V> args) {
args.add("BYBOX").add(width).add(height).add(unit);
}

}

}
56 changes: 53 additions & 3 deletions src/main/java/io/lettuce/core/RedisCommandBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -811,7 +811,6 @@ Command<K, V, Set<V>> georadius(CommandType commandType, K key, double longitude
String unit) {
notNullKey(key);
LettuceAssert.notNull(unit, "Unit " + MUST_NOT_BE_NULL);
LettuceAssert.notEmpty(unit, "Unit " + MUST_NOT_BE_EMPTY);

CommandArgs<K, V> args = new CommandArgs<>(codec).addKey(key).add(longitude).add(latitude).add(distance).add(unit);
return createCommand(commandType, new ValueSetOutput<>(codec), args);
Expand Down Expand Up @@ -840,7 +839,7 @@ Command<K, V, Long> georadius(K key, double longitude, double latitude, double d
LettuceAssert.notEmpty(unit, "Unit " + MUST_NOT_BE_EMPTY);
LettuceAssert.notNull(geoRadiusStoreArgs, "GeoRadiusStoreArgs " + MUST_NOT_BE_NULL);
LettuceAssert.isTrue(geoRadiusStoreArgs.getStoreKey() != null || geoRadiusStoreArgs.getStoreDistKey() != null,
"At least STORE key or STORDIST key is required");
"At least STORE key or STOREDIST key is required");

CommandArgs<K, V> args = new CommandArgs<>(codec).addKey(key).add(longitude).add(latitude).add(distance).add(unit);
geoRadiusStoreArgs.build(args);
Expand Down Expand Up @@ -882,14 +881,65 @@ Command<K, V, Long> georadiusbymember(K key, V member, double distance, String u
LettuceAssert.notNull(unit, "Unit " + MUST_NOT_BE_NULL);
LettuceAssert.notEmpty(unit, "Unit " + MUST_NOT_BE_EMPTY);
LettuceAssert.isTrue(geoRadiusStoreArgs.getStoreKey() != null || geoRadiusStoreArgs.getStoreDistKey() != null,
"At least STORE key or STORDIST key is required");
"At least STORE key or STOREDIST key is required");

CommandArgs<K, V> args = new CommandArgs<>(codec).addKey(key).addValue(member).add(distance).add(unit);
geoRadiusStoreArgs.build(args);

return createCommand(GEORADIUSBYMEMBER, new IntegerOutput<>(codec), args);
}

Command<K, V, Set<V>> geosearch(K key, GeoSearch.GeoRef<K> reference, GeoSearch.GeoPredicate predicate) {
notNullKey(key);
LettuceAssert.notNull(reference, "GeoRef " + MUST_NOT_BE_NULL);
LettuceAssert.notNull(predicate, "GeoPredicate " + MUST_NOT_BE_NULL);

CommandArgs<K, V> args = new CommandArgs<>(codec).addKey(key);

reference.build(args);
predicate.build(args);

return createCommand(GEOSEARCH, new ValueSetOutput<>(codec), args);
}

Command<K, V, List<GeoWithin<V>>> geosearch(K key, GeoSearch.GeoRef<K> reference, GeoSearch.GeoPredicate predicate,
GeoArgs geoArgs) {
notNullKey(key);
LettuceAssert.notNull(reference, "GeoRef " + MUST_NOT_BE_NULL);
LettuceAssert.notNull(predicate, "GeoPredicate " + MUST_NOT_BE_NULL);

CommandArgs<K, V> args = new CommandArgs<>(codec).addKey(key);

reference.build(args);
predicate.build(args);
geoArgs.build(args);

return createCommand(GEOSEARCH,
new GeoWithinListOutput<>(codec, geoArgs.isWithDistance(), geoArgs.isWithHash(), geoArgs.isWithCoordinates()),
args);
}

Command<K, V, Long> geosearchstore(K destination, K key, GeoSearch.GeoRef<K> reference, GeoSearch.GeoPredicate predicate,
GeoArgs geoArgs, boolean storeDist) {
notNullKey(key);
LettuceAssert.notNull(destination, "Destination " + MUST_NOT_BE_NULL);
LettuceAssert.notNull(key, "Key " + MUST_NOT_BE_NULL);
LettuceAssert.notNull(reference, "GeoRef " + MUST_NOT_BE_NULL);
LettuceAssert.notNull(predicate, "GeoPredicate " + MUST_NOT_BE_NULL);

CommandArgs<K, V> args = new CommandArgs<>(codec).addKey(destination).addKey(key);

reference.build(args);
predicate.build(args);
geoArgs.build(args);

if (storeDist) {
args.add("STOREDIST");
}

return createCommand(GEOSEARCHSTORE, new IntegerOutput<>(codec), args);
}

Command<K, V, V> get(K key) {
notNullKey(key);

Expand Down
Loading

0 comments on commit 2c75bb6

Please sign in to comment.