Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support GEOSHAPE field type in RediSearch #191

Merged
merged 17 commits into from
Oct 17, 2023
Merged
128 changes: 128 additions & 0 deletions Examples/GeoShapeQueryExample.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
# GeoShape Fields Usage In RediSearch

NRedisStack now supports GEOSHAPE field querying.

Any object that serializes the [well-known text (WKT)](https://en.wikipedia.org/wiki/Well-known_text_representation_of_geometry) as a `string` can be used with NRedisStack.

Using GeoShape fields in searches with the [NetTopologySuite](https://github.com/NetTopologySuite/NetTopologySuite) library.

## Example

### Modules Needed

```c#
using StackExchange.Redis;
using NRedisStack.RedisStackCommands;
using NRedisStack.Search;
using NetTopologySuite.Geometries;
using NetTopologySuite.IO;
```

### Setup

```csharp
// Connect to the Redis server:
var redis = ConnectionMultiplexer.Connect("localhost");
var db = redis.GetDatabase();
// Get a reference to the database and for search commands:
var ft = db.FT();

// Create WTKReader and GeometryFactory objects:
WKTReader reader = new WKTReader();
GeometryFactory factory = new GeometryFactory();

```

### Create the index

```csharp
ft.Create(index, new Schema().AddGeoShapeField("geom", GeoShapeField.CoordinateSystem.FLAT));
```

### Prepare the data

```csharp
Polygon small = factory.CreatePolygon(new Coordinate[]{new Coordinate(1, 1),
new Coordinate(1, 100), new Coordinate(100, 100), new Coordinate(100, 1), new Coordinate(1, 1)});
db.HashSet("small", "geom", small.ToString());

Polygon large = factory.CreatePolygon(new Coordinate[]{new Coordinate(1, 1),
new Coordinate(1, 200), new Coordinate(200, 200), new Coordinate(200, 1), new Coordinate(1, 1)});
db.HashSet("large", "geom", large.ToString());
```

## Polygon type

### Querying within condition

```csharp
Polygon within = factory.CreatePolygon(new Coordinate[]{new Coordinate(0, 0),
new Coordinate(0, 150), new Coordinate(150, 150), new Coordinate(150, 0), new Coordinate(0, 0)});

SearchResult res = ft.Search(index, new Query("@geom:[within $poly]")
.AddParam("poly", within.ToString()) // Note serializing the argument to string
.Dialect(3)); // DIALECT 3 is required for this query
```

The search result from redis is:

```bash
1) (integer) 1
2) "small"
3) 1) "geom"
2) "POLYGON ((1 1, 1 100, 100 100, 100 1, 1 1))"
```

Use the reader to get the polygon:

```csharp
reader.Read(res.Documents[0]["geom"].ToString());
```

### Querying contains condition

```csharp
Polygon contains = factory.CreatePolygon(new Coordinate[]{new Coordinate(2, 2),
new Coordinate(2, 50), new Coordinate(50, 50), new Coordinate(50, 2), new Coordinate(2, 2)});

res = ft.Search(index, new Query("@geom:[contains $poly]")
.AddParam("poly", contains.ToString()) // Note serializing the argument to string
.Dialect(3)); // DIALECT 3 is required for this query

```

Our search result:

```bash
1) (integer) 2
2) "small"
3) 1) "geom"
2) "POLYGON ((1 1, 1 100, 100 100, 100 1, 1 1))"
4) "large"
5) 1) "geom"
2) "POLYGON ((1 1, 1 200, 200 200, 200 1, 1 1))"
```

### Searching with Coordinates

```csharp
Point point = factory.CreatePoint(new Coordinate(10, 10));
db.HashSet("point", "geom", point.ToString());

res = ft.Search(index, new Query("@geom:[within $poly]")
.AddParam("poly", within.ToString()) // Note serializing the argument to string
.Dialect(3)); // DIALECT 3 is required for this query

```

Our search result:

```bash
1) (integer) 2
2) "small"
3) 1) "geom"
2) "POLYGON ((1 1, 1 100, 100 100, 100 1, 1 1))"
4) "point"
5) 1) "geom"
2) "POINT (10 10)"
```
3 changes: 2 additions & 1 deletion src/NRedisStack/NRedisStack.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="NetTopologySuite" Version="2.5.0" />
<PackageReference Include="System.Text.Json" Version="7.0.2" Condition="'$(TargetFramework)' == 'netstandard2.0'" />
<PackageReference Include="StackExchange.Redis" Version="2.6.96" />
<PackageReference Include="StackExchange.Redis" Version="2.6.122" />
shacharPash marked this conversation as resolved.
Show resolved Hide resolved
<None Include="..\..\README.md" Pack="true" PackagePath="\" />
</ItemGroup>

Expand Down
1 change: 1 addition & 0 deletions src/NRedisStack/Search/Query.cs
Original file line number Diff line number Diff line change
Expand Up @@ -617,6 +617,7 @@ public Query SetSortBy(string field, bool? ascending = null)
/// Parameters can be referenced in the query string by a $ , followed by the parameter name,
/// e.g., $user , and each such reference in the search query to a parameter name is substituted
/// by the corresponding parameter value.
/// Note: when calling this function with an externally supplied parameter, value should be a string.
/// </summary>
/// <param name="name"></param>
/// <param name="value"> can be String, long or float</param>
Expand Down
58 changes: 58 additions & 0 deletions src/NRedisStack/Search/Schema.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using NRedisStack.Search.Literals;
using static NRedisStack.Search.Schema.GeoShapeField;
using static NRedisStack.Search.Schema.VectorField;

namespace NRedisStack.Search
Expand All @@ -13,6 +14,7 @@ public enum FieldType
{
Text,
Geo,
GeoShape,
Numeric,
Tag,
Vector
Expand All @@ -38,6 +40,7 @@ internal void AddSchemaArgs(List<object> args)
{
FieldType.Text => "TEXT",
FieldType.Geo => "GEO",
FieldType.GeoShape => "GEOSHAPE",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good

FieldType.Numeric => "NUMERIC",
FieldType.Tag => "TAG",
FieldType.Vector => "VECTOR",
Expand Down Expand Up @@ -178,6 +181,37 @@ internal override void AddFieldTypeArgs(List<object> args)

}

public class GeoShapeField : Field
{
public enum CoordinateSystem
{
/// <summary>
/// For cartesian (X,Y).
/// </summary>
FLAT,

/// <summary>
/// For geographic (lon, lat).
/// </summary>
SPHERICAL
}
private CoordinateSystem system { get; }

internal GeoShapeField(FieldName name, CoordinateSystem system)
: base(name, FieldType.GeoShape)
{
this.system = system;
}

internal GeoShapeField(string name, CoordinateSystem system)
: this(FieldName.Of(name), system) { }

internal override void AddFieldTypeArgs(List<object> args)
{
args.Add(system.ToString());
}
}

public class NumericField : Field
{
public bool Sortable { get; }
Expand Down Expand Up @@ -288,6 +322,30 @@ public Schema AddTextField(FieldName name, double weight = 1.0, bool sortable =
return this;
}

/// <summary>
/// Add a GeoShape field to the schema.
/// </summary>
/// <param name="name">The field's name.</param>
/// <param name="system">The coordinate system to use.</param>
/// <returns>The <see cref="Schema"/> object.</returns>
public Schema AddGeoShapeField(string name, CoordinateSystem system)
{
Fields.Add(new GeoShapeField(name, system));
return this;
}

/// <summary>
/// Add a GeoShape field to the schema.
/// </summary>
/// <param name="name">The field's name.</param>
/// <param name="system">The coordinate system to use.</param>
/// <returns>The <see cref="Schema"/> object.</returns>
public Schema AddGeoShapeField(FieldName name, CoordinateSystem system)
{
Fields.Add(new GeoShapeField(name, system));
return this;
}

/// <summary>
/// Add a Geo field to the schema.
/// </summary>
Expand Down
2 changes: 1 addition & 1 deletion tests/Doc/Doc.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="StackExchange.Redis" Version="2.6.104" />
<PackageReference Include="StackExchange.Redis" Version="2.6.122" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\NRedisStack\NRedisStack.csproj" />
Expand Down
2 changes: 2 additions & 0 deletions tests/NRedisStack.Tests/Examples/ExampleTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1373,6 +1373,8 @@ public void AdvancedQueryOperationsTest()
Assert.Equal(expectedResSet, resSet);
}

// GeoShape Example Test is in SearchTests.cs, The test name is: GeoShapeFilterFlat.

private static void SortAndCompare(List<string> expectedList, List<string> res)
{
res.Sort();
Expand Down
5 changes: 3 additions & 2 deletions tests/NRedisStack.Tests/NRedisStack.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,12 @@
</PackageReference>
<PackageReference Include="dotenv.net" Version="3.1.2" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
<PackageReference Include="NetTopologySuite" Version="2.5.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
<PackageReference Include="StackExchange.Redis" Version="2.6.96" />
<PackageReference Include="StackExchange.Redis" Version="2.6.122" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.assert" Version="2.4.1" />
<PackageReference Include="BouncyCastle.Cryptography" Version="2.2.0"/>
<PackageReference Include="BouncyCastle.Cryptography" Version="2.2.0" />
</ItemGroup>

<ItemGroup>
Expand Down
Loading
Loading