Skip to content

Commit d81e0d7

Browse files
Merge pull request #328 from codecrafters-io/redis-geospatial
Add: Redis Geospatial Command Stages
2 parents 7aae395 + fdda190 commit d81e0d7

9 files changed

+518
-1
lines changed

course-definition.yml

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,17 @@ extensions:
138138
[redis-zset]: https://redis.io/docs/latest/develop/data-types/sorted-sets/
139139
[zadd-command]: https://redis.io/docs/latest/commands/zadd/
140140
[zrange-command]: https://redis.io/docs/latest/commands/zrange/
141+
142+
- slug: "geospatial"
143+
name: "Geospatial Commands"
144+
description_markdown: |
145+
In this challenge extension you'll add support for [Geospatial Commands][geospatial-commands] to your Redis implementation.
146+
147+
Along the way, you'll learn commands like [GEOADD][geoadd-command], [GEOSEARCH][geosearch-command], and more.
148+
149+
[geospatial-commands]: https://redis.io/docs/latest/develop/data-types/geospatial/
150+
[geoadd-command]: https://redis.io/docs/latest/commands/geoadd/
151+
[geosearch-command]: https://redis.io/docs/latest/commands/geosearch/
141152
142153
stages:
143154
- slug: "jm1"
@@ -744,4 +755,53 @@ stages:
744755
name: "Remove a member"
745756
difficulty: easy
746757
marketing_md: |
747-
In this stage, you'll add support for removing a member of a sorted set using the `ZREM` command.
758+
In this stage, you'll add support for removing a member of a sorted set using the `ZREM` command.
759+
760+
# Geospatial Commands
761+
- slug: "zt4"
762+
primary_extension_slug: "geospatial"
763+
name: "Respond to GEOADD"
764+
difficulty: easy
765+
marketing_md: In this stage, you'll add support for responding to the `GEOADD` command.
766+
767+
- slug: "ck3"
768+
primary_extension_slug: "geospatial"
769+
name: "Validate coordinates"
770+
difficulty: easy
771+
marketing_md: In this stage, you'll add support for validating the latitude and longitude provided in the `GEOADD` command.
772+
773+
- slug: "tn5"
774+
primary_extension_slug: "geospatial"
775+
name: "Store a location"
776+
difficulty: medium
777+
marketing_md: In this stage, you'll add support for storing a location in a sorted set.
778+
779+
- slug: "cr3"
780+
primary_extension_slug: "geospatial"
781+
name: "Calculate location score"
782+
difficulty: hard
783+
marketing_md: In this stage, you'll add support for calculating the score of a location.
784+
785+
- slug: "xg4"
786+
primary_extension_slug: "geospatial"
787+
name: "Respond to GEOPOS"
788+
difficulty: easy
789+
marketing_md: In this stage, you'll add support for responding to the `GEOPOS` command.
790+
791+
- slug: "hb5"
792+
primary_extension_slug: "geospatial"
793+
name: "Decode coordinates"
794+
difficulty: hard
795+
marketing_md: In this stage, you'll add support for decoding the coordinates of a location.
796+
797+
- slug: "ek6"
798+
primary_extension_slug: "geospatial"
799+
name: "Calculate distance"
800+
difficulty: medium
801+
marketing_md: In this stage, you'll add support for calculating the distance between two locations using the `GEODIST` comamnd.
802+
803+
- slug: "rm9"
804+
primary_extension_slug: "geospatial"
805+
name: "Search within radius"
806+
difficulty: easy
807+
marketing_md: In this stage, you'll add support for searching locations near a coordinate within a given radius using the `GEOSEARCH` command.
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
In this stage, you'll add support for responding to the `GEOADD` command.
2+
3+
### Extension prerequisites
4+
5+
This stage depends on the [**Sorted Sets**](https://redis.io/docs/latest/data-types/sorted-sets/) extension. Before attempting this extension, please make sure you've completed the Sorted Sets extension.
6+
7+
### The `GEOADD` command
8+
9+
The [`GEOADD` command](https://redis.io/docs/latest/commands/geoadd/) adds a location (with longitude, latitude, and name) to a key.
10+
11+
Example usage:
12+
13+
```bash
14+
> GEOADD places -0.0884948 51.506479 "London"
15+
(integer) 1
16+
```
17+
18+
The arguments `GEOADD` accepts are:
19+
20+
1. `key`: The key to store the location in.
21+
2. `longitude`: The longitude of the location.
22+
3. `latitude`: The latitude of the location.
23+
4. `member`: The name of the location.
24+
25+
It returns the count of elements added, encoded as a RESP Integer.
26+
27+
In this stage, you'll only implement the response to the `GEOADD` command. We'll get to validating arguments and storing locations in later stages.
28+
29+
### Tests
30+
31+
The tester will execute your program like this:
32+
33+
```bash
34+
$ ./your_program.sh
35+
```
36+
37+
It will then send a `GEOADD` command:
38+
39+
```bash
40+
$ redis-cli GEOADD places 11.5030378 48.164271 Munich
41+
```
42+
43+
The tester will expect the response to be `:1\r\n`, which is 1 (number of locations added) encoded as a RESP integer.
44+
45+
### Notes
46+
47+
- In this stage, you only need to implement responding to the `GEOADD` command. We'll get to validating arguments and storing locations in later stages.
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
In this stage, you'll add support for validating the latitude and longitude values provided in a `GEOADD` command.
2+
3+
### Validating latitude and longitude values
4+
5+
The latitude and longitude values used in the `GEOADD` command should be in a certain range as per [EPSG:3857](https://epsg.io/3857).
6+
7+
- Valid longitudes are from -180° to +180°
8+
- Both these limits are inclusive, so -180° and +180° are both valid.
9+
- Valid latitudes are from -85.05112878° to +85.05112878°
10+
- Both of these limits are inclusive, so -85.05112878° and +85.05112878° are both valid.
11+
- The reason these limits are not -/+90° is because of the [Web Mercator projection](https://en.wikipedia.org/wiki/Web_Mercator_projection) that Redis uses.
12+
13+
If either of these values aren't within the appropriate range, `GEOADD` returns an error. Examples:
14+
15+
```bash
16+
# Invalid latitude
17+
> GEOADD places 180 90 test1
18+
(error) ERR invalid longitude,latitude pair 180.000000,90.000000
19+
20+
# Invalid longitude
21+
> GEOADD places 181 0.3 test2
22+
(error) ERR invalid longitude,latitude pair 181.000000,0.300000
23+
```
24+
25+
In this stage, you'll implement validating latitude and longitude values and returning error messages as shown above. The tester is lenient with error message formats, so you don't have to use the exact error message format mentioned above.
26+
27+
### Tests
28+
29+
The tester will execute your program like this:
30+
31+
```bash
32+
$ ./your_program.sh
33+
```
34+
35+
It will then send multiple `GEOADD` commands.
36+
37+
If the coordinates are invalid, it will expect an error response. For example:
38+
39+
```bash
40+
# Expecting error
41+
$ redis-cli GEOADD location_key 200 100 foo
42+
(error) ERR invalid longitude,latitude pair 200,100
43+
```
44+
45+
The returned value must:
46+
47+
- Be a [RESP simple error](https://redis.io/docs/latest/develop/reference/protocol-spec/#simple-errors) i.e. start with `-` and end with `\r\n`
48+
- The error message must start with `ERR`, like standard Redis error messages
49+
- The error message must contain the word "latitude" if the latitude is invalid
50+
- The error message must contain the word "longitude" if the longitude is invalid
51+
52+
For example, if the latitude is invalid, valid error messages that the tester will accept are:
53+
54+
```bash
55+
-ERR invalid latitude argument\r\n
56+
-ERR invalid latitude value\r\n
57+
-ERR latitude value (200.0) is invalid\r\n
58+
```
59+
60+
### Notes
61+
62+
- You don't need to implement storing locations yet, we'll get to that in later stages.
63+
- The boundary of latitude are clipped at -/+85.05112878° instead of -/+90°. This is because of the [Web Mercator projection](https://en.wikipedia.org/wiki/Web_Mercator_projection) that Redis uses.
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
In this stage, you'll add support for storing locations in a sorted set.
2+
3+
### Storing locations in sorted set
4+
5+
The locations added using the `GEOADD` command are stored in a sorted set. Redis internally calculates a score for the specified location using a location's latitude and longitude.
6+
7+
For example, the following two commands are equivalent in Redis.
8+
9+
```bash
10+
# Adding a location
11+
$ redis-cli GEOADD places_key 2.2944692 48.8584625 location
12+
13+
# This command is equivalent to the command above
14+
$ redis-cli ZADD places_key 3663832614298053 location
15+
```
16+
17+
In this stage, you'll implement adding locations to a sorted set when a `GEOADD` command is run.
18+
19+
You can hardcode the score to be 0 for now, we'll get to calculating the score in later stages.
20+
21+
### Tests
22+
23+
The tester will execute your program like this:
24+
25+
```bash
26+
$ ./your_program.sh
27+
```
28+
29+
It will then send a `GEOADD` command:
30+
31+
```bash
32+
$ redis-cli GEOADD places 2.2944692 48.8584625 Paris
33+
# Expect: (integer) 1
34+
```
35+
36+
The tester will then send a `ZRANGE` command to validate that the location was added to the sorted set:
37+
38+
```bash
39+
$ redis-cli ZRANGE places 0 -1
40+
# Expect RESP Array: ["Paris"]
41+
```
42+
43+
### Notes
44+
45+
- In this stage, you can hardcode the score of the location to be 0. We'll get to calculating the value of score using latitude and longitude in later stages.
46+
- The implementation of the `ZRANGE` command is covered in the sorted sets extension.
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
In this stage, you'll add support for calculating the score of a location.
2+
3+
### Location scores
4+
5+
To store locations in a sorted set, Redis converts latitude and longitude values to a single "score".
6+
7+
We've created a [GitHub repository](https://github.com/codecrafters-io/redis-geocoding-algorithm) that explains how this conversion is done. It includes:
8+
9+
- A description of the algorithm used, along with pseudocode
10+
- Python code that implements the algorithm
11+
- A set of locations & scores to test against
12+
13+
Here's the [repository link](https://github.com/codecrafters-io/redis-geocoding-algorithm).
14+
15+
### Tests
16+
17+
The tester will execute your program like this:
18+
19+
```bash
20+
$ ./your_program.sh
21+
```
22+
23+
It will then send multiple `GEOADD` commands:
24+
25+
```bash
26+
$ redis-cli GEOADD places 2.2944692 48.8584625 Paris
27+
# Expect: (integer) 1
28+
$ redis-cli GEOADD places -0.127758 51.507351 London
29+
# Expect: (integer) 1
30+
```
31+
32+
The tester will validate that scores are calculated correctly by sending multiple `ZSCORE` commands:
33+
34+
```bash
35+
$ redis-cli ZSCORE places Paris
36+
# Expecting bulk string: "3663832614298053"
37+
```
38+
39+
The calculated scores must match the expected values as described in [this repository](https://github.com/codecrafters-io/redis-geocoding-algorithm).
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
In this stage, you'll add support for responding to the `GEOPOS` command.
2+
3+
### The `GEOPOS` command
4+
5+
The `GEOPOS` command returns the longitude and latitude of the specified location.
6+
7+
Example usage:
8+
9+
```bash
10+
> GEOADD places -0.0884948 51.506479 "London"
11+
> GEOADD places 11.5030378 48.164271 "Munich"
12+
13+
> GEOPOS places London
14+
1) 1) "-0.08849412202835083"
15+
2) "51.50647814139934"
16+
17+
> GEOPOS places Munich
18+
1) 1) "11.503036916255951"
19+
2) "48.16427086232978"
20+
```
21+
22+
It returns an array with one entry for each location requested.
23+
24+
- If the key does not exist, an empty array is returned `(*0\r\n)`
25+
- If a location exists under the key, its entry is an array of two items:
26+
- Longitude (Encoded as a [RESP Bulk string](https://redis.io/docs/latest/develop/reference/protocol-spec/#bulk-strings))
27+
- Latitude (Encoded as a [RESP Bulk string](https://redis.io/docs/latest/develop/reference/protocol-spec/#bulk-strings))
28+
- If a location doesn’t exist, the entry is a [null bulk string](https://redis.io/docs/latest/develop/reference/protocol-spec/#null-bulk-strings) `($-1\r\n)`.
29+
30+
To return the latitude and longitude values, Redis decodes the "score" back to latitude and longitude values. We'll cover this process in later stages, for now you can hardcode the returned latitude and longitude values to be 0 (or any number).
31+
32+
### Tests
33+
34+
The tester will execute your program like this:
35+
36+
```bash
37+
$ ./your_program.sh
38+
```
39+
40+
It will then add multiple locations using the `GEOADD` command.
41+
42+
```bash
43+
$ redis-cli
44+
> GEOADD location_key -0.0884948 51.506479 "London"
45+
# Expect: (integer) 1
46+
> GEOADD location_key 11.5030378 48.164271 "Munich"
47+
# Expect: (integer) 1
48+
```
49+
50+
The tester will then send multiple `GEOPOS` commands:
51+
52+
```bash
53+
> GEOPOS location_key London Munich
54+
# Expecting: [["0", "0"], ["0", "0"]], encoded as "*2\r\n$1\r\n0\r\n$1\r\n0\r\n$1\r\n0\r\n$1\r\n0\r\n"
55+
> GEOPOS location_key missing_location
56+
# Expecting: [nil], encoded as "*1\r\n$-1\r\n"
57+
```
58+
59+
The tester will assert that:
60+
61+
- The response is a RESP array that contains as many elements as the number of locations requested
62+
- For each location requested:
63+
- If the location exists:
64+
- The corresponding element is a RESP array with two elements (i.e. longitude and latitude)
65+
- Both elements are "0" (or any other valid floating point number), encoded as a RESP bulk string
66+
- If the location doesn't exist:
67+
- The corresponding element is a [null bulk string](https://redis.io/docs/latest/develop/reference/protocol-spec/#null-bulk-strings) `($-1\r\n)`.
68+
69+
The tester will also send a `GEOPOS` command using a key that doesn't exist:
70+
71+
```bash
72+
> GEOPOS missing_key Foo
73+
# Expecting: [], encoded as "*0\r\n"
74+
```
75+
76+
It will expect the response to be an empty RESP array, which is encoded as `*0\r\n`.
77+
78+
### Notes
79+
80+
- In this stage, you can hardcode the returned latitude and longitude values to be 0 (or any other valid floating point number). We'll get to testing actual latitude and longitude values in later stages.

0 commit comments

Comments
 (0)