Skip to content

Commit b178f7a

Browse files
shacharPashchayim
andauthored
Support GEOSHAPE field type in RediSearch (#191)
* Initial support for OSS Cluster API (#170) * delete mock * coverage * fix test * indent * change to var - check * cluster test * add opthin to connect cluster with dotnet test * use key in topk tests * get env vars inside RedisFixture * skip if redis * add skip where needed * Execute broadcast * delete cluster tests * RedisFixture fix * add to contributing * run cluster on CI * wip * fix / * -d * delete restore * return restore * add -RC3 * add RC3 to docker-compose * try define both .net 6 and 7 * Skip if cluster where needed * add names * skip configOnTimeout if cluster * try to fix win tests * tests names +fix win version * fix versions * versions * win verer * wording * change to OSSCluster * isOSSCluster * update skip reason to OSS cluster * general dispose * general dispose for the rest of the disposes --------- Co-authored-by: Chayim <chayim@users.noreply.github.com> * add GeoShapeField * Update SkipIfRedisAttribute.cs * delete line * AddGeoShapeField * add test * upgrade SE.Redis * fix test * add async test * add tests + Skips * skip id less than 7.2.1 * skip if cluster * add example * chayim review * chayims review 2 --------- Co-authored-by: Chayim <chayim@users.noreply.github.com>
1 parent 04e61da commit b178f7a

File tree

8 files changed

+421
-5
lines changed

8 files changed

+421
-5
lines changed

Examples/GeoShapeQueryExample.md

+128
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
# GeoShape Fields Usage In RediSearch
2+
3+
NRedisStack now supports GEOSHAPE field querying.
4+
5+
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.
6+
7+
Using GeoShape fields in searches with the [NetTopologySuite](https://github.com/NetTopologySuite/NetTopologySuite) library.
8+
9+
## Example
10+
11+
### Modules Needed
12+
13+
```c#
14+
using StackExchange.Redis;
15+
using NRedisStack.RedisStackCommands;
16+
using NRedisStack.Search;
17+
using NetTopologySuite.Geometries;
18+
using NetTopologySuite.IO;
19+
```
20+
21+
### Setup
22+
23+
```csharp
24+
// Connect to the Redis server:
25+
var redis = ConnectionMultiplexer.Connect("localhost");
26+
var db = redis.GetDatabase();
27+
// Get a reference to the database and for search commands:
28+
var ft = db.FT();
29+
30+
// Create WTKReader and GeometryFactory objects:
31+
WKTReader reader = new WKTReader();
32+
GeometryFactory factory = new GeometryFactory();
33+
34+
```
35+
36+
### Create the index
37+
38+
```csharp
39+
ft.Create(index, new Schema().AddGeoShapeField("geom", GeoShapeField.CoordinateSystem.FLAT));
40+
```
41+
42+
### Prepare the data
43+
44+
```csharp
45+
Polygon small = factory.CreatePolygon(new Coordinate[]{new Coordinate(1, 1),
46+
new Coordinate(1, 100), new Coordinate(100, 100), new Coordinate(100, 1), new Coordinate(1, 1)});
47+
db.HashSet("small", "geom", small.ToString());
48+
49+
Polygon large = factory.CreatePolygon(new Coordinate[]{new Coordinate(1, 1),
50+
new Coordinate(1, 200), new Coordinate(200, 200), new Coordinate(200, 1), new Coordinate(1, 1)});
51+
db.HashSet("large", "geom", large.ToString());
52+
```
53+
54+
## Polygon type
55+
56+
### Querying within condition
57+
58+
```csharp
59+
Polygon within = factory.CreatePolygon(new Coordinate[]{new Coordinate(0, 0),
60+
new Coordinate(0, 150), new Coordinate(150, 150), new Coordinate(150, 0), new Coordinate(0, 0)});
61+
62+
SearchResult res = ft.Search(index, new Query("@geom:[within $poly]")
63+
.AddParam("poly", within.ToString()) // Note serializing the argument to string
64+
.Dialect(3)); // DIALECT 3 is required for this query
65+
```
66+
67+
The search result from redis is:
68+
69+
```bash
70+
1) (integer) 1
71+
2) "small"
72+
3) 1) "geom"
73+
2) "POLYGON ((1 1, 1 100, 100 100, 100 1, 1 1))"
74+
```
75+
76+
Use the reader to get the polygon:
77+
78+
```csharp
79+
reader.Read(res.Documents[0]["geom"].ToString());
80+
```
81+
82+
### Querying contains condition
83+
84+
```csharp
85+
Polygon contains = factory.CreatePolygon(new Coordinate[]{new Coordinate(2, 2),
86+
new Coordinate(2, 50), new Coordinate(50, 50), new Coordinate(50, 2), new Coordinate(2, 2)});
87+
88+
res = ft.Search(index, new Query("@geom:[contains $poly]")
89+
.AddParam("poly", contains.ToString()) // Note serializing the argument to string
90+
.Dialect(3)); // DIALECT 3 is required for this query
91+
92+
```
93+
94+
Our search result:
95+
96+
```bash
97+
1) (integer) 2
98+
2) "small"
99+
3) 1) "geom"
100+
2) "POLYGON ((1 1, 1 100, 100 100, 100 1, 1 1))"
101+
4) "large"
102+
5) 1) "geom"
103+
2) "POLYGON ((1 1, 1 200, 200 200, 200 1, 1 1))"
104+
```
105+
106+
### Searching with Coordinates
107+
108+
```csharp
109+
Point point = factory.CreatePoint(new Coordinate(10, 10));
110+
db.HashSet("point", "geom", point.ToString());
111+
112+
res = ft.Search(index, new Query("@geom:[within $poly]")
113+
.AddParam("poly", within.ToString()) // Note serializing the argument to string
114+
.Dialect(3)); // DIALECT 3 is required for this query
115+
116+
```
117+
118+
Our search result:
119+
120+
```bash
121+
1) (integer) 2
122+
2) "small"
123+
3) 1) "geom"
124+
2) "POLYGON ((1 1, 1 100, 100 100, 100 1, 1 1))"
125+
4) "point"
126+
5) 1) "geom"
127+
2) "POINT (10 10)"
128+
```

src/NRedisStack/NRedisStack.csproj

+2-1
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,9 @@
1515
</PropertyGroup>
1616

1717
<ItemGroup>
18+
<PackageReference Include="NetTopologySuite" Version="2.5.0" />
1819
<PackageReference Include="System.Text.Json" Version="7.0.2" Condition="'$(TargetFramework)' == 'netstandard2.0'" />
19-
<PackageReference Include="StackExchange.Redis" Version="2.6.96" />
20+
<PackageReference Include="StackExchange.Redis" Version="2.6.122" />
2021
<None Include="..\..\README.md" Pack="true" PackagePath="\" />
2122
</ItemGroup>
2223

src/NRedisStack/Search/Query.cs

+1
Original file line numberDiff line numberDiff line change
@@ -617,6 +617,7 @@ public Query SetSortBy(string field, bool? ascending = null)
617617
/// Parameters can be referenced in the query string by a $ , followed by the parameter name,
618618
/// e.g., $user , and each such reference in the search query to a parameter name is substituted
619619
/// by the corresponding parameter value.
620+
/// Note: when calling this function with an externally supplied parameter, value should be a string.
620621
/// </summary>
621622
/// <param name="name"></param>
622623
/// <param name="value"> can be String, long or float</param>

src/NRedisStack/Search/Schema.cs

+58
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using NRedisStack.Search.Literals;
2+
using static NRedisStack.Search.Schema.GeoShapeField;
23
using static NRedisStack.Search.Schema.VectorField;
34

45
namespace NRedisStack.Search
@@ -13,6 +14,7 @@ public enum FieldType
1314
{
1415
Text,
1516
Geo,
17+
GeoShape,
1618
Numeric,
1719
Tag,
1820
Vector
@@ -38,6 +40,7 @@ internal void AddSchemaArgs(List<object> args)
3840
{
3941
FieldType.Text => "TEXT",
4042
FieldType.Geo => "GEO",
43+
FieldType.GeoShape => "GEOSHAPE",
4144
FieldType.Numeric => "NUMERIC",
4245
FieldType.Tag => "TAG",
4346
FieldType.Vector => "VECTOR",
@@ -178,6 +181,37 @@ internal override void AddFieldTypeArgs(List<object> args)
178181

179182
}
180183

184+
public class GeoShapeField : Field
185+
{
186+
public enum CoordinateSystem
187+
{
188+
/// <summary>
189+
/// For cartesian (X,Y).
190+
/// </summary>
191+
FLAT,
192+
193+
/// <summary>
194+
/// For geographic (lon, lat).
195+
/// </summary>
196+
SPHERICAL
197+
}
198+
private CoordinateSystem system { get; }
199+
200+
internal GeoShapeField(FieldName name, CoordinateSystem system)
201+
: base(name, FieldType.GeoShape)
202+
{
203+
this.system = system;
204+
}
205+
206+
internal GeoShapeField(string name, CoordinateSystem system)
207+
: this(FieldName.Of(name), system) { }
208+
209+
internal override void AddFieldTypeArgs(List<object> args)
210+
{
211+
args.Add(system.ToString());
212+
}
213+
}
214+
181215
public class NumericField : Field
182216
{
183217
public bool Sortable { get; }
@@ -288,6 +322,30 @@ public Schema AddTextField(FieldName name, double weight = 1.0, bool sortable =
288322
return this;
289323
}
290324

325+
/// <summary>
326+
/// Add a GeoShape field to the schema.
327+
/// </summary>
328+
/// <param name="name">The field's name.</param>
329+
/// <param name="system">The coordinate system to use.</param>
330+
/// <returns>The <see cref="Schema"/> object.</returns>
331+
public Schema AddGeoShapeField(string name, CoordinateSystem system)
332+
{
333+
Fields.Add(new GeoShapeField(name, system));
334+
return this;
335+
}
336+
337+
/// <summary>
338+
/// Add a GeoShape field to the schema.
339+
/// </summary>
340+
/// <param name="name">The field's name.</param>
341+
/// <param name="system">The coordinate system to use.</param>
342+
/// <returns>The <see cref="Schema"/> object.</returns>
343+
public Schema AddGeoShapeField(FieldName name, CoordinateSystem system)
344+
{
345+
Fields.Add(new GeoShapeField(name, system));
346+
return this;
347+
}
348+
291349
/// <summary>
292350
/// Add a Geo field to the schema.
293351
/// </summary>

tests/Doc/Doc.csproj

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
1717
<PrivateAssets>all</PrivateAssets>
1818
</PackageReference>
19-
<PackageReference Include="StackExchange.Redis" Version="2.6.104" />
19+
<PackageReference Include="StackExchange.Redis" Version="2.6.122" />
2020
</ItemGroup>
2121
<ItemGroup>
2222
<ProjectReference Include="..\..\src\NRedisStack\NRedisStack.csproj" />

tests/NRedisStack.Tests/Examples/ExampleTests.cs

+2
Original file line numberDiff line numberDiff line change
@@ -1373,6 +1373,8 @@ public void AdvancedQueryOperationsTest()
13731373
Assert.Equal(expectedResSet, resSet);
13741374
}
13751375

1376+
// GeoShape Example Test is in SearchTests.cs, The test name is: GeoShapeFilterFlat.
1377+
13761378
private static void SortAndCompare(List<string> expectedList, List<string> res)
13771379
{
13781380
res.Sort();

tests/NRedisStack.Tests/NRedisStack.Tests.csproj

+3-2
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,12 @@
2121
</PackageReference>
2222
<PackageReference Include="dotenv.net" Version="3.1.2" />
2323
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
24+
<PackageReference Include="NetTopologySuite" Version="2.5.0" />
2425
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
25-
<PackageReference Include="StackExchange.Redis" Version="2.6.96" />
26+
<PackageReference Include="StackExchange.Redis" Version="2.6.122" />
2627
<PackageReference Include="xunit" Version="2.4.1" />
2728
<PackageReference Include="xunit.assert" Version="2.4.1" />
28-
<PackageReference Include="BouncyCastle.Cryptography" Version="2.2.0"/>
29+
<PackageReference Include="BouncyCastle.Cryptography" Version="2.2.0" />
2930
</ItemGroup>
3031

3132
<ItemGroup>

0 commit comments

Comments
 (0)