Skip to content

Commit

Permalink
Improvements
Browse files Browse the repository at this point in the history
- new converters
- serialization improved
- better filter abstraction
  • Loading branch information
emmanuelmathot committed Nov 2, 2023
1 parent c1daa11 commit c82b8b4
Show file tree
Hide file tree
Showing 32 changed files with 733 additions and 121 deletions.
23 changes: 8 additions & 15 deletions src/Stac.Api.Clients/Converters/FilterSearchBodyConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,26 +34,19 @@ public FilterSearchBody ReadJObject(JObject jo, Type objectType, object existing
// Read base object SearchBody
SearchBody searchBody = serializer.Deserialize<SearchBody>(jo.CreateReader());

Api.Converters.CQL2FilterConverter.FilterLang? filter_lang = null;
// If there is no filter-lang nor filter, return the base object
if (!jo.ContainsKey("filter-lang") && !jo.ContainsKey("filter"))
return new FilterSearchBody(searchBody);

CQL2FilterConverter cql2FilterConverter = new CQL2FilterConverter();
if (jo.ContainsKey("filter-lang"))
{
filter_lang = StacAccessorsHelpers.LazyEnumParse(typeof(Api.Converters.CQL2FilterConverter.FilterLang), jo["filter-lang"].ToString()) as Api.Converters.CQL2FilterConverter.FilterLang?;
if (filter_lang != null)
{
cql2FilterConverter = new CQL2FilterConverter(filter_lang.Value);
}
else
{
cql2FilterConverter = new CQL2FilterConverter();
}
}
CQL2Expression cQL2Expression = cql2FilterConverter.ReadJObject(jo, typeof(CQL2Expression), existingValue, serializer);

// Read additional properties
var additionalProperties = jo.Properties().Where(p => p.Name != "filter" && p.Name != "filter-lang" && p.Name != "filter_crs").ToDictionary(p => p.Name, p => p.Value);

return new FilterSearchBody(searchBody){
FilterLang = filter_lang ?? Api.Converters.CQL2FilterConverter.FilterLang.Cql2Text,
Filter = cql2FilterConverter.ReadJObject(jo["filter"] as JObject, typeof(CQL2Expression), existingValue, serializer),
FilterLang = cQL2Expression.FilterLang,
Filter = cQL2Expression.Expression,
FilterCrs = jo["filter_crs"]?.ToObject<Uri>(),
AdditionalProperties = additionalProperties.Select(p => new KeyValuePair<string, object>(p.Key, p.Value.ToObject<object>())).ToDictionary(p => p.Key, p => p.Value)
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@

using Stac;
using Stac.Common;
using Stac.Api.Converters;
using Stac.Api.Models;
using Stac.Api.Interfaces;

Expand All @@ -21,7 +20,6 @@

namespace Stac.Api.Clients.Extensions.Filter
{

using System = global::System;

[System.CodeDom.Compiler.GeneratedCode("NSwag", "13.16.1.0 (NJsonSchema v10.7.2.0 (Newtonsoft.Json v13.0.0.0))")]
Expand Down Expand Up @@ -240,7 +238,7 @@ public virtual async System.Threading.Tasks.Task<StacQueryables> GetQueryablesFo
/// <br/>is 'http://www.opengis.net/def/crs/OGC/1.3/CRS84'.</param>
/// <returns>A feature collection.</returns>
/// <exception cref="StacApiException">A server side error occurred.</exception>
public virtual System.Threading.Tasks.Task<StacFeatureCollection> GetItemSearchAsync(Models.Cql2.CQL2Expression filter, CQL2FilterConverter.FilterLang? filter_lang, System.Uri filter_crs)
public virtual System.Threading.Tasks.Task<StacFeatureCollection> GetItemSearchAsync(Api.Interfaces.IFilterExpression filter, Api.Models.Cql2.FilterLang? filter_lang, System.Uri filter_crs)
{
return GetItemSearchAsync(filter, filter_lang, filter_crs, System.Threading.CancellationToken.None);
}
Expand All @@ -261,7 +259,7 @@ public virtual System.Threading.Tasks.Task<StacFeatureCollection> GetItemSearchA
/// <br/>is 'http://www.opengis.net/def/crs/OGC/1.3/CRS84'.</param>
/// <returns>A feature collection.</returns>
/// <exception cref="StacApiException">A server side error occurred.</exception>
public virtual async System.Threading.Tasks.Task<StacFeatureCollection> GetItemSearchAsync(Models.Cql2.CQL2Expression filter, CQL2FilterConverter.FilterLang? filter_lang, System.Uri filter_crs, System.Threading.CancellationToken cancellationToken)
public virtual async System.Threading.Tasks.Task<StacFeatureCollection> GetItemSearchAsync(Api.Interfaces.IFilterExpression filter, Api.Models.Cql2.FilterLang? filter_lang, System.Uri filter_crs, System.Threading.CancellationToken cancellationToken)
{
if (filter == null)
throw new System.ArgumentNullException("filter");
Expand Down Expand Up @@ -573,11 +571,11 @@ private string ConvertToString(object value, System.Globalization.CultureInfo cu
public partial class FilterSearchBody : Stac.Api.Clients.ItemSearch.SearchBody
{
[Newtonsoft.Json.JsonProperty("filter", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
public Models.Cql2.CQL2Expression Filter { get; set; }
public Api.Interfaces.IFilterExpression Filter { get; set; }

[Newtonsoft.Json.JsonProperty("filter-lang", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
[Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))]
public CQL2FilterConverter.FilterLang FilterLang { get; set; }
public Api.Models.Cql2.FilterLang FilterLang { get; set; }

[Newtonsoft.Json.JsonProperty("filter-crs", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
public System.Uri FilterCrs { get; set; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@
using Newtonsoft.Json;
using Stac.Api.Clients.Converters;
using Stac.Api.Clients.ItemSearch;
using Stac.Api.Interfaces;

namespace Stac.Api.Clients.Extensions.Filter
{
[JsonConverter(typeof(FilterSearchBodyConverter))]
public partial class FilterSearchBody
public partial class FilterSearchBody : ISearchExpression
{
public FilterSearchBody() { }

Expand Down
8 changes: 4 additions & 4 deletions src/Stac.Api.CodeGen/appsettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -469,7 +469,7 @@
"Ids",
"StacFeatureCollectionType",
"BboxFilter",
"FilterLang",
"Api.Models.Cql2.FilterLang",
"StacFeatureCollection",
"Stac.Api.Clients.Features.Error",
"Stac.Api.Clients.ItemSearch.SearchBody"
Expand All @@ -480,12 +480,12 @@
"JsonSchema": "StacQueryables",
"Response": "StacQueryables",
"SearchBody": "FilterSearchBody",
"Filter": "Models.Cql2.CQL2Filter",
"FilterLang": "Api.Converters.CQL2FilterConverter.FilterLang",
"Filter": "Api.Interfaces.IFilterExpression",
"FilterLang": "Api.Models.Cql2.FilterLang",
"ItemCollection": "StacFeatureCollection",
"Error": "Stac.Api.Clients.Features.Error",
"Anonymous": "Stac.Api.Clients.ItemSearch.SearchBody",
"FilterCql2Json2": "Models.Cql2.CQL2Filter"
"FilterCql2Json2": "Api.Interfaces.IFilterExpression"
},
// "Url": "https://raw.githubusercontent.com/stac-api-extensions/filter/main/openapi.yaml",
"File": "Schemas/extensions/filter/openapi-1.0.0-rc.2-mod.yaml"
Expand Down
5 changes: 2 additions & 3 deletions src/Stac.Api.Tests/AppTests/Extensions/FilterApiTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,10 @@ public async Task GetItemSearchAsync(string catalogName)
spatialFilter.Op = SpatialPredicateOp.S_intersects;
spatialFilter.Args.Add(new PropertyRef("geometry"));
spatialFilter.Args.Add(new GeometryLiteral(polygon));
CQL2Expression filter = new CQL2Expression(spatialFilter);

StacFeatureCollection result = await filterClient.GetItemSearchAsync(
filter,
Converters.CQL2FilterConverter.FilterLang.Cql2Json,
spatialFilter,
FilterLang.Cql2Json,
null
);

Expand Down
5 changes: 2 additions & 3 deletions src/Stac.Api.Tests/AppTests/Extensions/SortApiTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,10 @@ public async Task PostItemSearchAsync(string catalogName)
spatialFilter.Op = SpatialPredicateOp.S_intersects;
spatialFilter.Args.Add(new PropertyRef("geometry"));
spatialFilter.Args.Add(new GeometryLiteral(polygon));
CQL2Expression filter = new CQL2Expression(spatialFilter);

FilterSearchBody body = new FilterSearchBody();
body.Filter = filter;
body.FilterLang = Api.Converters.CQL2FilterConverter.FilterLang.Cql2Json;
body.Filter = spatialFilter;
body.FilterLang = Api.Models.Cql2.FilterLang.Cql2Json;
body.AdditionalProperties.Add("sortby", new Sortby(){
new SortByItem(){
Field = "id",
Expand Down
15 changes: 15 additions & 0 deletions src/Stac.Api.Tests/CQL2Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -310,5 +310,20 @@ public async Task Example12Test()
var json2 = JsonConvert.SerializeObject(new CQL2Expression(be), _settings);
JsonAssert.AreEqual(json, json2);
}

[Fact]
public async Task Example13Test()
{
var json = GetJson("CQL2", "Example13");
JObject jObject = JObject.Parse(json);
var be = JsonConvert.DeserializeObject<BooleanExpression>(jObject["filter"].ToString(), _settings);
Assert.IsType<SpatialPredicate>(be);
Assert.NotNull(be.AsSpatialPredicate());
Assert.Equal(SpatialPredicateOp.S_intersects, be.AsSpatialPredicate().Op);
Assert.Equal(2, be.AsSpatialPredicate().Args.Count);

var json2 = JsonConvert.SerializeObject(new CQL2Expression(be), _settings);
JsonAssert.AreEqual(json, json2);
}
}
}
66 changes: 66 additions & 0 deletions src/Stac.Api.Tests/Resources/CQL2/CQL2Tests_Example13.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
{
"filter-lang": "cql2-json",
"filter": {
"op": "s_intersects",
"args": [
{
"property": "geometry"
},
{
"type": "Polygon",
"coordinates": [
[
[
115.6433814,
57.6452553
],
[
115.6540305,
57.6649078
],
[
115.7314869,
57.809336
],
[
115.8109064,
57.9534193
],
[
115.8872017,
58.0983519
],
[
115.967219,
58.2424627
],
[
116.0459009,
58.3869795
],
[
116.1261709,
58.5312986
],
[
116.1846498,
58.6346043
],
[
117.1681312,
58.6405464
],
[
117.1635452,
57.6543437
],
[
115.6433814,
57.6452553
]
]
]
}
]
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public override object GetActionParameters(string actionName)
}
else if (actionName == "PostItemSearch")
{
return new { intersebodycts = default(SearchBody) };
return new { body = default(SearchBody) };
}

return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ public interface IFilterController

/// <returns>A feature collection.</returns>

System.Threading.Tasks.Task<Microsoft.AspNetCore.Mvc.ActionResult<StacFeatureCollection>> GetItemSearchAsync(Models.Cql2.CQL2Expression filter, Api.Converters.CQL2FilterConverter.FilterLang? filter_lang, System.Uri filter_crs, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken));
System.Threading.Tasks.Task<Microsoft.AspNetCore.Mvc.ActionResult<StacFeatureCollection>> GetItemSearchAsync(Api.Interfaces.IFilterExpression filter, Api.Models.Cql2.FilterLang? filter_lang, System.Uri filter_crs, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken));

/// <summary>
/// Search STAC items with full-featured filtering.
Expand Down Expand Up @@ -128,7 +128,7 @@ public FilterController(IFilterController implementation)
/// <br/>is 'http://www.opengis.net/def/crs/OGC/1.3/CRS84'.</param>
/// <returns>A feature collection.</returns>
[Microsoft.AspNetCore.Mvc.HttpGet, Microsoft.AspNetCore.Mvc.Route("search")]
public System.Threading.Tasks.Task<Microsoft.AspNetCore.Mvc.ActionResult<StacFeatureCollection>> GetItemSearch([Microsoft.AspNetCore.Mvc.FromQuery] [Microsoft.AspNetCore.Mvc.ModelBinding.BindRequired] Models.Cql2.CQL2Expression filter, [Microsoft.AspNetCore.Mvc.FromQuery(Name = "filter-lang")] Api.Converters.CQL2FilterConverter.FilterLang? filter_lang, [Microsoft.AspNetCore.Mvc.FromQuery(Name = "filter-crs")] System.Uri filter_crs, System.Threading.CancellationToken cancellationToken)
public System.Threading.Tasks.Task<Microsoft.AspNetCore.Mvc.ActionResult<StacFeatureCollection>> GetItemSearch([Microsoft.AspNetCore.Mvc.FromQuery] [Microsoft.AspNetCore.Mvc.ModelBinding.BindRequired] Api.Interfaces.IFilterExpression filter, [Microsoft.AspNetCore.Mvc.FromQuery(Name = "filter-lang")] Api.Models.Cql2.FilterLang? filter_lang, [Microsoft.AspNetCore.Mvc.FromQuery(Name = "filter-crs")] System.Uri filter_crs, System.Threading.CancellationToken cancellationToken)
{

return _implementation.GetItemSearchAsync(filter, filter_lang, filter_crs, cancellationToken);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,14 @@ public DefaultFilterController(IStacApiEndpointManager stacApiEndpointManager,

}

public async Task<ActionResult<StacFeatureCollection>> GetItemSearchAsync(CQL2Expression filter, Api.Converters.CQL2FilterConverter.FilterLang? filter_lang, Uri filter_crs, CancellationToken cancellationToken = default)
public async Task<ActionResult<StacFeatureCollection>> GetItemSearchAsync(IFilterExpression filter, Api.Models.Cql2.FilterLang? filter_lang, Uri filter_crs, CancellationToken cancellationToken = default)
{
// Only Support BooleanExpression
if (!(filter is BooleanExpression))
throw new NotImplementedException();

BooleanExpression be = filter as BooleanExpression;

// Create the context
IStacApiContext stacApiContext = _stacApiContextFactory.Create();

Expand All @@ -55,13 +61,13 @@ public async Task<ActionResult<StacFeatureCollection>> GetItemSearchAsync(CQL2Ex
var items = await itemsProvider.GetItemsAsync(stacApiContext, cancellationToken);

// Apply the filter
items = items.Boolean<StacItem>(filter.Expression);
items = items.Boolean<StacItem>(be);

// Log the filter expression
stacApiContext.Properties.SetProperty(DefaultConventions.DebugExpressionTreePropertiesKey, items.AsQueryable().Expression.ToString());

// Save the query parameters in the context
SetQueryParametersInContext(stacApiContext, filter);
SetQueryParametersInContext(stacApiContext, new CQL2Expression(be));

// Apply Context Post Query Filters
items = _stacApiContextFactory.ApplyContextPostQueryFilters<StacItem>(stacApiContext, itemsProvider, items);
Expand Down Expand Up @@ -106,6 +112,12 @@ public async Task<ActionResult<StacFeatureCollection>> PostItemSearchAsync(Filte
return await _itemSearchController.PostItemSearchAsync(body, cancellationToken);
}

// Only Support BooleanExpression
if (!(body.Filter is BooleanExpression))
throw new NotImplementedException();

BooleanExpression be = body.Filter as BooleanExpression;

// Create the context
IStacApiContext stacApiContext = _stacApiContextFactory.Create();

Expand All @@ -118,13 +130,13 @@ public async Task<ActionResult<StacFeatureCollection>> PostItemSearchAsync(Filte
var items = await itemsProvider.GetItemsAsync(stacApiContext, cancellationToken);

// Apply the filter
items = items.Boolean<StacItem>(body.Filter.Expression);
items = items.Boolean<StacItem>(be);

// Log the filter expression
stacApiContext.Properties.SetProperty(DefaultConventions.DebugExpressionTreePropertiesKey, items.AsQueryable().Expression.ToString());

// Save the query parameters in the context
SetQueryParametersInContext(stacApiContext, body.Filter);
SetQueryParametersInContext(stacApiContext, body);

// Apply Context Post Query Filters
items = _stacApiContextFactory.ApplyContextPostQueryFilters<StacItem>(stacApiContext, itemsProvider, items);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,10 @@ public Task BindModelAsync(ModelBindingContext bindingContext)
try
{
// Get filter lang from query string
Api.Converters.CQL2FilterConverter.FilterLang? filter_lang = StacAccessorsHelpers.LazyEnumParse(typeof(Api.Converters.CQL2FilterConverter.FilterLang), bindingContext.HttpContext.Request.Query["filter-lang"].ToString()) as Api.Converters.CQL2FilterConverter.FilterLang?;
var cql2FilterConverter = new CQL2FilterConverter(filter_lang != null ? Enum.Parse<CQL2FilterConverter.FilterLang>(filter_lang.ToString()) : null);
CQL2Expression cql2Filter = cql2FilterConverter.ReadJson(new JsonTextReader(new StringReader(value)), typeof(CQL2Expression), null, new JsonSerializer()) as CQL2Expression;
bindingContext.Result = ModelBindingResult.Success(cql2Filter);
Api.Models.Cql2.FilterLang? filter_lang = StacAccessorsHelpers.LazyEnumParse(typeof(Api.Models.Cql2.FilterLang), bindingContext.HttpContext.Request.Query["filter-lang"].ToString()) as Api.Models.Cql2.FilterLang?;
var cql2FilterConverter = new CQL2FilterConverter();
BooleanExpression be = cql2FilterConverter.CreateFilter(JObject.Parse(value), filter_lang);
bindingContext.Result = ModelBindingResult.Success(be);
}
catch (Exception ex)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using GeoJSON.Net.Geometry;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Stac.Api.Clients.Extensions.Filter;
using Stac.Api.Interfaces;
using Stac.Api.Models.Core;
using Stac.Api.Models.Cql2;

Expand All @@ -15,7 +16,7 @@ internal class FilterExtensionModelBinderProvider : IModelBinderProvider
public IModelBinder? GetBinder(ModelBinderProviderContext context)
{
// CQL2 BooleanExpression model binding
if (context.Metadata.ModelType == typeof(CQL2Expression))
if (context.Metadata.ModelType == typeof(IFilterExpression))
{
return new CQL2FilterModelBinder();
}
Expand Down
Loading

0 comments on commit c82b8b4

Please sign in to comment.