Search code examples
odataazure-cognitive-searchazure-search-.net-sdk

Azure Search - Query Collection of GeographyPoint


I am trying to query a collection GeographyPoint within a given range from my index. I am using Azure Search.

My index contains a list of GeographyPoint

public class Parent
{
     public List<Child> Coordinates {get; set;}
}


public class Child
{ 
     public GeographyPoint Coordinates { get; set; }
}

I know if there was a single property named location on the index you could query it with something like:

$filter=geo.distance(location, geography'POINT(-82.51571 31.89063)') le 0.1524

but how do you query a collection? I've tried using the any/all filters like:

$filter=geo.distance(IndexedCompany/Offices/any(c: c/PhysicalAddress/Coordinates), geography'POINT(-82.51571 31.89063)') le 0.1524

but I get the error: "Invalid expression: The Any/All query expression must evaluate to a single boolean value.\r\nParameter name: $filter"

Any help on how to query a collection of GeographyPoint would be much appreciated.


Solution

  • The issue is in how you've constructed your filter. Let's break it down using types, since that's the easiest way to see where things went wrong.

    $filter=geo.distance(IndexedCompany/Offices/any(c: c/PhysicalAddress/Coordinates), geography'POINT(-82.51571 31.89063)') le 0.1524

    Starting from the outside in, the top-most expression in a filter must be Boolean, and that checks out here because you're comparing geo.distance (which returns Edm.Double) with a literal distance using le. So that part is fine.

    Next, let's look at geo.distance. It takes two points of type Edm.GeographyPoint and returns an Edm.Double. The second parameter is a literal point, so that's fine, but the first parameter is not. Let's look at that next.

    The first parameter to geo.distance here is the expression IndexedCompany/Offices/any(c: c/PhysicalAddress/Coordinates). Although it's really an operator, you can think of any as a kind of function that returns a Boolean. That would be a problem, because geo.distance is expecting a point, not a Boolean. That's our first clue to what's wrong here, but it doesn't actually explain the error message you're seeing. Let's dig further to understand that.

    Assume for the moment that the expression IndexedCompany/Offices/any(c: c/PhysicalAddress/Coordinates) was your entire filter. You'd still get the same error message: "Invalid expression: The Any/All query expression must evaluate to a single boolean value." To see why, consider what any and all expect in terms of types. Each one iterates over a collection of some type, applying a predicate in the form of a lambda expression to each element. That lambda expression in this case is c/PhysicalAddress/Coordinates, which (I'm assuming) is an Edm.GeographyPoint, not a Boolean.

    Taking a step back, it helps to think about the cardinalities involved here. Each document contains a company, which has many offices, which each has a geo-point location. I'm assuming the goal is to match the whole document in case any of those co-ordinates match the filter. In that case, you really just need to re-order the parts of your query so that the types and cardinalities line up:

    $filter=IndexedCompany/Offices/any(office: geo.distance(office/PhysicalAddress/Coordinates, geography'POINT(-82.51571 31.89063)') le 0.1524)

    The filter is still a Boolean expression because that's the type of any. The lambda passed to any takes an Office, applies geo.distance to its geo-location, and compares the distance to the desired value. Now everything type checks and the iteration is happening the right place.

    Example: search=*

    "value": [
        {
            "@search.score": 1,
            "id": "myid1",
            "pts": [
                {
                    "type": "Point",
                    "coordinates": [ 0, 0 ],
                    "crs": { "type": "name", "properties": { "name": "EPSG:4326" } }
                },
                {
                    "type": "Point",
                    "coordinates": [ 0, 1 ],
                    "crs": { "type": "name", "properties": { "name": "EPSG:4326" } }
                }
            ]
        },
        {
            "@search.score": 1,
            "id": "myid2",
            "pts": [
                {
                    "type": "Point",
                    "coordinates": [ 0, 10 ],
                    "crs": { "type": "name", "properties": { "name": "EPSG:4326" } }
                },
                {
                    "type": "Point",
                    "coordinates": [ 0, 11 ],
                    "crs": { "type": "name", "properties": { "name": "EPSG:4326" } }
                }
            ]
        }
    ]
    

    Example $filter=pts/any(pt: geo.distance(pt, geography'POINT(0 2)') le 111.3192394008)

    "value": [
        {
            "@search.score": 1,
            "id": "myid1",
            "pts": [
                {
                    "type": "Point",
                    "coordinates": [ 0, 0 ],
                    "crs": { "type": "name", "properties": { "name": "EPSG:4326" } }
                },
                {
                    "type": "Point",
                    "coordinates": [ 0, 1 ],
                    "crs": { "type": "name", "properties": { "name": "EPSG:4326" } }
                }
            ]
        }
    ]