Spatial Queries with the .NET Client

Tip
The source code for this tutorial is available on GitHub.

This tutorial demonstrates how to run geospatial queries on indexed Infinispan caches using the .NET Hot Rod client.

Prerequisites

  • A running Infinispan server.

  • .NET 10 or later.

Running the Example

dotnet run --project spatial-queries

Code Walkthrough

Schema and Code Generation

The example uses Protos/restaurant.proto and Protos/trainroute.proto with Grpc.Tools to generate C# classes at build time. The same schemas — with Infinispan spatial annotations — are registered on the server.

The Restaurant schema uses @GeoPoint with @Latitude and @Longitude annotations to create a composite spatial field called location.

/**
 * @Indexed
 * @GeoPoint(fieldName = "location", projectable = true, sortable = true)
 */
message Restaurant {
    /** @Keyword(normalizer = "lowercase", projectable = true, sortable = true) */
    string name = 1;
    /** @Latitude(fieldName = "location") */
    double latitude = 4;
    /** @Longitude(fieldName = "location") */
    double longitude = 5;
}

The TrainRoute schema demonstrates multiple geo points on a single entity.

/**
 * @Indexed
 * @GeoPoint(fieldName = "departure", projectable = true, sortable = true)
 * @GeoPoint(fieldName = "arrival", projectable = true, sortable = true)
 */
message TrainRoute {
    /** @Latitude(fieldName = "departure") */
    double departureLat = 2;
    /** @Longitude(fieldName = "departure") */
    double departureLon = 3;
    /** @Latitude(fieldName = "arrival") */
    double arrivalLat = 4;
    /** @Longitude(fieldName = "arrival") */
    double arrivalLon = 5;
}

Creating Typed Caches

Two cache instances share the same underlying cache — one per entity type. Marshallers are inferred automatically from the generated protobuf classes.

var restaurantCache = client.NewCache<Restaurant>("spatialCache")
    .WithEncoding(MediaType.Protobuf)
    .Build();
var trainRouteCache = client.NewCache<TrainRoute>("spatialCache")
    .WithEncoding(MediaType.Protobuf)
    .Build();

Within Circle (Typed)

Find restaurants within a 100-meter radius. Query<Restaurant>() returns typed results.

var results = await restaurantCache.Query<Restaurant>(
    "from tutorial.Restaurant r where r.location within circle(41.908470, 12.455633, 100)");
foreach (var r in results)
    Console.WriteLine($"  {r.Name}");

Distance Projection

Use the distance() function to include the distance in meters in projections.

var results = await restaurantCache.Query(
    "select r.name, distance(r.location, 41.908470, 12.455633) from tutorial.Restaurant r");
foreach (object[] row in results)
    Console.WriteLine($"  {row[0]}: {Convert.ToDouble(row[1]):F0}m");

Multi-GeoPoint Queries

Query train routes using their departure geo point.

var results = await trainRouteCache.Query<TrainRoute>(
    "from tutorial.TrainRoute r where r.departure within circle(44.4949, 11.3426, 300000)");

Expected Output

The example runs 6 query demonstrations: within circle, within box, within polygon, distance projection, order by distance, and multi-geo-point queries on train routes.