Recursive AI

Geo radius search in C#

OneIndex is a free, open-source search library for .NET from Recursive AI. MIT or Apache-2.0, preview, runs in-process.

To search by location in C#, filter a [Geo] field by radius and sort by distance. Coordinates are latitude and longitude in degrees; the radius is in meters. This uses the preview release 0.4.0-preview.2.

Terminal
dotnet add package RecursiveAI.OneIndex

Requires .NET 10 (net10.0); .NET 9 and earlier aren’t supported.

C#
using OneIndex;
 
using var index = SearchIndex.InMemory<Place>();
index.Upsert([
    new() { Id = "center", Location = new(45.4727, 9.1840) },
    new() { Id = "near", Location = new(45.4800, 9.1840) },
    new() { Id = "far", Location = new(46.0000, 9.1840) }
]);
index.Commit();
var hits = index.Search()
    .Where(p => Geo.WithinRadius(
        p.Location, 45.4727, 9.1840, 2000))
    .OrderByDistance(p => p.Location, 45.4727, 9.1840)
    .Execute();
foreach (var hit in hits)
    Console.WriteLine(hit.Document.Id);
 
public class Place
{
    [SearchId] public string Id { get; set; } = "";
    [Geo] public GeoPoint Location { get; set; }
}
Text
center
near

The three invented coordinates are included in the code: the query center, a point nearby and a point farther north. The in-memory index starts empty on every run.

Invalid coordinates or a nonfinite or negative radius are rejected; see the geo input rules.

Unzip the example, then dotnet run.

Variations

Reduce the radius to 500 meters. Replace the search and print loop in the full program with this snippet.

C#
var nearby = index.Search()
    .Where(p => Geo.WithinRadius(
        p.Location, 45.4727, 9.1840, 500))
    .OrderByDistance(p => p.Location, 45.4727, 9.1840)
    .Execute();
foreach (var hit in nearby)
    Console.WriteLine(hit.Document.Id);
Text
center

The short answer

To search by location in C#, store coordinates in a OneIndex [Geo] field, filter with Geo.WithinRadius, and sort with OrderByDistance. Latitude and longitude are in degrees; the radius is in meters. This example returns the two invented points inside a radius of 2,000 meters, nearest first.

The sample uses the pinned 0.4.0-preview.2 package, a preview release. It creates an in-memory index, inserts three documents and commits them before searching. The geo search documentation defines filtering, sorting and coordinate validation for this version.

How do I write a geo search query?

Represent a searchable place with a document ID and a GeoPoint property marked [Geo]. Use latitude first and longitude second when constructing a point and when supplying the query center. Reversing the numbers can produce a valid coordinate in a completely different place, so constructor validation alone cannot catch that application error.

A radius filter decides which documents match; it does not by itself request nearest-first ordering. The sample adds OrderByDistance with the same field and center to make that order explicit. The outer point at latitude 46 is excluded, while the center point is returned before the nearby point. The variation reduces the radius and returns only the center.

Filtering and sorting are separate decisions. Use the radius to express the geographic region your application accepts, then choose whether proximity is the desired order. The LINQ geo predicate can be combined with other predicates using the documented boolean operators.

How does geo location search work?

OneIndex uses a haversine great-circle distance on a sphere, not driving distance or walking time. The documented sphere radius is 6,371,008.8 meters. That definition is useful for understanding borderline matches: a straight geographic radius does not describe road accessibility or a route around barriers.

The index proposes candidate points and then confirms them using their stored exact coordinates. Radius matching therefore does not depend on the geo index’s coordinate quantization. A segment with no values for that geo field matches nothing. Documents may hold several points per geo field, as described in the geo documentation; the sample deliberately keeps one per document.

A bounding-box query answers a different question: membership in latitude and longitude bounds, not distance from a center. Choose that shape when the requirement is a rectangular map viewport. Choose a radius when the requirement is distance from a point. Neither automatically assigns names to addresses; the example already has numeric coordinates before indexing.

Latitude must be within -90 through 90 and longitude within -180 through 180. GeoPoint rejects invalid coordinates and NaN. A radius must be finite and nonnegative; invalid inputs are rejected at construction or parsing rather than silently corrected. A radius larger than half the circumference matches every point.

The downloadable program uses InMemory so each run starts with the same three documents. For persistent application data, the same geo field and query concepts apply to an opened index, but the application must manage its own index path, updates and commit lifecycle.

All oneindex recipes