You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@geode.apache.org by go...@apache.org on 2018/11/09 01:20:48 UTC

[geode] branch develop updated: [GEODE-5998] Add geospatial commands to Redis adapter (#2802)

This is an automated email from the ASF dual-hosted git repository.

gosullivan pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/geode.git


The following commit(s) were added to refs/heads/develop by this push:
     new 7bf0225  [GEODE-5998] Add geospatial commands to Redis adapter (#2802)
7bf0225 is described below

commit 7bf02251fd047cb1cf575c01b80a9807108618da
Author: Aditya Anchuri <aa...@pivotal.io>
AuthorDate: Thu Nov 8 17:20:39 2018 -0800

    [GEODE-5998] Add geospatial commands to Redis adapter (#2802)
    
    This PR adds the following geospatial commands to the Redis adapter:
    
    * GEOADD
    * GEOPOS
    * GEODIST
    * GEOHASH
    * GEORADIUS
    * GEORADIUSBYMEMBER
    
    This leverages a lot of the same logic as the SortedSetExecutor used for ZRANGE commands. The geocoding logic uses the geo library, which implements the same public domain algorithm that Redis uses.
---
 .../integrationTest/resources/assembly_content.txt |   3 +
 .../integrationTest/resources/expected_jars.txt    |   3 +
 geode-core/build.gradle                            |   1 +
 .../java/org/apache/geode/redis/GeoCoderTest.java} |  35 +-
 .../java/org/apache/geode/redis/GeoJUnitTest.java  | 421 +++++++++++++++++++++
 .../org/apache/geode/redis/internal/Coder.java     | 163 ++++----
 .../EchoExecutor.java => CoderException.java}      |  30 +-
 .../org/apache/geode/redis/internal/GeoCoder.java  | 213 +++++++++++
 .../{executor/EchoExecutor.java => GeoCoord.java}  |  30 +-
 .../redis/internal/GeoRadiusResponseElement.java   |  68 ++++
 .../org/apache/geode/redis/internal/HashArea.java  |  30 ++
 .../EchoExecutor.java => HashNeighbors.java}       |  39 +-
 ...oExecutor.java => MemberNotFoundException.java} |  31 +-
 .../java/org/apache/geode/redis/internal/Pair.java |  30 ++
 .../geode/redis/internal/RedisCommandType.java     | 125 ++++++
 .../geode/redis/internal/RedisConstants.java       |  26 +-
 .../redis/internal/executor/AbstractExecutor.java  |  27 ++
 .../redis/internal/executor/EchoExecutor.java      |   2 +-
 .../redis/internal/executor/KeysExecutor.java      |   5 +-
 .../redis/internal/executor/SortedSetQuery.java    |   6 +
 .../redis/internal/executor/TypeExecutor.java      |   6 +-
 .../redis/internal/executor/hash/HGetExecutor.java |  10 +-
 .../executor/hash/HIncrByFloatExecutor.java        |   4 +-
 .../internal/executor/hash/HKeysExecutor.java      |   4 +-
 .../internal/executor/hash/HMGetExecutor.java      |   3 +-
 .../internal/executor/hash/HValsExecutor.java      |   3 +-
 .../internal/executor/list/LIndexExecutor.java     |   3 +-
 .../redis/internal/executor/list/PopExecutor.java  |   6 +-
 .../internal/executor/set/SMembersExecutor.java    |   3 +-
 .../redis/internal/executor/set/SPopExecutor.java  |   3 +-
 .../internal/executor/set/SRandMemberExecutor.java |  12 +-
 .../redis/internal/executor/set/SetOpExecutor.java |   9 +-
 .../executor/sortedset/GeoAddExecutor.java         |  76 ++++
 .../GeoDistExecutor.java}                          |  39 +-
 .../GeoHashExecutor.java}                          |  42 +-
 .../GeoPosExecutor.java}                           |  45 ++-
 .../sortedset/GeoRadiusByMemberExecutor.java       | 124 ++++++
 .../executor/sortedset/GeoRadiusExecutor.java      | 120 ++++++
 .../executor/sortedset/GeoRadiusParameters.java    | 132 +++++++
 .../executor/sortedset/GeoSortedSetExecutor.java   |  94 +++++
 .../executor/sortedset/ZIncrByExecutor.java        |   4 +-
 .../executor/sortedset/ZScoreExecutor.java         |   3 +-
 .../internal/executor/string/GetExecutor.java      |   9 +-
 .../internal/executor/string/GetRangeExecutor.java |   3 +-
 .../internal/executor/string/GetSetExecutor.java   |   8 +-
 .../executor/string/IncrByFloatExecutor.java       |   4 +-
 .../internal/executor/string/MGetExecutor.java     |   3 +-
 .../internal/executor/string/MSetExecutor.java     |   1 -
 .../sanctioned-geode-core-serializables.txt        |  11 +
 geode-core/src/test/resources/expected-pom.xml     |   6 +
 gradle/dependency-versions.properties              |   1 +
 51 files changed, 1760 insertions(+), 319 deletions(-)

diff --git a/geode-assembly/src/integrationTest/resources/assembly_content.txt b/geode-assembly/src/integrationTest/resources/assembly_content.txt
index 13c7f40..ad28303 100644
--- a/geode-assembly/src/integrationTest/resources/assembly_content.txt
+++ b/geode-assembly/src/integrationTest/resources/assembly_content.txt
@@ -1192,10 +1192,12 @@ lib/commons-digester-2.1.jar
 lib/commons-io-2.6.jar
 lib/commons-lang-2.6.jar
 lib/commons-logging-1.2.jar
+lib/commons-math3-3.2.jar
 lib/commons-modeler-2.0.1.jar
 lib/commons-validator-1.6.jar
 lib/fastutil-8.2.1.jar
 lib/findbugs-annotations-1.3.9-1.jar
+lib/geo-0.7.1.jar
 lib/geode-common-1.8.0-SNAPSHOT.jar
 lib/geode-connectors-1.8.0-SNAPSHOT.jar
 lib/geode-core-1.8.0-SNAPSHOT.jar
@@ -1211,6 +1213,7 @@ lib/geode-rebalancer-1.8.0-SNAPSHOT.jar
 lib/geode-wan-1.8.0-SNAPSHOT.jar
 lib/geode-web-1.8.0-SNAPSHOT.jar
 lib/gfsh-dependencies.jar
+lib/grumpy-core-0.2.2.jar
 lib/jackson-annotations-2.9.6.jar
 lib/jackson-core-2.9.6.jar
 lib/jackson-databind-2.9.6.jar
diff --git a/geode-assembly/src/integrationTest/resources/expected_jars.txt b/geode-assembly/src/integrationTest/resources/expected_jars.txt
index d1e6d29..4697b40 100644
--- a/geode-assembly/src/integrationTest/resources/expected_jars.txt
+++ b/geode-assembly/src/integrationTest/resources/expected_jars.txt
@@ -104,3 +104,6 @@ springfox-swagger-common
 springfox-swagger-ui
 swagger-annotations
 swagger-models
+commons-math
+geo
+grumpy-core
diff --git a/geode-core/build.gradle b/geode-core/build.gradle
index a2c7f00..67920ed 100755
--- a/geode-core/build.gradle
+++ b/geode-core/build.gradle
@@ -198,6 +198,7 @@ dependencies {
 
   compile('org.apache.logging.log4j:log4j-api:' + project.'log4j.version')
   compile('org.apache.logging.log4j:log4j-core:' + project.'log4j.version')
+  compile('com.github.davidmoten:geo:' + project.'geo.version')
 
   runtimeOnly('org.fusesource.jansi:jansi:' + project.'jansi.version') {
     ext.optional = true
diff --git a/geode-core/src/main/java/org/apache/geode/redis/internal/executor/EchoExecutor.java b/geode-core/src/integrationTest/java/org/apache/geode/redis/GeoCoderTest.java
similarity index 50%
copy from geode-core/src/main/java/org/apache/geode/redis/internal/executor/EchoExecutor.java
copy to geode-core/src/integrationTest/java/org/apache/geode/redis/GeoCoderTest.java
index 407e653..19db1d3 100755
--- a/geode-core/src/main/java/org/apache/geode/redis/internal/executor/EchoExecutor.java
+++ b/geode-core/src/integrationTest/java/org/apache/geode/redis/GeoCoderTest.java
@@ -12,27 +12,28 @@
  * or implied. See the License for the specific language governing permissions and limitations under
  * the License.
  */
-package org.apache.geode.redis.internal.executor;
+package org.apache.geode.redis;
 
-import java.util.List;
+import static org.junit.Assert.assertEquals;
 
-import org.apache.geode.redis.internal.Coder;
-import org.apache.geode.redis.internal.Command;
-import org.apache.geode.redis.internal.ExecutionHandlerContext;
-import org.apache.geode.redis.internal.RedisConstants.ArityDef;
+import com.github.davidmoten.geo.LatLong;
+import org.junit.Test;
 
-public class EchoExecutor extends AbstractExecutor {
+import org.apache.geode.redis.internal.CoderException;
+import org.apache.geode.redis.internal.GeoCoder;
 
-  @Override
-  public void executeCommand(Command command, ExecutionHandlerContext context) {
-    List<byte[]> commandElems = command.getProcessedCommand();
-    if (commandElems.size() < 2) {
-      command.setResponse(Coder.getErrorResponse(context.getByteBufAllocator(), ArityDef.ECHO));
-      return;
-    }
-
-    byte[] echoMessage = commandElems.get(1);
-    command.setResponse(Coder.getBulkStringResponse(context.getByteBufAllocator(), echoMessage));
+public class GeoCoderTest {
+  @Test
+  public void testGeoHash() throws CoderException {
+    String hash = GeoCoder.geohash(Double.toString(13.361389).getBytes(),
+        Double.toString(38.115556).getBytes());
+    assertEquals("sqc8b49rnyte", hash);
   }
 
+  @Test
+  public void testGeoPos() throws CoderException {
+    LatLong pos = GeoCoder.geoPos("sqc8b49rnyte");
+    assertEquals(13.361389, pos.getLon(), 0.000001);
+    assertEquals(38.115556, pos.getLat(), 0.000001);
+  }
 }
diff --git a/geode-core/src/integrationTest/java/org/apache/geode/redis/GeoJUnitTest.java b/geode-core/src/integrationTest/java/org/apache/geode/redis/GeoJUnitTest.java
new file mode 100755
index 0000000..d8d29fd
--- /dev/null
+++ b/geode-core/src/integrationTest/java/org/apache/geode/redis/GeoJUnitTest.java
@@ -0,0 +1,421 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The ASF licenses this file to You 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
+ *
+ * http://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 org.apache.geode.redis;
+
+import static org.apache.geode.distributed.ConfigurationProperties.LOCATORS;
+import static org.apache.geode.distributed.ConfigurationProperties.LOG_LEVEL;
+import static org.apache.geode.distributed.ConfigurationProperties.MCAST_PORT;
+import static org.apache.geode.internal.Assert.assertTrue;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import redis.clients.jedis.GeoCoordinate;
+import redis.clients.jedis.GeoRadiusResponse;
+import redis.clients.jedis.GeoUnit;
+import redis.clients.jedis.Jedis;
+import redis.clients.jedis.params.geo.GeoRadiusParam;
+
+import org.apache.geode.cache.CacheFactory;
+import org.apache.geode.cache.GemFireCache;
+import org.apache.geode.cache.Region;
+import org.apache.geode.internal.AvailablePortHelper;
+import org.apache.geode.redis.internal.ByteArrayWrapper;
+import org.apache.geode.test.junit.categories.RedisTest;
+
+@Category({RedisTest.class})
+public class GeoJUnitTest {
+  private static Jedis jedis;
+  private static GeodeRedisServer server;
+  private static GemFireCache cache;
+  private static Random rand;
+  private static int port = 6379;
+
+  @BeforeClass
+  public static void setUp() throws IOException {
+    rand = new Random();
+    CacheFactory cf = new CacheFactory();
+    // cf.set("log-file", "redis.log");
+    cf.set(LOG_LEVEL, "error");
+    cf.set(MCAST_PORT, "0");
+    cf.set(LOCATORS, "");
+    cache = cf.create();
+    port = AvailablePortHelper.getRandomAvailableTCPPort();
+    server = new GeodeRedisServer("localhost", port);
+
+    server.start();
+    jedis = new Jedis("localhost", port, 10000000);
+  }
+
+  @Test
+  public void testGeoAdd() {
+    Map<String, GeoCoordinate> memberCoordinateMap = new HashMap<>();
+    memberCoordinateMap.put("Palermo", new GeoCoordinate(13.361389, 38.115556));
+    memberCoordinateMap.put("Catania", new GeoCoordinate(15.087269, 37.502669));
+    Long l = jedis.geoadd("Sicily", memberCoordinateMap);
+    assertTrue(l == 2L);
+
+    Region<ByteArrayWrapper, ByteArrayWrapper> sicilyRegion = cache.getRegion("Sicily");
+    assertNotNull("Expected region to be not NULL", sicilyRegion);
+
+    // Check GeoHash
+    String hash =
+        sicilyRegion.get(new ByteArrayWrapper(new String("Palermo").getBytes())).toString();
+    assertEquals("sqc8b49rnyte", hash);
+
+    hash = sicilyRegion.get(new ByteArrayWrapper(new String("Catania").getBytes())).toString();
+    assertEquals("sqdtr74hyu5n", hash);
+  }
+
+  @Test
+  public void testGeoHash() {
+    Map<String, GeoCoordinate> memberCoordinateMap = new HashMap<>();
+    memberCoordinateMap.put("Palermo", new GeoCoordinate(13.361389, 38.115556));
+    memberCoordinateMap.put("Catania", new GeoCoordinate(15.087269, 37.502669));
+    Long l = jedis.geoadd("Sicily", memberCoordinateMap);
+    assertTrue(l == 2L);
+
+    List<String> hashes = jedis.geohash("Sicily", "Palermo", "Catania", "Rome");
+
+    assertEquals("sqc8b49rnyte", hashes.get(0));
+    assertEquals("sqdtr74hyu5n", hashes.get(1));
+    assertEquals(null, hashes.get(2));
+  }
+
+  @Test
+  public void testGeoPos() {
+    Map<String, GeoCoordinate> memberCoordinateMap = new HashMap<>();
+    memberCoordinateMap.put("Palermo", new GeoCoordinate(13.361389, 38.115556));
+    memberCoordinateMap.put("Catania", new GeoCoordinate(15.087269, 37.502669));
+    Long l = jedis.geoadd("Sicily", memberCoordinateMap);
+    assertTrue(l == 2L);
+
+    List<GeoCoordinate> positions = jedis.geopos("Sicily", "Palermo", "Catania", "Rome");
+
+    assertEquals(13.361389, positions.get(0).getLongitude(), 0.000001);
+    assertEquals(38.115556, positions.get(0).getLatitude(), 0.000001);
+    assertEquals(15.087269, positions.get(1).getLongitude(), 0.000001);
+    assertEquals(37.502669, positions.get(1).getLatitude(), 0.000001);
+    assertEquals(null, positions.get(2));
+  }
+
+  @Test
+  public void testGeoDist() {
+    Map<String, GeoCoordinate> memberCoordinateMap = new HashMap<>();
+    memberCoordinateMap.put("Palermo", new GeoCoordinate(13.361389, 38.115556));
+    memberCoordinateMap.put("Catania", new GeoCoordinate(15.087269, 37.502669));
+    Long l = jedis.geoadd("Sicily", memberCoordinateMap);
+    assertTrue(l == 2L);
+
+    Double dist = jedis.geodist("Sicily", "Palermo", "Catania");
+    assertEquals(166274.1516, dist, 5.0);
+
+    dist = jedis.geodist("Sicily", "Palermo", "Catania", GeoUnit.KM);
+    assertEquals(166.2742, dist, 0.005);
+
+    dist = jedis.geodist("Sicily", "Palermo", "Catania", GeoUnit.M);
+    assertEquals(166274.1516, dist, 5.0);
+
+    dist = jedis.geodist("Sicily", "Palermo", "Catania", GeoUnit.MI);
+    assertEquals(103.3182, dist, 0.003);
+
+    dist = jedis.geodist("Sicily", "Palermo", "Catania", GeoUnit.FT);
+    assertEquals(545520.0960, dist, 15.0);
+
+    dist = jedis.geodist("Sicily", "Palermo", "Foo");
+    assertNull(dist);
+  }
+
+  @Test
+  public void testGeoRadiusBasic() {
+    Map<String, GeoCoordinate> memberCoordinateMap = new HashMap<>();
+    memberCoordinateMap.put("Palermo", new GeoCoordinate(13.361389, 38.115556));
+    memberCoordinateMap.put("Catania", new GeoCoordinate(15.087269, 37.502669));
+    Long l = jedis.geoadd("Sicily", memberCoordinateMap);
+    assertTrue(l == 2L);
+
+    List<GeoRadiusResponse> gr = jedis.georadius("Sicily", 15.0, 37.0, 100000, GeoUnit.M);
+    assertEquals(1, gr.size());
+    assertEquals("Catania", gr.get(0).getMemberByString());
+
+    gr = jedis.georadius("Sicily", 15.0, 37.0, 200, GeoUnit.KM);
+    assertEquals(2, gr.size());
+    assertTrue(gr.stream().anyMatch(r -> r.getMemberByString().equals("Catania")));
+    assertTrue(gr.stream().anyMatch(r -> r.getMemberByString().equals("Palermo")));
+  }
+
+  @Test
+  public void testGeoRadiusWithDist() {
+    Map<String, GeoCoordinate> memberCoordinateMap = new HashMap<>();
+    memberCoordinateMap.put("Palermo", new GeoCoordinate(13.361389, 38.115556));
+    memberCoordinateMap.put("Catania", new GeoCoordinate(15.087269, 37.502669));
+    Long l = jedis.geoadd("Sicily", memberCoordinateMap);
+    assertTrue(l == 2L);
+
+    List<GeoRadiusResponse> gr = jedis.georadius("Sicily", 15.0, 37.0, 100, GeoUnit.KM,
+        GeoRadiusParam.geoRadiusParam().withDist());
+    assertEquals(1, gr.size());
+    assertEquals("Catania", gr.get(0).getMemberByString());
+    assertEquals(56.4413, gr.get(0).getDistance(), 0.0001);
+  }
+
+  @Test
+  public void testGeoRadiusWithCoord() {
+    Map<String, GeoCoordinate> memberCoordinateMap = new HashMap<>();
+    memberCoordinateMap.put("Palermo", new GeoCoordinate(13.361389, 38.115556));
+    memberCoordinateMap.put("Catania", new GeoCoordinate(15.087269, 37.502669));
+    Long l = jedis.geoadd("Sicily", memberCoordinateMap);
+    assertTrue(l == 2L);
+
+    List<GeoRadiusResponse> gr = jedis.georadius("Sicily", 15.0, 37.0, 100, GeoUnit.KM,
+        GeoRadiusParam.geoRadiusParam().withCoord());
+    assertEquals(1, gr.size());
+    assertEquals("Catania", gr.get(0).getMemberByString());
+    assertEquals(15.087269, gr.get(0).getCoordinate().getLongitude(), 0.0001);
+    assertEquals(37.502669, gr.get(0).getCoordinate().getLatitude(), 0.0001);
+  }
+
+  @Test
+  public void testGeoRadiusCount() {
+    Map<String, GeoCoordinate> memberCoordinateMap = new HashMap<>();
+    memberCoordinateMap.put("Palermo", new GeoCoordinate(13.361389, 38.115556));
+    memberCoordinateMap.put("Catania", new GeoCoordinate(15.087269, 37.502669));
+    Long l = jedis.geoadd("Sicily", memberCoordinateMap);
+    assertTrue(l == 2L);
+
+    List<GeoRadiusResponse> gr = jedis.georadius("Sicily", 15.0, 37.0, 200,
+        GeoUnit.KM, GeoRadiusParam.geoRadiusParam().count(1));
+    assertEquals(1, gr.size());
+  }
+
+  @Test
+  public void testGeoRadiusOrdered() {
+    Map<String, GeoCoordinate> memberCoordinateMap = new HashMap<>();
+    memberCoordinateMap.put("Palermo", new GeoCoordinate(13.361389, 38.115556));
+    memberCoordinateMap.put("Catania", new GeoCoordinate(15.087269, 37.502669));
+    Long l = jedis.geoadd("Sicily", memberCoordinateMap);
+    assertTrue(l == 2L);
+
+    List<GeoRadiusResponse> gr = jedis.georadius("Sicily", 15.0, 37.0, 200,
+        GeoUnit.KM, GeoRadiusParam.geoRadiusParam().sortAscending());
+    assertEquals(2, gr.size());
+    assertEquals("Catania", gr.get(0).getMemberByString());
+    assertEquals("Palermo", gr.get(1).getMemberByString());
+
+    gr = jedis.georadius("Sicily", 15.0, 37.0, 200,
+        GeoUnit.KM, GeoRadiusParam.geoRadiusParam().sortDescending());
+    assertEquals(2, gr.size());
+    assertEquals("Palermo", gr.get(0).getMemberByString());
+    assertEquals("Catania", gr.get(1).getMemberByString());
+  }
+
+  @Test
+  public void testGeoRadiusWithCoordDistCountAsc() {
+    Map<String, GeoCoordinate> memberCoordinateMap = new HashMap<>();
+    memberCoordinateMap.put("Palermo", new GeoCoordinate(13.361389, 38.115556));
+    memberCoordinateMap.put("Catania", new GeoCoordinate(15.087269, 37.502669));
+    Long l = jedis.geoadd("Sicily", memberCoordinateMap);
+    assertTrue(l == 2L);
+
+    List<GeoRadiusResponse> gr = jedis.georadius("Sicily", 15.0, 37.0, 200, GeoUnit.KM,
+        GeoRadiusParam.geoRadiusParam().withCoord().withDist().count(1).sortAscending());
+    assertEquals(1, gr.size());
+    assertEquals("Catania", gr.get(0).getMemberByString());
+    assertEquals(15.087269, gr.get(0).getCoordinate().getLongitude(), 0.0001);
+    assertEquals(37.502669, gr.get(0).getCoordinate().getLatitude(), 0.0001);
+    assertEquals(56.4413, gr.get(0).getDistance(), 0.0001);
+  }
+
+  @Test
+  public void testGeoRadiusByMemberBasic() {
+    Map<String, GeoCoordinate> memberCoordinateMap = new HashMap<>();
+    memberCoordinateMap.put("Palermo", new GeoCoordinate(13.361389, 38.115556));
+    memberCoordinateMap.put("Catania", new GeoCoordinate(15.087269, 37.502669));
+    Long l = jedis.geoadd("Sicily", memberCoordinateMap);
+    assertTrue(l == 2L);
+
+    List<GeoRadiusResponse> gr = jedis.georadiusByMember("Sicily", "Catania", 250, GeoUnit.KM);
+    assertEquals(1, gr.size());
+    assertEquals("Palermo", gr.get(0).getMemberByString());
+  }
+
+  @Test
+  public void testGeoRadiusByMemberWithDist() {
+    Map<String, GeoCoordinate> memberCoordinateMap = new HashMap<>();
+    memberCoordinateMap.put("Palermo", new GeoCoordinate(13.361389, 38.115556));
+    memberCoordinateMap.put("Catania", new GeoCoordinate(15.087269, 37.502669));
+    Long l = jedis.geoadd("Sicily", memberCoordinateMap);
+    assertTrue(l == 2L);
+
+    List<GeoRadiusResponse> gr = jedis.georadiusByMember("Sicily", "Catania", 250,
+        GeoUnit.KM, GeoRadiusParam.geoRadiusParam().withDist());
+    assertEquals(1, gr.size());
+    assertEquals("Palermo", gr.get(0).getMemberByString());
+    assertEquals(166.2742, gr.get(0).getDistance(), 0.0001);
+  }
+
+  @Test
+  public void testGeoRadiusByMemberWithCoord() {
+    Map<String, GeoCoordinate> memberCoordinateMap = new HashMap<>();
+    memberCoordinateMap.put("Palermo", new GeoCoordinate(13.361389, 38.115556));
+    memberCoordinateMap.put("Catania", new GeoCoordinate(15.087269, 37.502669));
+    Long l = jedis.geoadd("Sicily", memberCoordinateMap);
+    assertTrue(l == 2L);
+
+    List<GeoRadiusResponse> gr = jedis.georadiusByMember("Sicily", "Catania", 250,
+        GeoUnit.KM, GeoRadiusParam.geoRadiusParam().withCoord());
+    assertEquals(1, gr.size());
+    assertEquals("Palermo", gr.get(0).getMemberByString());
+    assertEquals(13.361389, gr.get(0).getCoordinate().getLongitude(), 0.0001);
+    assertEquals(38.115556, gr.get(0).getCoordinate().getLatitude(), 0.0001);
+  }
+
+
+  @Test
+  public void testGeoRadiusByMemberFull() {
+    Map<String, GeoCoordinate> memberCoordinateMap = new HashMap<>();
+    // 47.599246, -122.333826
+    memberCoordinateMap.put("Galvanize", new GeoCoordinate(-122.333826, 47.599246));
+    // 47.600249, -122.331166
+    memberCoordinateMap.put("Flatstick", new GeoCoordinate(-122.331166, 47.600249));
+    // 47.601120, -122.332063
+    memberCoordinateMap.put("Fuel Sports", new GeoCoordinate(-122.332063, 47.601120));
+    // 47.600657, -122.334362
+    memberCoordinateMap.put("Central Saloon", new GeoCoordinate(-122.334362, 47.600657));
+    // 47.598519, -122.334405
+    memberCoordinateMap.put("Cowgirls", new GeoCoordinate(-122.334405, 47.598519));
+
+    // 47.608336, -122.340746
+    memberCoordinateMap.put("Jarrbar", new GeoCoordinate(-122.340746, 47.608336));
+    // 47.612499, -122.336871
+    memberCoordinateMap.put("Oliver's lounge", new GeoCoordinate(-122.336871, 47.612499));
+    // 47.612622, -122.320288
+    memberCoordinateMap.put("Garage", new GeoCoordinate(-122.320288, 47.612622));
+    // 47.607362, -122.316517
+    memberCoordinateMap.put("Ba Bar", new GeoCoordinate(-122.316517, 47.607362));
+
+    // 47.615146, -122.322355
+    memberCoordinateMap.put("Bill's", new GeoCoordinate(-122.322355, 47.615146));
+    // 47.621821, -122.336747
+    memberCoordinateMap.put("Brave Horse Tavern", new GeoCoordinate(-122.336747, 47.621821));
+
+    // 47.616580, -122.200777
+    memberCoordinateMap.put("Earl's", new GeoCoordinate(-122.200777, 47.616580));
+    // 47.615165, -122.201317
+    memberCoordinateMap.put("Wild Ginger", new GeoCoordinate(-122.201317, 47.615165));
+
+    Long l = jedis.geoadd("Seattle", memberCoordinateMap);
+    assertTrue(l == 13L);
+
+    List<GeoRadiusResponse> gr = jedis.georadiusByMember("Seattle", "Galvanize", 0.2, GeoUnit.MI);
+    assertEquals(4, gr.size());
+    assertTrue(gr.stream().anyMatch(r -> r.getMemberByString().equals("Flatstick")));
+    assertTrue(gr.stream().anyMatch(r -> r.getMemberByString().equals("Fuel Sports")));
+    assertTrue(gr.stream().anyMatch(r -> r.getMemberByString().equals("Central Saloon")));
+    assertTrue(gr.stream().anyMatch(r -> r.getMemberByString().equals("Cowgirls")));
+
+    gr = jedis.georadiusByMember("Seattle", "Galvanize", 1.2, GeoUnit.MI);
+    assertEquals(8, gr.size());
+    assertTrue(gr.stream().anyMatch(r -> r.getMemberByString().equals("Jarrbar")));
+    assertTrue(gr.stream().anyMatch(r -> r.getMemberByString().equals("Oliver's lounge")));
+    assertTrue(gr.stream().anyMatch(r -> r.getMemberByString().equals("Garage")));
+    assertTrue(gr.stream().anyMatch(r -> r.getMemberByString().equals("Ba Bar")));
+
+    gr = jedis.georadiusByMember("Seattle", "Galvanize", 2.0, GeoUnit.MI);
+    assertEquals(10, gr.size());
+    assertTrue(gr.stream().anyMatch(r -> r.getMemberByString().equals("Bill's")));
+    assertTrue(gr.stream().anyMatch(r -> r.getMemberByString().equals("Brave Horse Tavern")));
+
+    gr = jedis.georadiusByMember("Seattle", "Galvanize", 10.0, GeoUnit.MI);
+    assertEquals(12, gr.size());
+    assertTrue(gr.stream().anyMatch(r -> r.getMemberByString().equals("Earl's")));
+    assertTrue(gr.stream().anyMatch(r -> r.getMemberByString().equals("Wild Ginger")));
+  }
+
+  @Test
+  public void testGeoRadiusByMemberNorth() {
+    Map<String, GeoCoordinate> memberCoordinateMap = new HashMap<>();
+    // 66.084124, -18.592468
+    memberCoordinateMap.put("Northern Iceland", new GeoCoordinate(-18.592468, 66.084124));
+    // 64.203491, -51.726331
+    memberCoordinateMap.put("Nuuk", new GeoCoordinate(-51.726331, 64.203491));
+    // 55.861822, -4.237179
+    memberCoordinateMap.put("Glasgow", new GeoCoordinate(-4.237179, 55.861822));
+    // 51.585005, -0.159837
+    memberCoordinateMap.put("London", new GeoCoordinate(-0.159837, 51.585005));
+    // 59.933071, 10.769027
+    memberCoordinateMap.put("Oslo", new GeoCoordinate(10.769027, 59.933071));
+
+    Long l = jedis.geoadd("North", memberCoordinateMap);
+    assertTrue(l == 5L);
+
+    List<GeoRadiusResponse> gr = jedis.georadiusByMember("North", "Northern Iceland", 1590.0,
+        GeoUnit.KM, GeoRadiusParam.geoRadiusParam().withDist());
+
+    assertEquals(2, gr.size());
+    assertTrue(gr.stream().anyMatch(r -> r.getMemberByString().equals("Nuuk")));
+    assertTrue(gr.stream().anyMatch(r -> r.getMemberByString().equals("Glasgow")));
+
+    gr = jedis.georadiusByMember("North", "Northern Iceland", 3000.0,
+        GeoUnit.KM, GeoRadiusParam.geoRadiusParam().withDist());
+    assertEquals(4, gr.size());
+    assertTrue(gr.stream().anyMatch(r -> r.getMemberByString().equals("Nuuk")));
+    assertTrue(gr.stream().anyMatch(r -> r.getMemberByString().equals("Glasgow")));
+    assertTrue(gr.stream().anyMatch(r -> r.getMemberByString().equals("Oslo")));
+    assertTrue(gr.stream().anyMatch(r -> r.getMemberByString().equals("London")));
+  }
+
+  @Test
+  public void testGeoRadiusByMemberInvalid() {
+    Map<String, GeoCoordinate> memberCoordinateMap = new HashMap<>();
+    memberCoordinateMap.put("Palermo", new GeoCoordinate(13.361389, 38.115556));
+    memberCoordinateMap.put("Catania", new GeoCoordinate(15.087269, 37.502669));
+    Long l = jedis.geoadd("Sicily", memberCoordinateMap);
+    assertTrue(l == 2L);
+
+    Exception ex = null;
+    try {
+      jedis.georadiusByMember("Sicily", "Roma", 91.0, GeoUnit.KM);
+    } catch (Exception e) {
+      ex = e;
+    }
+
+    assertNotNull(ex);
+    assertTrue(ex.getMessage().contains("could not decode requested zset member"));
+  }
+
+  @After
+  public void flushAll() {
+    jedis.flushAll();
+  }
+
+  @AfterClass
+  public static void tearDown() {
+    jedis.close();
+    cache.close();
+    server.shutdown();
+  }
+}
diff --git a/geode-core/src/main/java/org/apache/geode/redis/internal/Coder.java b/geode-core/src/main/java/org/apache/geode/redis/internal/Coder.java
index 44e8447..00d5619 100644
--- a/geode-core/src/main/java/org/apache/geode/redis/internal/Coder.java
+++ b/geode-core/src/main/java/org/apache/geode/redis/internal/Coder.java
@@ -17,7 +17,6 @@ package org.apache.geode.redis.internal;
 import java.io.UnsupportedEncodingException;
 import java.text.DecimalFormat;
 import java.util.Collection;
-import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
@@ -93,6 +92,7 @@ public class Coder {
   public static final String CHARSET = "UTF-8";
 
   protected static final DecimalFormat decimalFormatter = new DecimalFormat("#");
+
   static {
     decimalFormatter.setMaximumFractionDigits(10);
   }
@@ -107,73 +107,63 @@ public class Coder {
    */
   public static final String N_INF = "-inf";
 
-  public static ByteBuf getBulkStringResponse(ByteBufAllocator alloc, byte[] value) {
-    ByteBuf response = alloc.buffer(value.length + 20);
-    response.writeByte(BULK_STRING_ID);
-    response.writeBytes(intToBytes(value.length));
-    response.writeBytes(CRLFar);
-    response.writeBytes(value);
-    response.writeBytes(CRLFar);
-    return response;
-  }
+  public static ByteBuf getBulkStringResponse(ByteBufAllocator alloc, Object v)
+      throws CoderException {
+    ByteBuf response;
+    byte[] toWrite;
 
-  public static ByteBuf getBulkStringResponse(ByteBufAllocator alloc, double value) {
-    ByteBuf response = alloc.buffer();
-    byte[] doub = doubleToBytes(value);
-    response.writeByte(BULK_STRING_ID);
-    response.writeBytes(intToBytes(doub.length));
-    response.writeBytes(CRLFar);
-    response.writeBytes(doub);
-    response.writeBytes(CRLFar);
-    return response;
-  }
+    if (v == null) {
+      response = alloc.buffer();
+      response.writeBytes(bNIL);
+      return response;
+    } else if (v instanceof byte[]) {
+      byte[] value = (byte[]) v;
+      response = alloc.buffer(value.length + 20);
+      toWrite = value;
+    } else if (v instanceof ByteArrayWrapper) {
+      byte[] value = ((ByteArrayWrapper) v).toBytes();
+      response = alloc.buffer(value.length + 20);
+      toWrite = value;
+    } else if (v instanceof Double) {
+      response = alloc.buffer();
+      toWrite = doubleToBytes(((Double) v).doubleValue());
+    } else if (v instanceof String) {
+      String value = (String) v;
+      response = alloc.buffer(value.length() + 20);
+      toWrite = stringToBytes(value);
+    } else {
+      throw new CoderException();
+    }
 
-  public static ByteBuf getBulkStringResponse(ByteBufAllocator alloc, String value) {
-    byte[] valueAr = stringToBytes(value);
-    int length = valueAr == null ? 0 : valueAr.length;
-    ByteBuf response = alloc.buffer(length + 20);
     response.writeByte(BULK_STRING_ID);
-    response.writeBytes(intToBytes(length));
+    response.writeBytes(intToBytes(toWrite.length));
     response.writeBytes(CRLFar);
-    response.writeBytes(valueAr);
+    response.writeBytes(toWrite);
     response.writeBytes(CRLFar);
-    return response;
-  }
 
-  public static ByteBuf getBulkStringArrayResponse(ByteBufAllocator alloc, List<String> items) {
-    Iterator<String> it = items.iterator();
-    ByteBuf response = alloc.buffer();
-    response.writeByte(ARRAY_ID);
-    response.writeBytes(intToBytes(items.size()));
-    response.writeBytes(CRLFar);
-    while (it.hasNext()) {
-      String next = it.next();
-      response.writeByte(BULK_STRING_ID);
-      response.writeBytes(intToBytes(next.length()));
-      response.writeBytes(CRLFar);
-      response.writeBytes(stringToBytes(next));
-      response.writeBytes(CRLFar);
-    }
     return response;
   }
 
-  public static ByteBuf getBulkStringArrayResponse(ByteBufAllocator alloc,
-      Collection<ByteArrayWrapper> items) {
-    Iterator<ByteArrayWrapper> it = items.iterator();
+  public static ByteBuf getBulkStringArrayResponse(ByteBufAllocator alloc, Collection<?> items)
+      throws CoderException {
     ByteBuf response = alloc.buffer();
     response.writeByte(ARRAY_ID);
     response.writeBytes(intToBytes(items.size()));
     response.writeBytes(CRLFar);
-    while (it.hasNext()) {
-      ByteArrayWrapper nextWrapper = it.next();
-      if (nextWrapper != null) {
-        response.writeByte(BULK_STRING_ID);
-        response.writeBytes(intToBytes(nextWrapper.length()));
-        response.writeBytes(CRLFar);
-        response.writeBytes(nextWrapper.toBytes());
-        response.writeBytes(CRLFar);
-      } else
-        response.writeBytes(getNilResponse(alloc));
+    for (Object next : items) {
+      ByteBuf tmp = null;
+      try {
+        if (next instanceof Collection) {
+          Collection<?> nextItems = (Collection<?>) next;
+          tmp = getBulkStringArrayResponse(alloc, nextItems);
+          response.writeBytes(tmp);
+        } else {
+          tmp = getBulkStringResponse(alloc, next);
+          response.writeBytes(tmp);
+        }
+      } finally {
+        tmp.release();
+      }
     }
 
     return response;
@@ -181,14 +171,12 @@ public class Coder {
 
   public static ByteBuf getKeyValArrayResponse(ByteBufAllocator alloc,
       Collection<Entry<ByteArrayWrapper, ByteArrayWrapper>> items) {
-    Iterator<Map.Entry<ByteArrayWrapper, ByteArrayWrapper>> it = items.iterator();
     ByteBuf response = alloc.buffer();
     response.writeByte(ARRAY_ID);
 
     int size = 0;
     ByteBuf tmp = alloc.buffer();
-    while (it.hasNext()) {
-      Map.Entry<ByteArrayWrapper, ByteArrayWrapper> next = it.next();
+    for (Map.Entry<ByteArrayWrapper, ByteArrayWrapper> next : items) {
       byte[] key;
       byte[] nextByteArray;
       try {
@@ -221,6 +209,7 @@ public class Coder {
 
   public static ByteBuf getScanResponse(ByteBufAllocator alloc, List<?> items) {
     ByteBuf response = alloc.buffer();
+
     response.writeByte(ARRAY_ID);
     response.writeBytes(intToBytes(2));
     response.writeBytes(CRLFar);
@@ -231,13 +220,11 @@ public class Coder {
     response.writeBytes(cursor);
     response.writeBytes(CRLFar);
     items = items.subList(1, items.size());
-    Iterator<?> it = items.iterator();
     response.writeByte(ARRAY_ID);
     response.writeBytes(intToBytes(items.size()));
     response.writeBytes(CRLFar);
 
-    while (it.hasNext()) {
-      Object nextObject = it.next();
+    for (Object nextObject : items) {
       if (nextObject instanceof String) {
         String next = (String) nextObject;
         response.writeByte(BULK_STRING_ID);
@@ -325,40 +312,40 @@ public class Coder {
 
   public static ByteBuf getBulkStringArrayResponseOfValues(ByteBufAllocator alloc,
       Collection<?> items) {
-    Iterator<?> it = items.iterator();
     ByteBuf response = alloc.buffer();
     response.writeByte(Coder.ARRAY_ID);
     ByteBuf tmp = alloc.buffer();
     int size = 0;
-    while (it.hasNext()) {
-      Object next = it.next();
-      ByteArrayWrapper nextWrapper = null;
-      if (next instanceof Entry) {
-        try {
-          nextWrapper = (ByteArrayWrapper) ((Entry<?, ?>) next).getValue();
-        } catch (EntryDestroyedException e) {
-          continue;
+    try {
+      for (Object next : items) {
+        ByteArrayWrapper nextWrapper = null;
+        if (next instanceof Entry) {
+          try {
+            nextWrapper = (ByteArrayWrapper) ((Entry<?, ?>) next).getValue();
+          } catch (EntryDestroyedException e) {
+            continue;
+          }
+        } else if (next instanceof Struct) {
+          nextWrapper = (ByteArrayWrapper) ((Struct) next).getFieldValues()[1];
         }
-      } else if (next instanceof Struct) {
-        nextWrapper = (ByteArrayWrapper) ((Struct) next).getFieldValues()[1];
-      }
-      if (nextWrapper != null) {
-        tmp.writeByte(Coder.BULK_STRING_ID);
-        tmp.writeBytes(intToBytes(nextWrapper.length()));
-        tmp.writeBytes(Coder.CRLFar);
-        tmp.writeBytes(nextWrapper.toBytes());
-        tmp.writeBytes(Coder.CRLFar);
-      } else {
-        tmp.writeBytes(Coder.bNIL);
+        if (nextWrapper != null) {
+          tmp.writeByte(Coder.BULK_STRING_ID);
+          tmp.writeBytes(intToBytes(nextWrapper.length()));
+          tmp.writeBytes(Coder.CRLFar);
+          tmp.writeBytes(nextWrapper.toBytes());
+          tmp.writeBytes(Coder.CRLFar);
+        } else {
+          tmp.writeBytes(Coder.bNIL);
+        }
+        size++;
       }
-      size++;
-    }
 
-    response.writeBytes(intToBytes(size));
-    response.writeBytes(Coder.CRLFar);
-    response.writeBytes(tmp);
-
-    tmp.release();
+      response.writeBytes(intToBytes(size));
+      response.writeBytes(Coder.CRLFar);
+      response.writeBytes(tmp);
+    } finally {
+      tmp.release();
+    }
 
     return response;
   }
diff --git a/geode-core/src/main/java/org/apache/geode/redis/internal/executor/EchoExecutor.java b/geode-core/src/main/java/org/apache/geode/redis/internal/CoderException.java
old mode 100755
new mode 100644
similarity index 50%
copy from geode-core/src/main/java/org/apache/geode/redis/internal/executor/EchoExecutor.java
copy to geode-core/src/main/java/org/apache/geode/redis/internal/CoderException.java
index 407e653..7de1116
--- a/geode-core/src/main/java/org/apache/geode/redis/internal/executor/EchoExecutor.java
+++ b/geode-core/src/main/java/org/apache/geode/redis/internal/CoderException.java
@@ -12,27 +12,25 @@
  * or implied. See the License for the specific language governing permissions and limitations under
  * the License.
  */
-package org.apache.geode.redis.internal.executor;
 
-import java.util.List;
+package org.apache.geode.redis.internal;
 
-import org.apache.geode.redis.internal.Coder;
-import org.apache.geode.redis.internal.Command;
-import org.apache.geode.redis.internal.ExecutionHandlerContext;
-import org.apache.geode.redis.internal.RedisConstants.ArityDef;
+public class CoderException extends Exception {
+  private static final long serialVersionUID = 4707944288714910949L;
 
-public class EchoExecutor extends AbstractExecutor {
+  public CoderException() {
+    super();
+  }
 
-  @Override
-  public void executeCommand(Command command, ExecutionHandlerContext context) {
-    List<byte[]> commandElems = command.getProcessedCommand();
-    if (commandElems.size() < 2) {
-      command.setResponse(Coder.getErrorResponse(context.getByteBufAllocator(), ArityDef.ECHO));
-      return;
-    }
+  public CoderException(String message) {
+    super(message);
+  }
 
-    byte[] echoMessage = commandElems.get(1);
-    command.setResponse(Coder.getBulkStringResponse(context.getByteBufAllocator(), echoMessage));
+  public CoderException(Throwable cause) {
+    super(cause);
   }
 
+  public CoderException(String message, Throwable cause) {
+    super(message, cause);
+  }
 }
diff --git a/geode-core/src/main/java/org/apache/geode/redis/internal/GeoCoder.java b/geode-core/src/main/java/org/apache/geode/redis/internal/GeoCoder.java
new file mode 100644
index 0000000..f71dc2e
--- /dev/null
+++ b/geode-core/src/main/java/org/apache/geode/redis/internal/GeoCoder.java
@@ -0,0 +1,213 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The ASF licenses this file to You 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
+ *
+ * http://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 org.apache.geode.redis.internal;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+
+import com.github.davidmoten.geo.GeoHash;
+import com.github.davidmoten.geo.LatLong;
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufAllocator;
+
+public class GeoCoder {
+
+  /**
+   * Earth radius for distance calculations.
+   */
+  private static final double EARTH_RADIUS_IN_METERS = 6372797.560856;
+
+  public static ByteBuf getBulkStringGeoCoordinateArrayResponse(ByteBufAllocator alloc,
+      Collection<LatLong> items)
+      throws CoderException {
+    ByteBuf response = alloc.buffer();
+    response.writeByte(Coder.ARRAY_ID);
+    ByteBuf tmp = alloc.buffer();
+    int size = 0;
+    try {
+      for (LatLong next : items) {
+        if (next == null) {
+          tmp.writeBytes(Coder.bNIL);
+        } else {
+          tmp.writeBytes(Coder.getBulkStringArrayResponse(alloc,
+              Arrays.asList(
+                  Double.toString(next.getLon()),
+                  Double.toString(next.getLat()))));
+        }
+        size++;
+      }
+
+      response.writeBytes(Coder.intToBytes(size));
+      response.writeBytes(Coder.CRLFar);
+      response.writeBytes(tmp);
+    } finally {
+      tmp.release();
+    }
+
+    return response;
+  }
+
+  public static ByteBuf geoRadiusResponse(ByteBufAllocator alloc,
+      Collection<GeoRadiusResponseElement> list)
+      throws CoderException {
+    if (list.isEmpty())
+      return Coder.getEmptyArrayResponse(alloc);
+
+    List<Object> responseElements = new ArrayList<>();
+    for (GeoRadiusResponseElement element : list) {
+      String name = element.getName();
+
+      String distStr = "";
+      if (element.isShowDist()) {
+        distStr = element.getDistFromCenter().toString();
+      }
+
+      List<String> coord = new ArrayList<>();
+      if (element.getCoord().isPresent()) {
+        coord.add(Double.toString(element.getCoord().get().getLon()));
+        coord.add(Double.toString(element.getCoord().get().getLat()));
+      }
+
+      String hash = "";
+      if (element.getHash().isPresent()) {
+        hash = element.getHash().get();
+      }
+
+      if (distStr != "" || !coord.isEmpty() || hash != "") {
+        List<Object> elementData = new ArrayList<>();
+        elementData.add(name);
+        if (distStr != "")
+          elementData.add(distStr);
+        if (!coord.isEmpty())
+          elementData.add(coord);
+        if (hash != "")
+          elementData.add(hash);
+
+        responseElements.add(elementData);
+      } else {
+        responseElements.add(name);
+      }
+    }
+
+    return Coder.getBulkStringArrayResponse(alloc, responseElements);
+  }
+
+  /**
+   * Converts geohash to lat/long.
+   *
+   * @param hash geohash as base32
+   * @return a LatLong object containing the coordinates
+   */
+  public static LatLong geoPos(String hash) {
+    return GeoHash.decodeHash(hash);
+  }
+
+  /**
+   * Calculates distance between two points.
+   *
+   * @param hash1 geohash of first point
+   * @param hash2 geohash of second point
+   * @return distance in meters
+   */
+  public static Double geoDist(String hash1, String hash2) {
+    LatLong coord1 = geoPos(hash1);
+    LatLong coord2 = geoPos(hash2);
+
+    Double lat1 = Math.toRadians(coord1.getLat());
+    Double long1 = Math.toRadians(coord1.getLon());
+    Double lat2 = Math.toRadians(coord2.getLat());
+    Double long2 = Math.toRadians(coord2.getLon());
+
+    return dist(long1, lat1, long2, lat2);
+  }
+
+  /**
+   * Calculates geohash given latitude and longitude as byte arrays encoding decimals.
+   *
+   * @param lon byte array encoding longitude as decimal
+   * @param lat byte array encoding latitude as decimal
+   * @return geohash as base32
+   */
+  public static String geohash(byte[] lon, byte[] lat) throws IllegalArgumentException {
+    Double longitude = Coder.bytesToDouble(lon);
+    Double latitude = Coder.bytesToDouble(lat);
+    return GeoHash.encodeHash(latitude, longitude);
+  }
+
+  public static Set<String> geohashSearchAreas(double longitude, double latitude,
+      double radiusMeters) {
+    HashArea boundingBox = boundingBox(longitude, latitude, radiusMeters);
+    int steps =
+        Math.max(1, GeoHash.hashLengthToCoverBoundingBox(boundingBox.maxlat, boundingBox.maxlon,
+            boundingBox.minlat, boundingBox.minlon));
+
+    List<String> extra = new ArrayList<>();
+    // Large distance boundary condition
+    if (steps == 1) {
+      extra.addAll(GeoHash.neighbours(GeoHash.encodeHash(latitude, longitude, steps)));
+    }
+
+    Set<String> areas = GeoHash.coverBoundingBox(boundingBox.maxlat, boundingBox.maxlon,
+        boundingBox.minlat, boundingBox.minlon, steps).getHashes();
+    if (!extra.isEmpty()) {
+      extra.forEach(ex -> areas.add(ex));
+    }
+
+    return areas;
+  }
+
+  public static HashArea boundingBox(double longitude, double latitude,
+      double radiusMeters) {
+    double minlon = longitude - Math
+        .toDegrees((radiusMeters / EARTH_RADIUS_IN_METERS) * Math.cos(Math.toRadians(latitude)));
+    double maxlon = longitude + Math
+        .toDegrees((radiusMeters / EARTH_RADIUS_IN_METERS) * Math.cos(Math.toRadians(latitude)));
+    double minlat = latitude - Math.toDegrees(radiusMeters / EARTH_RADIUS_IN_METERS);
+    double maxlat = latitude + Math.toDegrees(radiusMeters / EARTH_RADIUS_IN_METERS);
+
+    return new HashArea(minlon, maxlon, minlat, maxlat);
+  }
+
+  public static Double dist(Double long1, Double lat1, Double long2, Double lat2) {
+    Double hav =
+        haversine(lat2 - lat1) + (Math.cos(lat1) * Math.cos(lat2) * haversine(long2 - long1));
+    Double distAngle = Math.acos(1 - (2 * hav));
+
+    return EARTH_RADIUS_IN_METERS * distAngle;
+  }
+
+  public static double haversine(Double rad) {
+    return 0.5 * (1 - Math.cos(rad));
+  }
+
+  public static double parseUnitScale(String unit) throws IllegalArgumentException {
+    switch (unit) {
+      case "km":
+        return 0.001;
+      case "m":
+        return 1.0;
+      case "ft":
+        return 3.28084;
+      case "mi":
+        return 0.000621371;
+      default:
+        throw new IllegalArgumentException();
+    }
+  }
+}
diff --git a/geode-core/src/main/java/org/apache/geode/redis/internal/executor/EchoExecutor.java b/geode-core/src/main/java/org/apache/geode/redis/internal/GeoCoord.java
old mode 100755
new mode 100644
similarity index 50%
copy from geode-core/src/main/java/org/apache/geode/redis/internal/executor/EchoExecutor.java
copy to geode-core/src/main/java/org/apache/geode/redis/internal/GeoCoord.java
index 407e653..c3e31a4
--- a/geode-core/src/main/java/org/apache/geode/redis/internal/executor/EchoExecutor.java
+++ b/geode-core/src/main/java/org/apache/geode/redis/internal/GeoCoord.java
@@ -12,27 +12,23 @@
  * or implied. See the License for the specific language governing permissions and limitations under
  * the License.
  */
-package org.apache.geode.redis.internal.executor;
 
-import java.util.List;
+package org.apache.geode.redis.internal;
 
-import org.apache.geode.redis.internal.Coder;
-import org.apache.geode.redis.internal.Command;
-import org.apache.geode.redis.internal.ExecutionHandlerContext;
-import org.apache.geode.redis.internal.RedisConstants.ArityDef;
+public class GeoCoord {
+  double longitude;
+  double latitude;
 
-public class EchoExecutor extends AbstractExecutor {
-
-  @Override
-  public void executeCommand(Command command, ExecutionHandlerContext context) {
-    List<byte[]> commandElems = command.getProcessedCommand();
-    if (commandElems.size() < 2) {
-      command.setResponse(Coder.getErrorResponse(context.getByteBufAllocator(), ArityDef.ECHO));
-      return;
-    }
+  public GeoCoord(double lon, double lat) {
+    this.longitude = lon;
+    this.latitude = lat;
+  }
 
-    byte[] echoMessage = commandElems.get(1);
-    command.setResponse(Coder.getBulkStringResponse(context.getByteBufAllocator(), echoMessage));
+  public double getLongitude() {
+    return longitude;
   }
 
+  public double getLatitude() {
+    return latitude;
+  }
 }
diff --git a/geode-core/src/main/java/org/apache/geode/redis/internal/GeoRadiusResponseElement.java b/geode-core/src/main/java/org/apache/geode/redis/internal/GeoRadiusResponseElement.java
new file mode 100644
index 0000000..4e0dd81
--- /dev/null
+++ b/geode-core/src/main/java/org/apache/geode/redis/internal/GeoRadiusResponseElement.java
@@ -0,0 +1,68 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The ASF licenses this file to You 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
+ *
+ * http://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 org.apache.geode.redis.internal;
+
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Optional;
+
+import com.github.davidmoten.geo.LatLong;
+
+public class GeoRadiusResponseElement {
+  private String name;
+  private Optional<LatLong> coord;
+  private Double distFromCenter;
+  private boolean showDist;
+  private Optional<String> hash;
+
+  public String getName() {
+    return name;
+  }
+
+  public Optional<LatLong> getCoord() {
+    return coord;
+  }
+
+  public Double getDistFromCenter() {
+    return distFromCenter;
+  }
+
+  public boolean isShowDist() {
+    return showDist;
+  }
+
+  public Optional<String> getHash() {
+    return hash;
+  }
+
+  public GeoRadiusResponseElement(String n, Optional<LatLong> c, Double d, boolean sh,
+      Optional<String> h) {
+    this.name = n;
+    this.coord = c;
+    this.distFromCenter = d;
+    this.showDist = sh;
+    this.hash = h;
+  }
+
+  public static void sortByDistanceAscending(List<GeoRadiusResponseElement> elements) {
+    Collections.sort(elements, Comparator.comparing(GeoRadiusResponseElement::getDistFromCenter));
+  }
+
+  public static void sortByDistanceDescending(List<GeoRadiusResponseElement> elements) {
+    Collections.sort(elements,
+        Comparator.comparing((GeoRadiusResponseElement x) -> -1.0 * x.getDistFromCenter()));
+  }
+}
diff --git a/geode-core/src/main/java/org/apache/geode/redis/internal/HashArea.java b/geode-core/src/main/java/org/apache/geode/redis/internal/HashArea.java
new file mode 100644
index 0000000..4a1c7cb
--- /dev/null
+++ b/geode-core/src/main/java/org/apache/geode/redis/internal/HashArea.java
@@ -0,0 +1,30 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The ASF licenses this file to You 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
+ *
+ * http://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 org.apache.geode.redis.internal;
+
+public class HashArea {
+  public Double minlon;
+  public Double maxlon;
+  public Double minlat;
+  public Double maxlat;
+
+  public HashArea(double minlon, double maxlon, double minlat, double maxlat) {
+    this.minlon = minlon;
+    this.maxlon = maxlon;
+    this.minlat = minlat;
+    this.maxlat = maxlat;
+  }
+}
diff --git a/geode-core/src/main/java/org/apache/geode/redis/internal/executor/EchoExecutor.java b/geode-core/src/main/java/org/apache/geode/redis/internal/HashNeighbors.java
old mode 100755
new mode 100644
similarity index 51%
copy from geode-core/src/main/java/org/apache/geode/redis/internal/executor/EchoExecutor.java
copy to geode-core/src/main/java/org/apache/geode/redis/internal/HashNeighbors.java
index 407e653..be39f40
--- a/geode-core/src/main/java/org/apache/geode/redis/internal/executor/EchoExecutor.java
+++ b/geode-core/src/main/java/org/apache/geode/redis/internal/HashNeighbors.java
@@ -12,27 +12,30 @@
  * or implied. See the License for the specific language governing permissions and limitations under
  * the License.
  */
-package org.apache.geode.redis.internal.executor;
 
-import java.util.List;
-
-import org.apache.geode.redis.internal.Coder;
-import org.apache.geode.redis.internal.Command;
-import org.apache.geode.redis.internal.ExecutionHandlerContext;
-import org.apache.geode.redis.internal.RedisConstants.ArityDef;
+package org.apache.geode.redis.internal;
 
-public class EchoExecutor extends AbstractExecutor {
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
 
-  @Override
-  public void executeCommand(Command command, ExecutionHandlerContext context) {
-    List<byte[]> commandElems = command.getProcessedCommand();
-    if (commandElems.size() < 2) {
-      command.setResponse(Coder.getErrorResponse(context.getByteBufAllocator(), ArityDef.ECHO));
-      return;
-    }
+public class HashNeighbors {
+  public String center;
+  public String west;
+  public String east;
+  public String north;
+  public String south;
+  public String northwest;
+  public String northeast;
+  public String southwest;
+  public String southeast;
 
-    byte[] echoMessage = commandElems.get(1);
-    command.setResponse(Coder.getBulkStringResponse(context.getByteBufAllocator(), echoMessage));
+  public List<String> get() {
+    return Arrays
+        .asList(center, west, east, north, south, northwest, northeast, southwest, southeast)
+        .stream()
+        .filter(n -> n != null)
+        .distinct()
+        .collect(Collectors.toList());
   }
-
 }
diff --git a/geode-core/src/main/java/org/apache/geode/redis/internal/executor/EchoExecutor.java b/geode-core/src/main/java/org/apache/geode/redis/internal/MemberNotFoundException.java
old mode 100755
new mode 100644
similarity index 50%
copy from geode-core/src/main/java/org/apache/geode/redis/internal/executor/EchoExecutor.java
copy to geode-core/src/main/java/org/apache/geode/redis/internal/MemberNotFoundException.java
index 407e653..0492098
--- a/geode-core/src/main/java/org/apache/geode/redis/internal/executor/EchoExecutor.java
+++ b/geode-core/src/main/java/org/apache/geode/redis/internal/MemberNotFoundException.java
@@ -12,27 +12,26 @@
  * or implied. See the License for the specific language governing permissions and limitations under
  * the License.
  */
-package org.apache.geode.redis.internal.executor;
 
-import java.util.List;
 
-import org.apache.geode.redis.internal.Coder;
-import org.apache.geode.redis.internal.Command;
-import org.apache.geode.redis.internal.ExecutionHandlerContext;
-import org.apache.geode.redis.internal.RedisConstants.ArityDef;
+package org.apache.geode.redis.internal;
 
-public class EchoExecutor extends AbstractExecutor {
+public class MemberNotFoundException extends Exception {
+  private static final long serialVersionUID = 4707944288714910949L;
 
-  @Override
-  public void executeCommand(Command command, ExecutionHandlerContext context) {
-    List<byte[]> commandElems = command.getProcessedCommand();
-    if (commandElems.size() < 2) {
-      command.setResponse(Coder.getErrorResponse(context.getByteBufAllocator(), ArityDef.ECHO));
-      return;
-    }
+  public MemberNotFoundException() {
+    super();
+  }
+
+  public MemberNotFoundException(String message) {
+    super(message);
+  }
 
-    byte[] echoMessage = commandElems.get(1);
-    command.setResponse(Coder.getBulkStringResponse(context.getByteBufAllocator(), echoMessage));
+  public MemberNotFoundException(Throwable cause) {
+    super(cause);
   }
 
+  public MemberNotFoundException(String message, Throwable cause) {
+    super(message, cause);
+  }
 }
diff --git a/geode-core/src/main/java/org/apache/geode/redis/internal/Pair.java b/geode-core/src/main/java/org/apache/geode/redis/internal/Pair.java
new file mode 100644
index 0000000..aaff10e
--- /dev/null
+++ b/geode-core/src/main/java/org/apache/geode/redis/internal/Pair.java
@@ -0,0 +1,30 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The ASF licenses this file to You 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
+ *
+ * http://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 org.apache.geode.redis.internal;
+
+public class Pair<T, U> {
+  T fst;
+  U snd;
+
+  Pair(Object fst, Object snd) {
+    this.fst = (T) fst;
+    this.snd = (U) snd;
+  }
+
+  public static Pair of(Object fst, Object snd) {
+    return new Pair(fst, snd);
+  }
+}
diff --git a/geode-core/src/main/java/org/apache/geode/redis/internal/RedisCommandType.java b/geode-core/src/main/java/org/apache/geode/redis/internal/RedisCommandType.java
index a4a42ad..c2625eb 100755
--- a/geode-core/src/main/java/org/apache/geode/redis/internal/RedisCommandType.java
+++ b/geode-core/src/main/java/org/apache/geode/redis/internal/RedisCommandType.java
@@ -81,6 +81,12 @@ import org.apache.geode.redis.internal.executor.set.SRemExecutor;
 import org.apache.geode.redis.internal.executor.set.SScanExecutor;
 import org.apache.geode.redis.internal.executor.set.SUnionExecutor;
 import org.apache.geode.redis.internal.executor.set.SUnionStoreExecutor;
+import org.apache.geode.redis.internal.executor.sortedset.GeoAddExecutor;
+import org.apache.geode.redis.internal.executor.sortedset.GeoDistExecutor;
+import org.apache.geode.redis.internal.executor.sortedset.GeoHashExecutor;
+import org.apache.geode.redis.internal.executor.sortedset.GeoPosExecutor;
+import org.apache.geode.redis.internal.executor.sortedset.GeoRadiusByMemberExecutor;
+import org.apache.geode.redis.internal.executor.sortedset.GeoRadiusExecutor;
 import org.apache.geode.redis.internal.executor.sortedset.ZAddExecutor;
 import org.apache.geode.redis.internal.executor.sortedset.ZCardExecutor;
 import org.apache.geode.redis.internal.executor.sortedset.ZCountExecutor;
@@ -2589,6 +2595,125 @@ public enum RedisCommandType {
     }
   },
 
+
+  /**************************************
+   * Geospatial commands ****************
+   **************************************/
+
+  GEOADD {
+    private Executor executor;
+
+    @Override
+    public Executor getExecutor() {
+      if (executor == null) {
+        executor = new GeoAddExecutor();
+      }
+      return executor;
+    }
+
+    private final RedisDataType dataType = RedisDataType.REDIS_SORTEDSET;
+
+    @Override
+    public RedisDataType getDataType() {
+      return this.dataType;
+    }
+  },
+
+  GEOHASH {
+    private Executor executor;
+
+    @Override
+    public Executor getExecutor() {
+      if (executor == null) {
+        executor = new GeoHashExecutor();
+      }
+      return executor;
+    }
+
+    private final RedisDataType dataType = RedisDataType.REDIS_SORTEDSET;
+
+    @Override
+    public RedisDataType getDataType() {
+      return this.dataType;
+    }
+  },
+
+  GEOPOS {
+    private Executor executor;
+
+    @Override
+    public Executor getExecutor() {
+      if (executor == null) {
+        executor = new GeoPosExecutor();
+      }
+      return executor;
+    }
+
+    private final RedisDataType dataType = RedisDataType.REDIS_SORTEDSET;
+
+    @Override
+    public RedisDataType getDataType() {
+      return this.dataType;
+    }
+  },
+
+  GEODIST {
+    private Executor executor;
+
+    @Override
+    public Executor getExecutor() {
+      if (executor == null) {
+        executor = new GeoDistExecutor();
+      }
+      return executor;
+    }
+
+    private final RedisDataType dataType = RedisDataType.REDIS_SORTEDSET;
+
+    @Override
+    public RedisDataType getDataType() {
+      return this.dataType;
+    }
+  },
+
+  GEORADIUS {
+    private Executor executor;
+
+    @Override
+    public Executor getExecutor() {
+      if (executor == null) {
+        executor = new GeoRadiusExecutor();
+      }
+      return executor;
+    }
+
+    private final RedisDataType dataType = RedisDataType.REDIS_SORTEDSET;
+
+    @Override
+    public RedisDataType getDataType() {
+      return this.dataType;
+    }
+  },
+
+  GEORADIUSBYMEMBER {
+    private Executor executor;
+
+    @Override
+    public Executor getExecutor() {
+      if (executor == null) {
+        executor = new GeoRadiusByMemberExecutor();
+      }
+      return executor;
+    }
+
+    private final RedisDataType dataType = RedisDataType.REDIS_SORTEDSET;
+
+    @Override
+    public RedisDataType getDataType() {
+      return this.dataType;
+    }
+  },
+
   /***************************************
    ************ Transactions *************
    ***************************************/
diff --git a/geode-core/src/main/java/org/apache/geode/redis/internal/RedisConstants.java b/geode-core/src/main/java/org/apache/geode/redis/internal/RedisConstants.java
index ad42dbb..ddfa639 100644
--- a/geode-core/src/main/java/org/apache/geode/redis/internal/RedisConstants.java
+++ b/geode-core/src/main/java/org/apache/geode/redis/internal/RedisConstants.java
@@ -31,7 +31,8 @@ public class RedisConstants {
    */
   static final String PARSING_EXCEPTION_MESSAGE =
       "The command received by GeodeRedisServer was improperly formatted";
-  static final String SERVER_ERROR_MESSAGE = "The server had an internal error please try again";
+  public static final String SERVER_ERROR_MESSAGE =
+      "The server had an internal error please try again";
   static final String SERVER_ERROR_UNKNOWN_RESPONSE = "Unkown response";
   static final String SERVER_ERROR_SHUTDOWN = "The server is shutting down";
   static final String ERROR_UNSUPPORTED_OPERATION_IN_TRANSACTION =
@@ -39,7 +40,10 @@ public class RedisConstants {
   static final String ERROR_TRANSACTION_EXCEPTION =
       "This transcation cannot be initiated, make sure the command is executed against a replicate region or your data is collocated. If you are using persistent regions, make sure transactions are enabled";
   public static final String ERROR_NOT_NUMERIC = "Illegal non numeric argument";
-  public static final String ERROR_UNKOWN_COMMAND = "Unable to process uknown command";
+  public static final String ERROR_INVALID_ARGUMENT_UNIT_NUM =
+      "Either illegal non numeric argument or invalid unit" +
+          "(please use either km/m/ft/mi)";
+  public static final String ERROR_UNKOWN_COMMAND = "Unable to process unknown command";
   public static final String ERROR_COMMIT_CONFLICT =
       "There has been a conflict with another transaction";
   public static final String ERROR_REGION_CREATION =
@@ -50,6 +54,7 @@ public class RedisConstants {
       "Keys cannot be watched or unwatched because GemFire watches all keys by default for transactions";
   public static final String ERROR_ILLEGAL_GLOB = "Incorrect syntax for given glob regex";
   public static final String ERROR_OUT_OF_RANGE = "The number provided is out of range";
+  public static final String ERROR_INVALID_LATLONG = "Invalid longitude-latitude pair";
   public static final String ERROR_NESTED_MULTI = "The MULTI command cannot be nested";
   public static final String ERROR_NAN_INF_INCR = "increment would produce NaN or Infinity";
   public static final String ERROR_NO_PASS =
@@ -57,6 +62,7 @@ public class RedisConstants {
   public static final String ERROR_INVALID_PWD =
       "Attemping to authenticate with an invalid password";
   public static final String ERROR_NOT_AUTH = "Must authenticate before sending any requests";
+  public static final String ERROR_ZSET_MEMBER_NOT_FOUND = "could not decode requested zset member";
 
   public static class ArityDef {
 
@@ -246,6 +252,22 @@ public class RedisConstants {
         "The wrong number of arguments or syntax was provided, the format for the ZSCORE command is \"ZSCORE key member\"";
 
     /*
+     * Geospatial
+     */
+    public static final String GEOADD =
+        "The wrong number of arguments or syntax was provided, the format for the GEOADD command is \"GEOADD key longitude latitude member [longitude latitude member ...]\", or not every latitude/longitude pair matches to a member";
+    public static final String GEOHASH =
+        "The wrong number of arguments or syntax was provided, the format for the GEOHASH command is \"GEOHASH key member [member...]\"";
+    public static final String GEOPOS =
+        "The wrong number of arguments or syntax was provided, the format for the GEOPOS command is \"GEOPOS key member [member...]\"";
+    public static final String GEODIST =
+        "The wrong number of arguments or syntax was provided, the format for the GEODIST command is \"GEODIST key member member [unit]\"";
+    public static final String GEORADIUS =
+        "The wrong number of arguments or syntax was provided, the format for the GEORADIUS command is \"GEORADIUS key longitude latitude radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] [ASC|DESC]\"";
+    public static final String GEORADIUSBYMEMBER =
+        "The wrong number of arguments or syntax was provided, the format for the GEORADIUSBYMEMBER command is \"GEORADIUSBYMEMBER key member radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] [ASC|DESC]\"";
+
+    /*
      * String
      */
     public static final String APPEND =
diff --git a/geode-core/src/main/java/org/apache/geode/redis/internal/executor/AbstractExecutor.java b/geode-core/src/main/java/org/apache/geode/redis/internal/executor/AbstractExecutor.java
index f95e1a4..dfe0668 100755
--- a/geode-core/src/main/java/org/apache/geode/redis/internal/executor/AbstractExecutor.java
+++ b/geode-core/src/main/java/org/apache/geode/redis/internal/executor/AbstractExecutor.java
@@ -14,12 +14,20 @@
  */
 package org.apache.geode.redis.internal.executor;
 
+import java.util.Collection;
+
+import io.netty.buffer.ByteBuf;
+
 import org.apache.geode.cache.Region;
 import org.apache.geode.cache.query.Query;
 import org.apache.geode.redis.GeodeRedisServer;
 import org.apache.geode.redis.internal.ByteArrayWrapper;
+import org.apache.geode.redis.internal.Coder;
+import org.apache.geode.redis.internal.CoderException;
+import org.apache.geode.redis.internal.Command;
 import org.apache.geode.redis.internal.ExecutionHandlerContext;
 import org.apache.geode.redis.internal.Executor;
+import org.apache.geode.redis.internal.RedisConstants;
 import org.apache.geode.redis.internal.RedisDataType;
 import org.apache.geode.redis.internal.RedisDataTypeMismatchException;
 import org.apache.geode.redis.internal.RegionProvider;
@@ -135,4 +143,23 @@ public abstract class AbstractExecutor implements Executor {
     else
       return Math.max(index + size, -1);
   }
+
+  protected void respondBulkStrings(Command command, ExecutionHandlerContext context,
+      Object message) {
+    ByteBuf rsp;
+    try {
+      if (message instanceof Collection) {
+        rsp = Coder.getBulkStringArrayResponse(context.getByteBufAllocator(),
+            (Collection<?>) message);
+      } else {
+        rsp = Coder.getBulkStringResponse(context.getByteBufAllocator(), message);
+      }
+    } catch (CoderException e) {
+      command.setResponse(Coder.getErrorResponse(context.getByteBufAllocator(),
+          RedisConstants.SERVER_ERROR_MESSAGE));
+      return;
+    }
+
+    command.setResponse(rsp);
+  }
 }
diff --git a/geode-core/src/main/java/org/apache/geode/redis/internal/executor/EchoExecutor.java b/geode-core/src/main/java/org/apache/geode/redis/internal/executor/EchoExecutor.java
index 407e653..3ad9885 100755
--- a/geode-core/src/main/java/org/apache/geode/redis/internal/executor/EchoExecutor.java
+++ b/geode-core/src/main/java/org/apache/geode/redis/internal/executor/EchoExecutor.java
@@ -32,7 +32,7 @@ public class EchoExecutor extends AbstractExecutor {
     }
 
     byte[] echoMessage = commandElems.get(1);
-    command.setResponse(Coder.getBulkStringResponse(context.getByteBufAllocator(), echoMessage));
+    respondBulkStrings(command, context, echoMessage);
   }
 
 }
diff --git a/geode-core/src/main/java/org/apache/geode/redis/internal/executor/KeysExecutor.java b/geode-core/src/main/java/org/apache/geode/redis/internal/executor/KeysExecutor.java
index cb11762..c4466fc 100755
--- a/geode-core/src/main/java/org/apache/geode/redis/internal/executor/KeysExecutor.java
+++ b/geode-core/src/main/java/org/apache/geode/redis/internal/executor/KeysExecutor.java
@@ -61,9 +61,6 @@ public class KeysExecutor extends AbstractExecutor {
     if (matchingKeys.isEmpty())
       command.setResponse(Coder.getEmptyArrayResponse(context.getByteBufAllocator()));
     else
-      command.setResponse(
-          Coder.getBulkStringArrayResponse(context.getByteBufAllocator(), matchingKeys));
-
-
+      respondBulkStrings(command, context, matchingKeys);
   }
 }
diff --git a/geode-core/src/main/java/org/apache/geode/redis/internal/executor/SortedSetQuery.java b/geode-core/src/main/java/org/apache/geode/redis/internal/executor/SortedSetQuery.java
index a0a8f63..b71a37f 100644
--- a/geode-core/src/main/java/org/apache/geode/redis/internal/executor/SortedSetQuery.java
+++ b/geode-core/src/main/java/org/apache/geode/redis/internal/executor/SortedSetQuery.java
@@ -266,6 +266,12 @@ public enum SortedSetQuery {
           + ".entrySet entry ORDER BY entry.value desc, entry.key desc LIMIT $1";
     }
   },
+  GEORADIUS {
+    public String getQueryString(String fullpath) {
+      return "SELECT DISTINCT entry.key, entry.value FROM " + fullpath
+          + ".entries entry WHERE entry.value.toString LIKE $1 ORDER BY entry.value asc";
+    }
+  },
   ZRANK {
     public String getQueryString(String fullpath) {
       return "SELECT COUNT(*) FROM " + fullpath
diff --git a/geode-core/src/main/java/org/apache/geode/redis/internal/executor/TypeExecutor.java b/geode-core/src/main/java/org/apache/geode/redis/internal/executor/TypeExecutor.java
index d97bc05..c42fd62 100755
--- a/geode-core/src/main/java/org/apache/geode/redis/internal/executor/TypeExecutor.java
+++ b/geode-core/src/main/java/org/apache/geode/redis/internal/executor/TypeExecutor.java
@@ -36,12 +36,10 @@ public class TypeExecutor extends AbstractExecutor {
     ByteArrayWrapper key = command.getKey();
 
     RedisDataType type = context.getRegionProvider().getRedisDataType(key);
-
     if (type == null)
-      command.setResponse(Coder.getBulkStringResponse(context.getByteBufAllocator(), "none"));
+      respondBulkStrings(command, context, "none");
     else
-      command
-          .setResponse(Coder.getBulkStringResponse(context.getByteBufAllocator(), type.toString()));
+      respondBulkStrings(command, context, type.toString());
   }
 
 }
diff --git a/geode-core/src/main/java/org/apache/geode/redis/internal/executor/hash/HGetExecutor.java b/geode-core/src/main/java/org/apache/geode/redis/internal/executor/hash/HGetExecutor.java
index 8574263..b3a9aa2 100755
--- a/geode-core/src/main/java/org/apache/geode/redis/internal/executor/hash/HGetExecutor.java
+++ b/geode-core/src/main/java/org/apache/geode/redis/internal/executor/hash/HGetExecutor.java
@@ -47,15 +47,7 @@ public class HGetExecutor extends HashExecutor {
 
     byte[] byteField = commandElems.get(FIELD_INDEX);
     ByteArrayWrapper field = new ByteArrayWrapper(byteField);
-
-    ByteArrayWrapper valueWrapper = keyRegion.get(field);
-
-    if (valueWrapper != null) {
-      command.setResponse(
-          Coder.getBulkStringResponse(context.getByteBufAllocator(), valueWrapper.toBytes()));
-    } else
-      command.setResponse(Coder.getNilResponse(context.getByteBufAllocator()));
-
+    respondBulkStrings(command, context, keyRegion.get(field));
   }
 
 }
diff --git a/geode-core/src/main/java/org/apache/geode/redis/internal/executor/hash/HIncrByFloatExecutor.java b/geode-core/src/main/java/org/apache/geode/redis/internal/executor/hash/HIncrByFloatExecutor.java
index 8b4af48..c837686 100755
--- a/geode-core/src/main/java/org/apache/geode/redis/internal/executor/hash/HIncrByFloatExecutor.java
+++ b/geode-core/src/main/java/org/apache/geode/redis/internal/executor/hash/HIncrByFloatExecutor.java
@@ -73,7 +73,7 @@ public class HIncrByFloatExecutor extends HashExecutor {
 
     if (oldValue == null) {
       keyRegion.put(field, new ByteArrayWrapper(incrArray));
-      command.setResponse(Coder.getBulkStringResponse(context.getByteBufAllocator(), increment));
+      respondBulkStrings(command, context, increment);
       return;
     }
 
@@ -98,7 +98,7 @@ public class HIncrByFloatExecutor extends HashExecutor {
 
     value += increment;
     keyRegion.put(field, new ByteArrayWrapper(Coder.doubleToBytes(value)));
-    command.setResponse(Coder.getBulkStringResponse(context.getByteBufAllocator(), value));
+    respondBulkStrings(command, context, value);
   }
 
 }
diff --git a/geode-core/src/main/java/org/apache/geode/redis/internal/executor/hash/HKeysExecutor.java b/geode-core/src/main/java/org/apache/geode/redis/internal/executor/hash/HKeysExecutor.java
index 8d0a6b7..808668b 100755
--- a/geode-core/src/main/java/org/apache/geode/redis/internal/executor/hash/HKeysExecutor.java
+++ b/geode-core/src/main/java/org/apache/geode/redis/internal/executor/hash/HKeysExecutor.java
@@ -54,8 +54,6 @@ public class HKeysExecutor extends HashExecutor {
       return;
     }
 
-    // String response = getBulkStringArrayResponse(keys);
-
-    command.setResponse(Coder.getBulkStringArrayResponse(context.getByteBufAllocator(), keys));
+    respondBulkStrings(command, context, keys);
   }
 }
diff --git a/geode-core/src/main/java/org/apache/geode/redis/internal/executor/hash/HMGetExecutor.java b/geode-core/src/main/java/org/apache/geode/redis/internal/executor/hash/HMGetExecutor.java
index 495e86b..696edad 100755
--- a/geode-core/src/main/java/org/apache/geode/redis/internal/executor/hash/HMGetExecutor.java
+++ b/geode-core/src/main/java/org/apache/geode/redis/internal/executor/hash/HMGetExecutor.java
@@ -65,7 +65,6 @@ public class HMGetExecutor extends HashExecutor {
     for (ByteArrayWrapper field : fields)
       values.add(results.get(field));
 
-    command.setResponse(Coder.getBulkStringArrayResponse(context.getByteBufAllocator(), values));
-
+    respondBulkStrings(command, context, values);
   }
 }
diff --git a/geode-core/src/main/java/org/apache/geode/redis/internal/executor/hash/HValsExecutor.java b/geode-core/src/main/java/org/apache/geode/redis/internal/executor/hash/HValsExecutor.java
index 1ca8071..c9fb533 100755
--- a/geode-core/src/main/java/org/apache/geode/redis/internal/executor/hash/HValsExecutor.java
+++ b/geode-core/src/main/java/org/apache/geode/redis/internal/executor/hash/HValsExecutor.java
@@ -48,13 +48,12 @@ public class HValsExecutor extends HashExecutor {
     }
 
     Collection<ByteArrayWrapper> vals = new ArrayList(keyRegion.values());
-
     if (vals.isEmpty()) {
       command.setResponse(Coder.getEmptyArrayResponse(context.getByteBufAllocator()));
       return;
     }
 
-    command.setResponse(Coder.getBulkStringArrayResponse(context.getByteBufAllocator(), vals));
+    respondBulkStrings(command, context, vals);
   }
 
 }
diff --git a/geode-core/src/main/java/org/apache/geode/redis/internal/executor/list/LIndexExecutor.java b/geode-core/src/main/java/org/apache/geode/redis/internal/executor/list/LIndexExecutor.java
index c80a505..dd10e29 100755
--- a/geode-core/src/main/java/org/apache/geode/redis/internal/executor/list/LIndexExecutor.java
+++ b/geode-core/src/main/java/org/apache/geode/redis/internal/executor/list/LIndexExecutor.java
@@ -97,8 +97,7 @@ public class LIndexExecutor extends ListExecutor {
 
     Object[] entryArray = entry.getFieldValues();
     ByteArrayWrapper valueWrapper = (ByteArrayWrapper) entryArray[1];
-    command.setResponse(
-        Coder.getBulkStringResponse(context.getByteBufAllocator(), valueWrapper.toBytes()));
+    respondBulkStrings(command, context, valueWrapper);
   }
 
   private Struct getEntryAtIndex(ExecutionHandlerContext context, ByteArrayWrapper key, int index)
diff --git a/geode-core/src/main/java/org/apache/geode/redis/internal/executor/list/PopExecutor.java b/geode-core/src/main/java/org/apache/geode/redis/internal/executor/list/PopExecutor.java
index 3ca7224..05f8e28 100755
--- a/geode-core/src/main/java/org/apache/geode/redis/internal/executor/list/PopExecutor.java
+++ b/geode-core/src/main/java/org/apache/geode/redis/internal/executor/list/PopExecutor.java
@@ -121,11 +121,7 @@ public abstract class PopExecutor extends ListExecutor implements Extendable {
         index = metaIndex;
       i++;
     } while (!removed && keyRegion.size() != LIST_EMPTY_SIZE);
-    if (valueWrapper != null)
-      command.setResponse(
-          Coder.getBulkStringResponse(context.getByteBufAllocator(), valueWrapper.toBytes()));
-    else
-      command.setResponse(Coder.getNilResponse(context.getByteBufAllocator()));
+    respondBulkStrings(command, context, valueWrapper);
   }
 
   protected abstract ListDirection popType();
diff --git a/geode-core/src/main/java/org/apache/geode/redis/internal/executor/set/SMembersExecutor.java b/geode-core/src/main/java/org/apache/geode/redis/internal/executor/set/SMembersExecutor.java
index dc94897..cc23e0b 100755
--- a/geode-core/src/main/java/org/apache/geode/redis/internal/executor/set/SMembersExecutor.java
+++ b/geode-core/src/main/java/org/apache/geode/redis/internal/executor/set/SMembersExecutor.java
@@ -49,7 +49,6 @@ public class SMembersExecutor extends SetExecutor {
     }
 
     Set<ByteArrayWrapper> members = new HashSet(keyRegion.keySet()); // Emulate copy on read
-
-    command.setResponse(Coder.getBulkStringArrayResponse(context.getByteBufAllocator(), members));
+    respondBulkStrings(command, context, members);
   }
 }
diff --git a/geode-core/src/main/java/org/apache/geode/redis/internal/executor/set/SPopExecutor.java b/geode-core/src/main/java/org/apache/geode/redis/internal/executor/set/SPopExecutor.java
index 0d2c2a9..5bd0acb 100755
--- a/geode-core/src/main/java/org/apache/geode/redis/internal/executor/set/SPopExecutor.java
+++ b/geode-core/src/main/java/org/apache/geode/redis/internal/executor/set/SPopExecutor.java
@@ -54,7 +54,8 @@ public class SPopExecutor extends SetExecutor {
     if (keyRegion.isEmpty()) {
       context.getRegionProvider().removeKey(key);
     }
-    command.setResponse(Coder.getBulkStringResponse(context.getByteBufAllocator(), pop.toBytes()));
+
+    respondBulkStrings(command, context, pop);
   }
 
 }
diff --git a/geode-core/src/main/java/org/apache/geode/redis/internal/executor/set/SRandMemberExecutor.java b/geode-core/src/main/java/org/apache/geode/redis/internal/executor/set/SRandMemberExecutor.java
index fe45b61..16bbaf6 100755
--- a/geode-core/src/main/java/org/apache/geode/redis/internal/executor/set/SRandMemberExecutor.java
+++ b/geode-core/src/main/java/org/apache/geode/redis/internal/executor/set/SRandMemberExecutor.java
@@ -66,8 +66,7 @@ public class SRandMemberExecutor extends SetExecutor {
     int members = keyRegion.size();
 
     if (members <= count && count != 1) {
-      command.setResponse(Coder.getBulkStringArrayResponse(context.getByteBufAllocator(),
-          new HashSet<ByteArrayWrapper>(keyRegion.keySet())));
+      respondBulkStrings(command, context, new HashSet<ByteArrayWrapper>(keyRegion.keySet()));
       return;
     }
 
@@ -77,16 +76,14 @@ public class SRandMemberExecutor extends SetExecutor {
 
     if (count == 1) {
       ByteArrayWrapper randEntry = entries[rand.nextInt(entries.length)];
-      command.setResponse(
-          Coder.getBulkStringResponse(context.getByteBufAllocator(), randEntry.toBytes()));
+      respondBulkStrings(command, context, randEntry);
     } else if (count > 0) {
       Set<ByteArrayWrapper> randEntries = new HashSet<ByteArrayWrapper>();
       do {
         ByteArrayWrapper s = entries[rand.nextInt(entries.length)];
         randEntries.add(s);
       } while (randEntries.size() < count);
-      command.setResponse(
-          Coder.getBulkStringArrayResponse(context.getByteBufAllocator(), randEntries));
+      respondBulkStrings(command, context, randEntries);
     } else {
       count = -count;
       List<ByteArrayWrapper> randEntries = new ArrayList<ByteArrayWrapper>();
@@ -94,8 +91,7 @@ public class SRandMemberExecutor extends SetExecutor {
         ByteArrayWrapper s = entries[rand.nextInt(entries.length)];
         randEntries.add(s);
       }
-      command.setResponse(
-          Coder.getBulkStringArrayResponse(context.getByteBufAllocator(), randEntries));
+      respondBulkStrings(command, context, randEntries);
     }
   }
 }
diff --git a/geode-core/src/main/java/org/apache/geode/redis/internal/executor/set/SetOpExecutor.java b/geode-core/src/main/java/org/apache/geode/redis/internal/executor/set/SetOpExecutor.java
index 7ca72a5..fe0e7e8 100755
--- a/geode-core/src/main/java/org/apache/geode/redis/internal/executor/set/SetOpExecutor.java
+++ b/geode-core/src/main/java/org/apache/geode/redis/internal/executor/set/SetOpExecutor.java
@@ -70,11 +70,7 @@ public abstract class SetOpExecutor extends SetExecutor implements Extendable {
         command.setResponse(Coder.getIntegerResponse(context.getByteBufAllocator(), 0));
         context.getRegionProvider().removeKey(destination);
       } else {
-        if (firstSet == null)
-          command.setResponse(Coder.getNilResponse(context.getByteBufAllocator()));
-        else
-          command.setResponse(
-              Coder.getBulkStringArrayResponse(context.getByteBufAllocator(), firstSet));
+        respondBulkStrings(command, context, firstSet);
       }
       return;
     }
@@ -102,8 +98,7 @@ public abstract class SetOpExecutor extends SetExecutor implements Extendable {
       if (resultSet == null || resultSet.isEmpty())
         command.setResponse(Coder.getEmptyArrayResponse(context.getByteBufAllocator()));
       else
-        command.setResponse(
-            Coder.getBulkStringArrayResponse(context.getByteBufAllocator(), resultSet));
+        respondBulkStrings(command, context, resultSet);
     }
   }
 
diff --git a/geode-core/src/main/java/org/apache/geode/redis/internal/executor/sortedset/GeoAddExecutor.java b/geode-core/src/main/java/org/apache/geode/redis/internal/executor/sortedset/GeoAddExecutor.java
new file mode 100644
index 0000000..67f4920
--- /dev/null
+++ b/geode-core/src/main/java/org/apache/geode/redis/internal/executor/sortedset/GeoAddExecutor.java
@@ -0,0 +1,76 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The ASF licenses this file to You 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
+ *
+ * http://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 org.apache.geode.redis.internal.executor.sortedset;
+
+import static org.apache.geode.redis.internal.RedisConstants.ERROR_INVALID_LATLONG;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.geode.cache.Region;
+import org.apache.geode.redis.internal.ByteArrayWrapper;
+import org.apache.geode.redis.internal.Coder;
+import org.apache.geode.redis.internal.Command;
+import org.apache.geode.redis.internal.ExecutionHandlerContext;
+import org.apache.geode.redis.internal.GeoCoder;
+import org.apache.geode.redis.internal.RedisConstants;
+import org.apache.geode.redis.internal.RedisDataType;
+
+public class GeoAddExecutor extends GeoSortedSetExecutor {
+
+  @Override
+  public void executeCommand(Command command, ExecutionHandlerContext context) {
+    int numberOfAdds = 0;
+    List<byte[]> commandElems = command.getProcessedCommand();
+    ByteArrayWrapper key = command.getKey();
+
+    if (commandElems.size() < 5 || ((commandElems.size() - 2) % 3) != 0) {
+      command.setResponse(
+          Coder.getErrorResponse(context.getByteBufAllocator(), RedisConstants.ArityDef.GEOADD));
+      return;
+    }
+
+    Region<ByteArrayWrapper, ByteArrayWrapper> keyRegion =
+        getOrCreateRegion(context, key, RedisDataType.REDIS_SORTEDSET);
+
+    Map<ByteArrayWrapper, ByteArrayWrapper> tempMap = new HashMap<>();
+    for (int i = 2; i < commandElems.size(); i += 3) {
+      byte[] longitude = commandElems.get(i);
+      byte[] latitude = commandElems.get(i + 1);
+      byte[] member = commandElems.get(i + 2);
+
+      String score;
+      try {
+        score = GeoCoder.geohash(longitude, latitude);
+      } catch (IllegalArgumentException e) {
+        command.setResponse(Coder.getErrorResponse(context.getByteBufAllocator(),
+            "ERR " + ERROR_INVALID_LATLONG +
+                " " + longitude.toString() + " " + latitude.toString()));
+        return;
+      }
+
+      tempMap.put(new ByteArrayWrapper(member), new ByteArrayWrapper(score.getBytes()));
+    }
+
+    for (ByteArrayWrapper m : tempMap.keySet()) {
+      Object oldVal = keyRegion.put(m, tempMap.get(m));
+      if (oldVal == null)
+        numberOfAdds++;
+    }
+
+    command.setResponse(Coder.getIntegerResponse(context.getByteBufAllocator(), numberOfAdds));
+  }
+}
diff --git a/geode-core/src/main/java/org/apache/geode/redis/internal/executor/hash/HGetExecutor.java b/geode-core/src/main/java/org/apache/geode/redis/internal/executor/sortedset/GeoDistExecutor.java
old mode 100755
new mode 100644
similarity index 63%
copy from geode-core/src/main/java/org/apache/geode/redis/internal/executor/hash/HGetExecutor.java
copy to geode-core/src/main/java/org/apache/geode/redis/internal/executor/sortedset/GeoDistExecutor.java
index 8574263..4879e9d
--- a/geode-core/src/main/java/org/apache/geode/redis/internal/executor/hash/HGetExecutor.java
+++ b/geode-core/src/main/java/org/apache/geode/redis/internal/executor/sortedset/GeoDistExecutor.java
@@ -12,7 +12,8 @@
  * or implied. See the License for the specific language governing permissions and limitations under
  * the License.
  */
-package org.apache.geode.redis.internal.executor.hash;
+
+package org.apache.geode.redis.internal.executor.sortedset;
 
 import java.util.List;
 
@@ -21,41 +22,37 @@ import org.apache.geode.redis.internal.ByteArrayWrapper;
 import org.apache.geode.redis.internal.Coder;
 import org.apache.geode.redis.internal.Command;
 import org.apache.geode.redis.internal.ExecutionHandlerContext;
-import org.apache.geode.redis.internal.RedisConstants.ArityDef;
-import org.apache.geode.redis.internal.RedisDataType;
+import org.apache.geode.redis.internal.GeoCoder;
+import org.apache.geode.redis.internal.RedisConstants;
 
-public class HGetExecutor extends HashExecutor {
+public class GeoDistExecutor extends GeoSortedSetExecutor {
 
   @Override
   public void executeCommand(Command command, ExecutionHandlerContext context) {
     List<byte[]> commandElems = command.getProcessedCommand();
+    ByteArrayWrapper key = command.getKey();
 
-    if (commandElems.size() < 3) {
-      command.setResponse(Coder.getErrorResponse(context.getByteBufAllocator(), ArityDef.HGET));
+    if (commandElems.size() < 4 || commandElems.size() > 5) {
+      command.setResponse(
+          Coder.getErrorResponse(context.getByteBufAllocator(), RedisConstants.ArityDef.GEODIST));
       return;
     }
 
-    ByteArrayWrapper key = command.getKey();
-
-    checkDataType(key, RedisDataType.REDIS_HASH, context);
     Region<ByteArrayWrapper, ByteArrayWrapper> keyRegion = getRegion(context, key);
-
-    if (keyRegion == null) {
+    ByteArrayWrapper hw1 = keyRegion.get(new ByteArrayWrapper(commandElems.get(2)));
+    ByteArrayWrapper hw2 = keyRegion.get(new ByteArrayWrapper(commandElems.get(3)));
+    if (hw1 == null || hw2 == null) {
       command.setResponse(Coder.getNilResponse(context.getByteBufAllocator()));
       return;
     }
 
-    byte[] byteField = commandElems.get(FIELD_INDEX);
-    ByteArrayWrapper field = new ByteArrayWrapper(byteField);
+    Double dist = GeoCoder.geoDist(hw1.toString(), hw2.toString());
 
-    ByteArrayWrapper valueWrapper = keyRegion.get(field);
-
-    if (valueWrapper != null) {
-      command.setResponse(
-          Coder.getBulkStringResponse(context.getByteBufAllocator(), valueWrapper.toBytes()));
-    } else
-      command.setResponse(Coder.getNilResponse(context.getByteBufAllocator()));
+    if (commandElems.size() == 5) {
+      String unit = new String(commandElems.get(4));
+      dist = dist * GeoCoder.parseUnitScale(unit);
+    }
 
+    respondBulkStrings(command, context, Double.toString(dist));
   }
-
 }
diff --git a/geode-core/src/main/java/org/apache/geode/redis/internal/executor/hash/HGetExecutor.java b/geode-core/src/main/java/org/apache/geode/redis/internal/executor/sortedset/GeoHashExecutor.java
old mode 100755
new mode 100644
similarity index 61%
copy from geode-core/src/main/java/org/apache/geode/redis/internal/executor/hash/HGetExecutor.java
copy to geode-core/src/main/java/org/apache/geode/redis/internal/executor/sortedset/GeoHashExecutor.java
index 8574263..98a9eef
--- a/geode-core/src/main/java/org/apache/geode/redis/internal/executor/hash/HGetExecutor.java
+++ b/geode-core/src/main/java/org/apache/geode/redis/internal/executor/sortedset/GeoHashExecutor.java
@@ -12,8 +12,10 @@
  * or implied. See the License for the specific language governing permissions and limitations under
  * the License.
  */
-package org.apache.geode.redis.internal.executor.hash;
 
+package org.apache.geode.redis.internal.executor.sortedset;
+
+import java.util.ArrayList;
 import java.util.List;
 
 import org.apache.geode.cache.Region;
@@ -21,41 +23,35 @@ import org.apache.geode.redis.internal.ByteArrayWrapper;
 import org.apache.geode.redis.internal.Coder;
 import org.apache.geode.redis.internal.Command;
 import org.apache.geode.redis.internal.ExecutionHandlerContext;
-import org.apache.geode.redis.internal.RedisConstants.ArityDef;
-import org.apache.geode.redis.internal.RedisDataType;
+import org.apache.geode.redis.internal.RedisConstants;
 
-public class HGetExecutor extends HashExecutor {
+public class GeoHashExecutor extends GeoSortedSetExecutor {
 
   @Override
   public void executeCommand(Command command, ExecutionHandlerContext context) {
     List<byte[]> commandElems = command.getProcessedCommand();
+    ByteArrayWrapper key = command.getKey();
 
     if (commandElems.size() < 3) {
-      command.setResponse(Coder.getErrorResponse(context.getByteBufAllocator(), ArityDef.HGET));
+      command.setResponse(
+          Coder.getErrorResponse(context.getByteBufAllocator(), RedisConstants.ArityDef.GEOHASH));
       return;
     }
 
-    ByteArrayWrapper key = command.getKey();
-
-    checkDataType(key, RedisDataType.REDIS_HASH, context);
+    List<String> hashes = new ArrayList<>();
     Region<ByteArrayWrapper, ByteArrayWrapper> keyRegion = getRegion(context, key);
 
-    if (keyRegion == null) {
-      command.setResponse(Coder.getNilResponse(context.getByteBufAllocator()));
-      return;
-    }
-
-    byte[] byteField = commandElems.get(FIELD_INDEX);
-    ByteArrayWrapper field = new ByteArrayWrapper(byteField);
-
-    ByteArrayWrapper valueWrapper = keyRegion.get(field);
+    for (int i = 2; i < commandElems.size(); i++) {
+      byte[] member = commandElems.get(i);
 
-    if (valueWrapper != null) {
-      command.setResponse(
-          Coder.getBulkStringResponse(context.getByteBufAllocator(), valueWrapper.toBytes()));
-    } else
-      command.setResponse(Coder.getNilResponse(context.getByteBufAllocator()));
+      ByteArrayWrapper hashWrapper = keyRegion.get(new ByteArrayWrapper(member));
+      if (hashWrapper != null) {
+        hashes.add(hashWrapper.toString());
+      } else {
+        hashes.add(null);
+      }
+    }
 
+    respondBulkStrings(command, context, hashes);
   }
-
 }
diff --git a/geode-core/src/main/java/org/apache/geode/redis/internal/executor/hash/HGetExecutor.java b/geode-core/src/main/java/org/apache/geode/redis/internal/executor/sortedset/GeoPosExecutor.java
old mode 100755
new mode 100644
similarity index 61%
copy from geode-core/src/main/java/org/apache/geode/redis/internal/executor/hash/HGetExecutor.java
copy to geode-core/src/main/java/org/apache/geode/redis/internal/executor/sortedset/GeoPosExecutor.java
index 8574263..20b17eb
--- a/geode-core/src/main/java/org/apache/geode/redis/internal/executor/hash/HGetExecutor.java
+++ b/geode-core/src/main/java/org/apache/geode/redis/internal/executor/sortedset/GeoPosExecutor.java
@@ -12,50 +12,49 @@
  * or implied. See the License for the specific language governing permissions and limitations under
  * the License.
  */
-package org.apache.geode.redis.internal.executor.hash;
 
+package org.apache.geode.redis.internal.executor.sortedset;
+
+import java.util.ArrayList;
 import java.util.List;
 
+import com.github.davidmoten.geo.LatLong;
+
 import org.apache.geode.cache.Region;
 import org.apache.geode.redis.internal.ByteArrayWrapper;
 import org.apache.geode.redis.internal.Coder;
 import org.apache.geode.redis.internal.Command;
 import org.apache.geode.redis.internal.ExecutionHandlerContext;
-import org.apache.geode.redis.internal.RedisConstants.ArityDef;
-import org.apache.geode.redis.internal.RedisDataType;
+import org.apache.geode.redis.internal.GeoCoder;
+import org.apache.geode.redis.internal.RedisConstants;
 
-public class HGetExecutor extends HashExecutor {
+public class GeoPosExecutor extends GeoSortedSetExecutor {
 
   @Override
   public void executeCommand(Command command, ExecutionHandlerContext context) {
     List<byte[]> commandElems = command.getProcessedCommand();
+    ByteArrayWrapper key = command.getKey();
 
     if (commandElems.size() < 3) {
-      command.setResponse(Coder.getErrorResponse(context.getByteBufAllocator(), ArityDef.HGET));
+      command.setResponse(
+          Coder.getErrorResponse(context.getByteBufAllocator(), RedisConstants.ArityDef.GEOPOS));
       return;
     }
 
-    ByteArrayWrapper key = command.getKey();
-
-    checkDataType(key, RedisDataType.REDIS_HASH, context);
+    List<LatLong> positions = new ArrayList<>();
     Region<ByteArrayWrapper, ByteArrayWrapper> keyRegion = getRegion(context, key);
 
-    if (keyRegion == null) {
-      command.setResponse(Coder.getNilResponse(context.getByteBufAllocator()));
-      return;
-    }
-
-    byte[] byteField = commandElems.get(FIELD_INDEX);
-    ByteArrayWrapper field = new ByteArrayWrapper(byteField);
-
-    ByteArrayWrapper valueWrapper = keyRegion.get(field);
+    for (int i = 2; i < commandElems.size(); i++) {
+      byte[] member = commandElems.get(i);
 
-    if (valueWrapper != null) {
-      command.setResponse(
-          Coder.getBulkStringResponse(context.getByteBufAllocator(), valueWrapper.toBytes()));
-    } else
-      command.setResponse(Coder.getNilResponse(context.getByteBufAllocator()));
+      ByteArrayWrapper hashWrapper = keyRegion.get(new ByteArrayWrapper(member));
+      if (hashWrapper != null) {
+        positions.add(GeoCoder.geoPos(hashWrapper.toString()));
+      } else {
+        positions.add(null);
+      }
+    }
 
+    respondGeoCoordinates(command, context, positions);
   }
-
 }
diff --git a/geode-core/src/main/java/org/apache/geode/redis/internal/executor/sortedset/GeoRadiusByMemberExecutor.java b/geode-core/src/main/java/org/apache/geode/redis/internal/executor/sortedset/GeoRadiusByMemberExecutor.java
new file mode 100644
index 0000000..6579236
--- /dev/null
+++ b/geode-core/src/main/java/org/apache/geode/redis/internal/executor/sortedset/GeoRadiusByMemberExecutor.java
@@ -0,0 +1,124 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The ASF licenses this file to You 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
+ *
+ * http://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 org.apache.geode.redis.internal.executor.sortedset;
+
+import static org.apache.geode.redis.internal.RedisConstants.ERROR_INVALID_ARGUMENT_UNIT_NUM;
+import static org.apache.geode.redis.internal.RedisConstants.ERROR_ZSET_MEMBER_NOT_FOUND;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+
+import com.github.davidmoten.geo.LatLong;
+
+import org.apache.geode.cache.Region;
+import org.apache.geode.cache.query.internal.StructImpl;
+import org.apache.geode.redis.internal.ByteArrayWrapper;
+import org.apache.geode.redis.internal.Coder;
+import org.apache.geode.redis.internal.Command;
+import org.apache.geode.redis.internal.ExecutionHandlerContext;
+import org.apache.geode.redis.internal.GeoCoder;
+import org.apache.geode.redis.internal.GeoRadiusResponseElement;
+import org.apache.geode.redis.internal.MemberNotFoundException;
+import org.apache.geode.redis.internal.RedisCommandParserException;
+import org.apache.geode.redis.internal.RedisConstants;
+import org.apache.geode.redis.internal.RedisDataType;
+
+public class GeoRadiusByMemberExecutor extends GeoSortedSetExecutor {
+
+  @Override
+  public void executeCommand(Command command, ExecutionHandlerContext context) {
+    List<byte[]> commandElems = command.getProcessedCommand();
+
+    if (commandElems.size() < 5) {
+      command.setResponse(Coder.getErrorResponse(context.getByteBufAllocator(),
+          RedisConstants.ArityDef.GEORADIUSBYMEMBER));
+      return;
+    }
+
+    ByteArrayWrapper key = command.getKey();
+    checkDataType(key, RedisDataType.REDIS_SORTEDSET, context);
+    Region<ByteArrayWrapper, ByteArrayWrapper> keyRegion = getRegion(context, key);
+
+    if (keyRegion == null) {
+      command.setResponse(Coder.getEmptyArrayResponse(context.getByteBufAllocator()));
+      return;
+    }
+
+    GeoRadiusParameters params;
+    try {
+      params = new GeoRadiusParameters(keyRegion, commandElems,
+          GeoRadiusParameters.CommandType.GEORADIUSBYMEMBER);
+    } catch (IllegalArgumentException e) {
+      command.setResponse(
+          Coder.getErrorResponse(context.getByteBufAllocator(), ERROR_INVALID_ARGUMENT_UNIT_NUM));
+      return;
+    } catch (RedisCommandParserException e) {
+      command.setResponse(Coder.getErrorResponse(context.getByteBufAllocator(),
+          RedisConstants.ArityDef.GEORADIUSBYMEMBER));
+      return;
+    } catch (MemberNotFoundException e) {
+      command.setResponse(Coder.getErrorResponse(context.getByteBufAllocator(), "ERR "
+          + ERROR_ZSET_MEMBER_NOT_FOUND));
+      return;
+    }
+
+    Set<String> hn = GeoCoder.geohashSearchAreas(params.lon, params.lat, params.radius);
+
+    List<GeoRadiusResponseElement> results = new ArrayList<>();
+    for (String neighbor : hn) {
+      try {
+        List<StructImpl> range = getGeoRadiusRange(context, key, neighbor);
+        for (StructImpl point : range) {
+          String name = point.get("key").toString();
+          String hash = point.get("value").toString();
+
+          Double dist = GeoCoder.geoDist(params.centerHashPrecise, hash) * params.distScale;
+
+          // Post-filter for accuracy
+          if (dist > (params.radius * params.distScale))
+            continue;
+
+          Optional<LatLong> coord =
+              params.withCoord ? Optional.of(GeoCoder.geoPos(hash)) : Optional.empty();
+          Optional<String> hashOpt =
+              params.withHash ? Optional.of(hash) : Optional.empty();
+
+          // Because of the way hashing works, sometimes you can get the same requested member back
+          // in the results
+          if (!name.equals(params.member))
+            results.add(new GeoRadiusResponseElement(name, coord, dist, params.withDist, hashOpt));
+        }
+      } catch (Exception e) {
+        command.setResponse(Coder.getErrorResponse(context.getByteBufAllocator(), e.getMessage()));
+        return;
+      }
+    }
+
+    if (params.order == GeoRadiusParameters.SortOrder.ASC) {
+      GeoRadiusResponseElement.sortByDistanceAscending(results);
+    } else if (params.order == GeoRadiusParameters.SortOrder.DESC) {
+      GeoRadiusResponseElement.sortByDistanceDescending(results);
+    }
+
+    if (params.count != null && params.count < results.size()) {
+      results = results.subList(0, params.count);
+    }
+
+    respondGeoRadius(command, context, results);
+  }
+}
diff --git a/geode-core/src/main/java/org/apache/geode/redis/internal/executor/sortedset/GeoRadiusExecutor.java b/geode-core/src/main/java/org/apache/geode/redis/internal/executor/sortedset/GeoRadiusExecutor.java
new file mode 100644
index 0000000..6b160b6
--- /dev/null
+++ b/geode-core/src/main/java/org/apache/geode/redis/internal/executor/sortedset/GeoRadiusExecutor.java
@@ -0,0 +1,120 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The ASF licenses this file to You 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
+ *
+ * http://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 org.apache.geode.redis.internal.executor.sortedset;
+
+import static org.apache.geode.redis.internal.RedisConstants.ERROR_INVALID_ARGUMENT_UNIT_NUM;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+
+import com.github.davidmoten.geo.LatLong;
+
+import org.apache.geode.cache.Region;
+import org.apache.geode.cache.query.internal.StructImpl;
+import org.apache.geode.redis.internal.ByteArrayWrapper;
+import org.apache.geode.redis.internal.Coder;
+import org.apache.geode.redis.internal.Command;
+import org.apache.geode.redis.internal.ExecutionHandlerContext;
+import org.apache.geode.redis.internal.GeoCoder;
+import org.apache.geode.redis.internal.GeoRadiusResponseElement;
+import org.apache.geode.redis.internal.MemberNotFoundException;
+import org.apache.geode.redis.internal.RedisCommandParserException;
+import org.apache.geode.redis.internal.RedisConstants;
+import org.apache.geode.redis.internal.RedisDataType;
+
+public class GeoRadiusExecutor extends GeoSortedSetExecutor {
+
+  @Override
+  public void executeCommand(Command command, ExecutionHandlerContext context) {
+    List<byte[]> commandElems = command.getProcessedCommand();
+
+    if (commandElems.size() < 6) {
+      command.setResponse(
+          Coder.getErrorResponse(context.getByteBufAllocator(), RedisConstants.ArityDef.GEORADIUS));
+      return;
+    }
+
+    ByteArrayWrapper key = command.getKey();
+    checkDataType(key, RedisDataType.REDIS_SORTEDSET, context);
+    Region<ByteArrayWrapper, ByteArrayWrapper> keyRegion = getRegion(context, key);
+
+    if (keyRegion == null) {
+      command.setResponse(Coder.getEmptyArrayResponse(context.getByteBufAllocator()));
+      return;
+    }
+
+    GeoRadiusParameters params;
+    try {
+      params = new GeoRadiusParameters(keyRegion, commandElems,
+          GeoRadiusParameters.CommandType.GEORADIUS);
+    } catch (IllegalArgumentException e) {
+      command.setResponse(
+          Coder.getErrorResponse(context.getByteBufAllocator(), ERROR_INVALID_ARGUMENT_UNIT_NUM));
+      return;
+    } catch (RedisCommandParserException e) {
+      command.setResponse(Coder.getErrorResponse(context.getByteBufAllocator(),
+          RedisConstants.ArityDef.GEORADIUS));
+      return;
+    } catch (MemberNotFoundException e) {
+      /* Not possible for GEORADIUS */
+      return;
+    }
+
+    Set<String> hn = GeoCoder.geohashSearchAreas(params.lon, params.lat, params.radius);
+
+    List<GeoRadiusResponseElement> results = new ArrayList<>();
+    for (String neighbor : hn) {
+      try {
+        List<StructImpl> range = getGeoRadiusRange(context, key, neighbor);
+        for (StructImpl point : range) {
+          String name = point.get("key").toString();
+          String hash = point.get("value").toString();
+
+          Double dist = GeoCoder.geoDist(params.centerHashPrecise, hash) * params.distScale;
+
+          // Post-filter for accuracy
+          if (dist > (params.radius * params.distScale))
+            continue;
+
+          Optional<LatLong> coord =
+              params.withCoord ? Optional.of(GeoCoder.geoPos(hash)) : Optional.empty();
+          Optional<String> hashOpt =
+              params.withHash ? Optional.of(hash) : Optional.empty();
+
+          results.add(new GeoRadiusResponseElement(name, coord, dist, params.withDist, hashOpt));
+        }
+      } catch (Exception e) {
+        command.setResponse(Coder.getErrorResponse(context.getByteBufAllocator(), e.getMessage()));
+        return;
+      }
+    }
+
+    if (params.order == GeoRadiusParameters.SortOrder.ASC) {
+      GeoRadiusResponseElement.sortByDistanceAscending(results);
+    } else if (params.order == GeoRadiusParameters.SortOrder.DESC) {
+      GeoRadiusResponseElement.sortByDistanceDescending(results);
+    }
+
+    if (params.count != null && params.count < results.size()) {
+      results = results.subList(0, params.count);
+    }
+
+    respondGeoRadius(command, context, results);
+  }
+
+}
diff --git a/geode-core/src/main/java/org/apache/geode/redis/internal/executor/sortedset/GeoRadiusParameters.java b/geode-core/src/main/java/org/apache/geode/redis/internal/executor/sortedset/GeoRadiusParameters.java
new file mode 100644
index 0000000..6097b40
--- /dev/null
+++ b/geode-core/src/main/java/org/apache/geode/redis/internal/executor/sortedset/GeoRadiusParameters.java
@@ -0,0 +1,132 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The ASF licenses this file to You 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
+ *
+ * http://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 org.apache.geode.redis.internal.executor.sortedset;
+
+import java.util.List;
+
+import com.github.davidmoten.geo.LatLong;
+
+import org.apache.geode.cache.Region;
+import org.apache.geode.redis.internal.ByteArrayWrapper;
+import org.apache.geode.redis.internal.Coder;
+import org.apache.geode.redis.internal.GeoCoder;
+import org.apache.geode.redis.internal.MemberNotFoundException;
+import org.apache.geode.redis.internal.RedisCommandParserException;
+
+public class GeoRadiusParameters {
+  final double lon;
+  final double lat;
+  final double radius;
+  final String unit;
+  final String member;
+
+  final boolean withDist;
+  final boolean withCoord;
+  final boolean withHash;
+  final Integer count;
+  final SortOrder order;
+
+  final Double distScale;
+  final String centerHashPrecise;
+
+  public enum CommandType {
+    GEORADIUS, GEORADIUSBYMEMBER
+  }
+
+  public enum SortOrder {
+    ASC, DESC, UNSORTED
+  }
+
+  public GeoRadiusParameters(Region<ByteArrayWrapper, ByteArrayWrapper> keyRegion,
+      List<byte[]> commandElems, CommandType cmdType) throws IllegalArgumentException,
+      RedisCommandParserException, MemberNotFoundException {
+    byte[] radArray;
+
+    switch (cmdType) {
+      case GEORADIUS:
+        byte[] lonArray = commandElems.get(2);
+        byte[] latArray = commandElems.get(3);
+        radArray = commandElems.get(4);
+        unit = new String(commandElems.get(5));
+        centerHashPrecise = GeoCoder.geohash(lonArray, latArray);
+        lon = Coder.bytesToDouble(lonArray);
+        lat = Coder.bytesToDouble(latArray);
+        member = null;
+        break;
+      default:
+        byte[] memberArray = commandElems.get(2);
+        radArray = commandElems.get(3);
+        unit = new String(commandElems.get(4));
+        member = new String(memberArray);
+        ByteArrayWrapper hashWrapper = keyRegion.get(new ByteArrayWrapper(memberArray));
+        if (hashWrapper == null) {
+          throw new MemberNotFoundException();
+        }
+
+        centerHashPrecise = hashWrapper.toString();
+        LatLong pos = GeoCoder.geoPos(centerHashPrecise);
+        lon = pos.getLon();
+        lat = pos.getLat();
+        break;
+    }
+
+    distScale = GeoCoder.parseUnitScale(unit);
+    radius = Coder.bytesToDouble(radArray) / distScale;
+
+    int i = (cmdType == CommandType.GEORADIUS) ? 6 : 5;
+
+    boolean showDist = false;
+    boolean showCoord = false;
+    boolean showHash = false;
+    for (; i < commandElems.size() && (new String(commandElems.get(i))).contains("with"); i++) {
+      String elem = new String(commandElems.get(i));
+
+      if (elem.equals("withdist"))
+        showDist = true;
+      if (elem.equals("withcoord"))
+        showCoord = true;
+      if (elem.equals("withhash"))
+        showHash = true;
+    }
+    withDist = showDist;
+    withCoord = showCoord;
+    withHash = showHash;
+
+    if (i < commandElems.size() && (new String(commandElems.get(i))).equals("count")) {
+      count = Coder.bytesToInt(commandElems.get(++i));
+      i++;
+    } else {
+      count = null;
+    }
+
+    if (i < commandElems.size() && (new String(commandElems.get(i))).contains("sc")) {
+      String elem = new String(commandElems.get(i++));
+
+      if (elem.equals("asc"))
+        order = SortOrder.ASC;
+      else if (elem.equals("desc"))
+        order = SortOrder.DESC;
+      else
+        order = SortOrder.UNSORTED;
+    } else {
+      order = SortOrder.UNSORTED;
+    }
+
+    if (i < commandElems.size()) {
+      throw new RedisCommandParserException();
+    }
+  }
+}
diff --git a/geode-core/src/main/java/org/apache/geode/redis/internal/executor/sortedset/GeoSortedSetExecutor.java b/geode-core/src/main/java/org/apache/geode/redis/internal/executor/sortedset/GeoSortedSetExecutor.java
new file mode 100755
index 0000000..70f0f40
--- /dev/null
+++ b/geode-core/src/main/java/org/apache/geode/redis/internal/executor/sortedset/GeoSortedSetExecutor.java
@@ -0,0 +1,94 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The ASF licenses this file to You 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
+ *
+ * http://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 org.apache.geode.redis.internal.executor.sortedset;
+
+import java.util.List;
+
+import com.github.davidmoten.geo.LatLong;
+import io.netty.buffer.ByteBuf;
+
+import org.apache.geode.cache.Region;
+import org.apache.geode.cache.query.Query;
+import org.apache.geode.cache.query.SelectResults;
+import org.apache.geode.cache.query.internal.StructImpl;
+import org.apache.geode.redis.internal.ByteArrayWrapper;
+import org.apache.geode.redis.internal.Coder;
+import org.apache.geode.redis.internal.CoderException;
+import org.apache.geode.redis.internal.Command;
+import org.apache.geode.redis.internal.ExecutionHandlerContext;
+import org.apache.geode.redis.internal.GeoCoder;
+import org.apache.geode.redis.internal.GeoRadiusResponseElement;
+import org.apache.geode.redis.internal.RedisConstants;
+import org.apache.geode.redis.internal.RedisDataType;
+import org.apache.geode.redis.internal.executor.AbstractExecutor;
+import org.apache.geode.redis.internal.executor.SortedSetQuery;
+
+public abstract class GeoSortedSetExecutor extends AbstractExecutor {
+
+  @Override
+  protected Region<ByteArrayWrapper, ByteArrayWrapper> getOrCreateRegion(
+      ExecutionHandlerContext context, ByteArrayWrapper key, RedisDataType type) {
+    @SuppressWarnings("unchecked")
+    Region<ByteArrayWrapper, ByteArrayWrapper> r =
+        (Region<ByteArrayWrapper, ByteArrayWrapper>) context
+            .getRegionProvider().getOrCreateRegion(key, type, context);
+    return r;
+  }
+
+  protected Region<ByteArrayWrapper, ByteArrayWrapper> getRegion(ExecutionHandlerContext context,
+      ByteArrayWrapper key) {
+    @SuppressWarnings("unchecked")
+    Region<ByteArrayWrapper, ByteArrayWrapper> r =
+        (Region<ByteArrayWrapper, ByteArrayWrapper>) context.getRegionProvider().getRegion(key);
+    return r;
+  }
+
+  protected List<StructImpl> getGeoRadiusRange(ExecutionHandlerContext context,
+      ByteArrayWrapper key, String hash) throws Exception {
+    Query query = getQuery(key, SortedSetQuery.GEORADIUS, context);
+    Object[] params = {hash + "%"};
+    SelectResults<StructImpl> results = (SelectResults<StructImpl>) query.execute(params);
+    return results.asList();
+  }
+
+  protected void respondGeoRadius(Command command, ExecutionHandlerContext context,
+      List<GeoRadiusResponseElement> results) {
+    ByteBuf rsp;
+    try {
+      rsp = GeoCoder.geoRadiusResponse(context.getByteBufAllocator(), results);
+    } catch (CoderException e) {
+      command.setResponse(Coder.getErrorResponse(context.getByteBufAllocator(),
+          RedisConstants.SERVER_ERROR_MESSAGE));
+      return;
+    }
+
+    command.setResponse(rsp);
+  }
+
+  protected void respondGeoCoordinates(Command command, ExecutionHandlerContext context,
+      List<LatLong> positions) {
+    ByteBuf rsp;
+    try {
+      rsp = GeoCoder.getBulkStringGeoCoordinateArrayResponse(context.getByteBufAllocator(),
+          positions);
+    } catch (CoderException e) {
+      command.setResponse(Coder.getErrorResponse(context.getByteBufAllocator(),
+          RedisConstants.SERVER_ERROR_MESSAGE));
+      return;
+    }
+
+    command.setResponse(rsp);
+  }
+}
diff --git a/geode-core/src/main/java/org/apache/geode/redis/internal/executor/sortedset/ZIncrByExecutor.java b/geode-core/src/main/java/org/apache/geode/redis/internal/executor/sortedset/ZIncrByExecutor.java
index aa95525..e63c5cb 100755
--- a/geode-core/src/main/java/org/apache/geode/redis/internal/executor/sortedset/ZIncrByExecutor.java
+++ b/geode-core/src/main/java/org/apache/geode/redis/internal/executor/sortedset/ZIncrByExecutor.java
@@ -60,7 +60,7 @@ public class ZIncrByExecutor extends SortedSetExecutor {
 
     if (score == null) {
       keyRegion.put(member, new DoubleWrapper(incr));
-      command.setResponse(Coder.getBulkStringResponse(context.getByteBufAllocator(), incr));
+      respondBulkStrings(command, context, incr);
       return;
     }
     double result = score.score + incr;
@@ -70,7 +70,7 @@ public class ZIncrByExecutor extends SortedSetExecutor {
     }
     score.score = result;
     keyRegion.put(member, score);
-    command.setResponse(Coder.getBulkStringResponse(context.getByteBufAllocator(), score.score));
+    respondBulkStrings(command, context, score.score);
   }
 
 }
diff --git a/geode-core/src/main/java/org/apache/geode/redis/internal/executor/sortedset/ZScoreExecutor.java b/geode-core/src/main/java/org/apache/geode/redis/internal/executor/sortedset/ZScoreExecutor.java
index 158adb3..00db72c 100755
--- a/geode-core/src/main/java/org/apache/geode/redis/internal/executor/sortedset/ZScoreExecutor.java
+++ b/geode-core/src/main/java/org/apache/geode/redis/internal/executor/sortedset/ZScoreExecutor.java
@@ -51,8 +51,7 @@ public class ZScoreExecutor extends SortedSetExecutor {
       command.setResponse(Coder.getNilResponse(context.getByteBufAllocator()));
       return;
     }
-    command
-        .setResponse(Coder.getBulkStringResponse(context.getByteBufAllocator(), score.toString()));
+    respondBulkStrings(command, context, score.toString());
   }
 
 }
diff --git a/geode-core/src/main/java/org/apache/geode/redis/internal/executor/string/GetExecutor.java b/geode-core/src/main/java/org/apache/geode/redis/internal/executor/string/GetExecutor.java
index 31aac75..6a4e1e5 100755
--- a/geode-core/src/main/java/org/apache/geode/redis/internal/executor/string/GetExecutor.java
+++ b/geode-core/src/main/java/org/apache/geode/redis/internal/executor/string/GetExecutor.java
@@ -38,14 +38,7 @@ public class GetExecutor extends StringExecutor {
     checkDataType(key, RedisDataType.REDIS_STRING, context);
     ByteArrayWrapper wrapper = r.get(key);
 
-    if (wrapper == null) {
-      command.setResponse(Coder.getNilResponse(context.getByteBufAllocator()));
-      return;
-    } else {
-      command.setResponse(
-          Coder.getBulkStringResponse(context.getByteBufAllocator(), wrapper.toBytes()));
-    }
-
+    respondBulkStrings(command, context, wrapper);
   }
 
 }
diff --git a/geode-core/src/main/java/org/apache/geode/redis/internal/executor/string/GetRangeExecutor.java b/geode-core/src/main/java/org/apache/geode/redis/internal/executor/string/GetRangeExecutor.java
index 56c246f..804488d 100755
--- a/geode-core/src/main/java/org/apache/geode/redis/internal/executor/string/GetRangeExecutor.java
+++ b/geode-core/src/main/java/org/apache/geode/redis/internal/executor/string/GetRangeExecutor.java
@@ -90,7 +90,6 @@ public class GetRangeExecutor extends StringExecutor {
       return;
     }
 
-    command.setResponse(Coder.getBulkStringResponse(context.getByteBufAllocator(), returnRange));
-
+    respondBulkStrings(command, context, returnRange);
   }
 }
diff --git a/geode-core/src/main/java/org/apache/geode/redis/internal/executor/string/GetSetExecutor.java b/geode-core/src/main/java/org/apache/geode/redis/internal/executor/string/GetSetExecutor.java
index adfcbea..9d93b3e 100755
--- a/geode-core/src/main/java/org/apache/geode/redis/internal/executor/string/GetSetExecutor.java
+++ b/geode-core/src/main/java/org/apache/geode/redis/internal/executor/string/GetSetExecutor.java
@@ -47,12 +47,6 @@ public class GetSetExecutor extends StringExecutor {
     ByteArrayWrapper oldValueWrapper = r.get(key);
     r.put(key, newValueWrapper);
 
-    if (oldValueWrapper == null) {
-      command.setResponse(Coder.getNilResponse(context.getByteBufAllocator()));
-    } else {
-      command.setResponse(
-          Coder.getBulkStringResponse(context.getByteBufAllocator(), oldValueWrapper.toBytes()));
-    }
-
+    respondBulkStrings(command, context, oldValueWrapper);
   }
 }
diff --git a/geode-core/src/main/java/org/apache/geode/redis/internal/executor/string/IncrByFloatExecutor.java b/geode-core/src/main/java/org/apache/geode/redis/internal/executor/string/IncrByFloatExecutor.java
index 0c20f66..221a080 100755
--- a/geode-core/src/main/java/org/apache/geode/redis/internal/executor/string/IncrByFloatExecutor.java
+++ b/geode-core/src/main/java/org/apache/geode/redis/internal/executor/string/IncrByFloatExecutor.java
@@ -84,7 +84,7 @@ public class IncrByFloatExecutor extends StringExecutor {
 
     if (valueWrapper == null) {
       r.put(key, new ByteArrayWrapper(incrArray));
-      command.setResponse(Coder.getBulkStringResponse(context.getByteBufAllocator(), increment));
+      respondBulkStrings(command, context, increment);
       return;
     }
 
@@ -122,7 +122,7 @@ public class IncrByFloatExecutor extends StringExecutor {
     stringValue = "" + value;
     r.put(key, new ByteArrayWrapper(Coder.stringToBytes(stringValue)));
 
-    command.setResponse(Coder.getBulkStringResponse(context.getByteBufAllocator(), value));
+    respondBulkStrings(command, context, value);
   }
 
 }
diff --git a/geode-core/src/main/java/org/apache/geode/redis/internal/executor/string/MGetExecutor.java b/geode-core/src/main/java/org/apache/geode/redis/internal/executor/string/MGetExecutor.java
index 7c9e808..5d65f6c 100755
--- a/geode-core/src/main/java/org/apache/geode/redis/internal/executor/string/MGetExecutor.java
+++ b/geode-core/src/main/java/org/apache/geode/redis/internal/executor/string/MGetExecutor.java
@@ -60,8 +60,7 @@ public class MGetExecutor extends StringExecutor {
     for (ByteArrayWrapper key : keys)
       values.add(results.get(key));
 
-    command.setResponse(Coder.getBulkStringArrayResponse(context.getByteBufAllocator(), values));
-
+    respondBulkStrings(command, context, values);
   }
 
 }
diff --git a/geode-core/src/main/java/org/apache/geode/redis/internal/executor/string/MSetExecutor.java b/geode-core/src/main/java/org/apache/geode/redis/internal/executor/string/MSetExecutor.java
index 5952625..3827247 100755
--- a/geode-core/src/main/java/org/apache/geode/redis/internal/executor/string/MSetExecutor.java
+++ b/geode-core/src/main/java/org/apache/geode/redis/internal/executor/string/MSetExecutor.java
@@ -56,7 +56,6 @@ public class MSetExecutor extends StringExecutor {
     r.putAll(map);
 
     command.setResponse(Coder.getSimpleStringResponse(context.getByteBufAllocator(), SUCCESS));
-
   }
 
 }
diff --git a/geode-core/src/main/resources/org/apache/geode/internal/sanctioned-geode-core-serializables.txt b/geode-core/src/main/resources/org/apache/geode/internal/sanctioned-geode-core-serializables.txt
index 6987220..ebf4602 100644
--- a/geode-core/src/main/resources/org/apache/geode/internal/sanctioned-geode-core-serializables.txt
+++ b/geode-core/src/main/resources/org/apache/geode/internal/sanctioned-geode-core-serializables.txt
@@ -632,6 +632,8 @@ org/apache/geode/pdx/internal/EnumInfo$PdxInstanceEnumInfo,true,7907582104525106
 org/apache/geode/pdx/internal/FieldNotFoundInPdxVersion,true,1292033563588485577
 org/apache/geode/pdx/internal/PdxInputStream,false
 org/apache/geode/pdx/internal/PdxReaderImpl,true,-6094553093860427759,blobType:org/apache/geode/pdx/internal/PdxType,dis:org/apache/geode/pdx/internal/PdxInputStream
+org/apache/geode/redis/internal/CoderException,true,4707944288714910949
+org/apache/geode/redis/internal/MemberNotFoundException,true,4707944288714910949
 org/apache/geode/redis/internal/RedisCommandParserException,true,4707944288714910949
 org/apache/geode/redis/internal/RedisCommandType,false
 org/apache/geode/redis/internal/RedisCommandType$1,false,dataType:org/apache/geode/redis/internal/RedisDataType,executor:org/apache/geode/redis/internal/Executor
@@ -651,6 +653,12 @@ org/apache/geode/redis/internal/RedisCommandType$110,false,dataType:org/apache/g
 org/apache/geode/redis/internal/RedisCommandType$111,false,dataType:org/apache/geode/redis/internal/RedisDataType,executor:org/apache/geode/redis/internal/Executor
 org/apache/geode/redis/internal/RedisCommandType$112,false,dataType:org/apache/geode/redis/internal/RedisDataType,executor:org/apache/geode/redis/internal/Executor
 org/apache/geode/redis/internal/RedisCommandType$113,false,dataType:org/apache/geode/redis/internal/RedisDataType,executor:org/apache/geode/redis/internal/Executor
+org/apache/geode/redis/internal/RedisCommandType$114,false,dataType:org/apache/geode/redis/internal/RedisDataType,executor:org/apache/geode/redis/internal/Executor
+org/apache/geode/redis/internal/RedisCommandType$115,false,dataType:org/apache/geode/redis/internal/RedisDataType,executor:org/apache/geode/redis/internal/Executor
+org/apache/geode/redis/internal/RedisCommandType$116,false,dataType:org/apache/geode/redis/internal/RedisDataType,executor:org/apache/geode/redis/internal/Executor
+org/apache/geode/redis/internal/RedisCommandType$117,false,dataType:org/apache/geode/redis/internal/RedisDataType,executor:org/apache/geode/redis/internal/Executor
+org/apache/geode/redis/internal/RedisCommandType$118,false,dataType:org/apache/geode/redis/internal/RedisDataType,executor:org/apache/geode/redis/internal/Executor
+org/apache/geode/redis/internal/RedisCommandType$119,false,dataType:org/apache/geode/redis/internal/RedisDataType,executor:org/apache/geode/redis/internal/Executor
 org/apache/geode/redis/internal/RedisCommandType$12,false,dataType:org/apache/geode/redis/internal/RedisDataType,executor:org/apache/geode/redis/internal/Executor
 org/apache/geode/redis/internal/RedisCommandType$13,false,dataType:org/apache/geode/redis/internal/RedisDataType,executor:org/apache/geode/redis/internal/Executor
 org/apache/geode/redis/internal/RedisCommandType$14,false,dataType:org/apache/geode/redis/internal/RedisDataType,executor:org/apache/geode/redis/internal/Executor
@@ -807,12 +815,15 @@ org/apache/geode/redis/internal/executor/SortedSetQuery$42,false
 org/apache/geode/redis/internal/executor/SortedSetQuery$43,false
 org/apache/geode/redis/internal/executor/SortedSetQuery$44,false
 org/apache/geode/redis/internal/executor/SortedSetQuery$45,false
+org/apache/geode/redis/internal/executor/SortedSetQuery$46,false
 org/apache/geode/redis/internal/executor/SortedSetQuery$5,false
 org/apache/geode/redis/internal/executor/SortedSetQuery$6,false
 org/apache/geode/redis/internal/executor/SortedSetQuery$7,false
 org/apache/geode/redis/internal/executor/SortedSetQuery$8,false
 org/apache/geode/redis/internal/executor/SortedSetQuery$9,false
 org/apache/geode/redis/internal/executor/list/ListExecutor$ListDirection,false
+org/apache/geode/redis/internal/executor/sortedset/GeoRadiusParameters$CommandType,false
+org/apache/geode/redis/internal/executor/sortedset/GeoRadiusParameters$SortOrder,false
 org/apache/geode/security/AuthenticationFailedException,true,-8202866472279088879
 org/apache/geode/security/AuthenticationRequiredException,true,4675976651103154919
 org/apache/geode/security/GemFireSecurityException,true,3814254578203076926,cause:java/lang/Throwable
diff --git a/geode-core/src/test/resources/expected-pom.xml b/geode-core/src/test/resources/expected-pom.xml
index 0ff3525..418271e 100644
--- a/geode-core/src/test/resources/expected-pom.xml
+++ b/geode-core/src/test/resources/expected-pom.xml
@@ -210,6 +210,12 @@
       <scope>compile</scope>
     </dependency>
     <dependency>
+      <groupId>com.github.davidmoten</groupId>
+      <artifactId>geo</artifactId>
+      <version>0.7.1</version>
+      <scope>compile</scope>
+    </dependency>
+    <dependency>
       <groupId>org.eclipse.jetty</groupId>
       <artifactId>jetty-webapp</artifactId>
       <version>9.4.12.v20180830</version>
diff --git a/gradle/dependency-versions.properties b/gradle/dependency-versions.properties
index a1203dc..4c6dab3 100644
--- a/gradle/dependency-versions.properties
+++ b/gradle/dependency-versions.properties
@@ -39,6 +39,7 @@ dom4j.version = 1.6.1
 fastutil.version = 8.2.1
 google-gson.version=2.8.5
 guava.version = 25.1-jre
+geo.version = 0.7.1
 hamcrest-all.version = 1.3
 httpclient.version = 4.5.6
 httpcore.version = 4.4.10