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
123 changes: 123 additions & 0 deletions Examples/GeoShape.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
# GeoShape Fields Usage In RediSearch
shacharPash marked this conversation as resolved.
Show resolved Hide resolved

As of RediSearch 2.8.4, advanced GEO querying with GEOSHAPE fields is supported.
shacharPash marked this conversation as resolved.
Show resolved Hide resolved

Any object/library producing a
shacharPash marked this conversation as resolved.
Show resolved Hide resolved
[well-known text (WKT)](https://en.wikipedia.org/wiki/Well-known_text_representation_of_geometry) in `string` format can be used.

shacharPash marked this conversation as resolved.
Show resolved Hide resolved
In this example, we'll demonstrate how to use GeoShape fields in RediSearch with [NetTopologySuite](https://github.com/NetTopologySuite/NetTopologySuite) library.
shacharPash marked this conversation as resolved.
Show resolved Hide resolved

## 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()).Dialect(3));
```

The search result from redis is:
shacharPash marked this conversation as resolved.
Show resolved Hide resolved

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

we can use the reader to get the polygon object:
shacharPash marked this conversation as resolved.
Show resolved Hide resolved

```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()).Dialect(3)); // DIALECT 3 is required for this query

```

The search result from redis is:
shacharPash marked this conversation as resolved.
Show resolved Hide resolved

```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))"
```

### Point type
Copy link
Contributor

Choose a reason for hiding this comment

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

Searching with Co-ordinates


```csharp
Point point = factory.CreatePoint(new Coordinate(10, 10));
db.HashSet("point", "geom", point.ToString());
shacharPash marked this conversation as resolved.
Show resolved Hide resolved

res = ft.Search(index, new Query("@geom:[within $poly]").AddParam("poly", within.ToString()).Dialect(3));

```

The search result from redis is:

```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