# Geospatial

> Store longitude and latitude per member, then measure distances and search by radius or box.

URL: https://docs.hotkv.com/data-types/geospatial/


Geospatial commands store a position (longitude and latitude) for each
member of a key, and answer questions such as "how far apart are these two
places?" and "which stores are within 10 km of this point, nearest first?".
They suit store locators, delivery and ride matching, and any "near me"
feature.

A geospatial key is a [sorted set](https://docs.hotkv.com/data-types/sorted-sets/index.md) whose scores
encode positions, so `TYPE` reports `zset` and the sorted-set commands work
on it too: [ZREM](https://docs.hotkv.com/commands/zrem/index.md) removes a place, [ZCARD](https://docs.hotkv.com/commands/zcard/index.md)
counts them, and [ZSCAN](https://docs.hotkv.com/commands/zscan/index.md) iterates them. The scores are
the same ones Redis computes, so a geospatial key copied between Redis and
HotKV keeps its positions.

## Create, read, update, delete

[GEOADD](https://docs.hotkv.com/commands/geoadd/index.md) takes **longitude first, then latitude**, then
the member name. Adding an existing member moves it.

```
127.0.0.1:6379> GEOADD cities 16.3738 48.2082 "vienna" 17.1077 48.1486 "bratislava" 19.0402 47.4979 "budapest"
(integer) 3
127.0.0.1:6379> GEODIST cities vienna bratislava km
"54.8333"
127.0.0.1:6379> GEODIST cities vienna budapest km
"214.1047"
127.0.0.1:6379> GEOSEARCH cities FROMLONLAT 16.5 48.2 BYRADIUS 100 km ASC WITHDIST
1) 1) "vienna"
   2) "9.3996"
2) 1) "bratislava"
   2) "45.4360"
127.0.0.1:6379> ZREM cities budapest
(integer) 1
```

Distances are returned with four decimals in the unit you ask for: `m`
(the default), `km`, `mi` or `ft`. [GEOPOS](https://docs.hotkv.com/commands/geopos/index.md) returns the
stored coordinates of members, and [GEOHASH](https://docs.hotkv.com/commands/geohash/index.md) returns
them as standard 11-character geohash strings.

### GEOADD options

`NX` only adds new members, `XX` only moves existing ones, and `CH` makes
the reply count moved members as well as added ones.

### Searching

[GEOSEARCH](https://docs.hotkv.com/commands/geosearch/index.md) finds members inside a shape around a
center:

- Center: `FROMLONLAT <lon> <lat>`, or `FROMMEMBER <member>` to search
  around a stored place.
- Shape: `BYRADIUS <radius> <unit>` for a circle, or
  `BYBOX <width> <height> <unit>` for a rectangle.
- `ASC` or `DESC` sorts by distance; `COUNT n` limits the results (with
  `ANY`, HotKV returns as soon as it has found `n` matches, which is faster
  but not necessarily the nearest).
- `WITHDIST`, `WITHCOORD` and `WITHHASH` add the distance, the position,
  and the raw score to each result.

[GEOSEARCHSTORE](https://docs.hotkv.com/commands/geosearchstore/index.md) writes the matches to another
key, as a geospatial set or, with `STOREDIST`, as a sorted set scored by
distance.

The older [GEORADIUS](https://docs.hotkv.com/commands/georadius/index.md) and
[GEORADIUSBYMEMBER](https://docs.hotkv.com/commands/georadiusbymember/index.md) commands (and their
`_RO` read-only forms) still work for existing code.

## Accuracy and limits

- Longitudes run from -180 to 180. Latitudes are limited to the range
  -85.05112878 to 85.05112878, as in Redis: positions closer to the poles
  are refused.
- Positions are stored at a fixed precision of well under a meter, so
  `GEOPOS` returns values that differ slightly from what you added.
- Distances treat the Earth as a sphere, which can be off by up to about
  0.5% compared with the true ellipsoid. That is fine for "nearby" searches
  but not for surveying.

## Common patterns

**Store locator.** One key per brand or region, one member per store ID,
details in a [hash](https://docs.hotkv.com/data-types/hashes/index.md) per store. `GEOSEARCH ...
FROMLONLAT <user position> BYRADIUS 10 km ASC COUNT 20 WITHDIST` lists the
nearest twenty.

**Moving objects.** Re-run `GEOADD` with each position update for drivers
or vehicles, and `ZREM` them when they go offline. Searches always see the
latest position.

**Nearby, but only some members.** Search with `GEOSEARCHSTORE` into a
temporary key, then intersect it with a set of matching IDs using
`ZINTERSTORE`, or filter the results in your application.

## Expiry

A time to live applies to the whole key; single places cannot expire. To
drop stale positions, keep a separate sorted set of "last seen" times and
remove members from both keys.

## Commands

| Command | What it does |
| --- | --- |
| [GEOADD](https://docs.hotkv.com/commands/geoadd/index.md) | Add or move members |
| [GEODIST](https://docs.hotkv.com/commands/geodist/index.md) | Distance between two members |
| [GEOPOS](https://docs.hotkv.com/commands/geopos/index.md) | Stored positions of members |
| [GEOHASH](https://docs.hotkv.com/commands/geohash/index.md) | Geohash strings of members |
| [GEOSEARCH](https://docs.hotkv.com/commands/geosearch/index.md) | Members within a radius or box |
| [GEOSEARCHSTORE](https://docs.hotkv.com/commands/geosearchstore/index.md) | Store search results in a key |
| [GEORADIUS](https://docs.hotkv.com/commands/georadius/index.md) / [GEORADIUS_RO](https://docs.hotkv.com/commands/georadius_ro/index.md) | Older radius search around a point |
| [GEORADIUSBYMEMBER](https://docs.hotkv.com/commands/georadiusbymember/index.md) / [GEORADIUSBYMEMBER_RO](https://docs.hotkv.com/commands/georadiusbymember_ro/index.md) | Older radius search around a member |

## From your application

**Python**

```python
import hotkv

client = hotkv.HotKV(host="localhost", port=6379, decode_responses=True)

client.geoadd("cities", {
    "vienna": (16.3738, 48.2082),
    "bratislava": (17.1077, 48.1486),
})
print(client.geodist("cities", "vienna", "bratislava", unit="km"))
nearby = client.geosearch("cities", longitude=16.5, latitude=48.2,
                          radius=100, unit="km", sort="ASC", withdist=True)
print(nearby)
client.close()
```

**Node.js**

```ts
import { HotKV } from "@hotkv/client";

const client = new HotKV({ url: "hotkv://127.0.0.1:6379" });

await client.geoAdd("cities", [
  { longitude: 16.3738, latitude: 48.2082, member: "vienna" },
  { longitude: 17.1077, latitude: 48.1486, member: "bratislava" },
]);
console.log(await client.geoDist("cities", "vienna", "bratislava", "km"));
const nearby = await client.geoSearch("cities", {
  fromLonLat: [16.5, 48.2],
  byRadius: { radius: 100, unit: "km" },
  asc: true,
  withDist: true,
});
console.log(nearby);
await client.close();
```

**Java**

```java
import com.hotkv.HotKV;

try (HotKV client = HotKV.create("hotkv://localhost:6379")) {
    client.geoAdd("cities", 16.3738, 48.2082, "vienna");
    client.geoAdd("cities", 17.1077, 48.1486, "bratislava");
    System.out.println(client.geoDist("cities", "vienna", "bratislava", "km"));
}
```

To use the geospatial commands from the Go client, send `GEOADD`,
`GEOSEARCH` and the rest through a pipeline's `Do`.