Skip to main content

Geospatial indexes

Use redis.Geo from the core Respire package. Coordinates are longitude first, then latitude. Basic geo commands require Redis 3.2+; SearchAsync and SearchStoreAsync require Redis 6.2+.

await using var redis = await RespireClient.ConnectAsync("redis://localhost:6379");
long added = await redis.Geo.AddAsync("{places}:cafes",
new RespireGeoEntry(-0.1276, 51.5072, "london"),
new RespireGeoEntry(-0.1419, 51.5014, "westminster"));

RespireGeoSearchResult[] nearby = await redis.Geo.SearchAsync(
"{places}:cafes",
RespireGeoSearchOrigin.FromCoordinates(-0.1, 51.5),
RespireGeoSearchShape.Circle(10, RespireGeoUnit.Kilometers),
new RespireGeoSearchOptions
{
Sort = RespireGeoSortOrder.Ascending,
Count = 10,
IncludeDistance = true,
IncludeCoordinates = true,
});

RespireGeoSearchOrigin.FromMember searches around an existing member. Use RespireGeoSearchShape.Box for a rectangular area. Count limits results and must be positive. Any = true requires Count and Sort = RespireGeoSortOrder.Unsorted; it cannot be combined with the ascending sort in the example above. It lets the server stop early, so it does not guarantee the nearest matches. Distance uses the search shape's unit.

Polygon searches (Valkey 9.0+)​

Use RespireGeoSearchShape.Polygon with the overload that omits the origin:

var area = RespireGeoSearchShape.Polygon(
new RespireGeoPosition(-0.2, 51.4),
new RespireGeoPosition(0, 51.4),
new RespireGeoPosition(0, 51.6),
new RespireGeoPosition(-0.2, 51.6));

var matches = await redis.Geo.SearchAsync("{places}:cafes", area,
new RespireGeoSearchOptions { IncludeDistance = true, IncludeCoordinates = true });
long stored = await redis.Geo.SearchStoreAsync("{places}:area", "{places}:cafes", area);

This sends GEOSEARCH key BYPOLYGON count longitude latitude ... or GEOSEARCHSTORE destination source BYPOLYGON count longitude latitude .... The Valkey command reference requires no FROMMEMBER, FROMLONLAT, or distance unit for polygons. Valkey computes the bounding box and a spherical centroid from the vertices. Distances from that centroid, including STOREDIST scores, are in meters.

The factory copies the vertices. It accepts an open ring or a closed ring whose last vertex repeats the first; repeated closing vertices are removed before sending. At least three distinct vertices are required, with finite longitude in [-180, 180] and latitude in [-85.05112878, 85.05112878]. Supply vertices in boundary order; Respire does not check self-intersections or repair polygon geometry. An explicit origin is rejected for polygons. Circle and box searches still require an origin.

Sorting, Count, Any, and result details work as for the other shapes. batch.Geo.Search(key, area) and transaction.Geo.Search(key, area), plus their SearchStore counterparts, use the same shape and validation.

Older Valkey and unsupported Redis servers reject BYPOLYGON with a RespireServerException. Typed polygon syntax errors include a BYPOLYGON requires Valkey 9.0+ compatibility hint while retaining the server error code, command name, and endpoint. Raw commands and circle/box searches retain the server's original error message. Other errors, such as WRONGTYPE or ACL denials, retain their original message.

Commands and results​

MethodRedis commandResult
AddAsyncGEOADDNumber of newly added members; RespireGeoAddOptions.Changed includes updated members
DistanceAsyncGEODISTDistance, or null when either member is absent
HashAsyncGEOHASHOne geohash string per member, with null for missing members
PositionAsyncGEOPOSOne nullable RespireGeoPosition per member
SearchAsyncGEOSEARCHMembers and requested distance, hash, and coordinate details
SearchStoreAsyncGEOSEARCHSTORENumber of members stored at the destination

RespireGeoAddOptions.When selects conditional writes with RespireSetWhen. Search result Member decodes UTF-8; use MemberBytes for exact binary member data. Optional result fields are null unless requested.

Store results and remove members​

await using var redis = await RespireClient.ConnectAsync("redis://localhost:6379");
long stored = await redis.Geo.SearchStoreAsync(
"{places}:nearby", "{places}:cafes",
RespireGeoSearchOrigin.FromCoordinates(-0.1, 51.5),
RespireGeoSearchShape.Circle(10, RespireGeoUnit.Kilometers));
await redis.SortedSets.RemoveAsync("{places}:cafes", "london");

Geo indexes are sorted sets, so remove a member through SortedSets.RemoveAsync. storeDistance: true stores distances as destination scores instead of geospatial scores. Key prefixes apply to source and destination keys, not member names. In Cluster, both keys must share one slot; the {places} hash tag above keeps them together.

batch.Geo and transaction.Geo expose the same operations without the Async suffix. Read pending values after executing the batch or transaction.