简体   繁体   中英

Do geospatial queries work on arrays? ( $geoWithin, $geoIntersects )

I have documents as the one below stored in MongoDB 3.0.5

{
    "_id" : ObjectId("55f3a6ae0907233b5e9e7da0"),
    "user" : {
        "id" : "2d5b093ec8a3",
        "regions" : [ 
            {
                "id" : NumberLong(1442365443852),
                "name" : "penta",
                "type" : "Polygon",
                "center" : {
                    "lat" : -12.1254159880008100,
                    "lng" : -77.0316830277442930
                },
                "zoom" : 17.0000000000000000,
                "coordinates" : [ 
                    [ 
                        -77.0322804898023610, 
                        -12.1271067552781560
                    ], 
                    [ 
                        -77.0336792618036270, 
                        -12.1255133434450870
                    ], 
                    [ 
                        -77.0326449349522590, 
                        -12.1239143495252150
                    ], 
                    [ 
                        -77.0300991833209990, 
                        -12.1238251884504540
                    ], 
                    [ 
                        -77.0299865305423740, 
                        -12.1262000752832540
                    ], 
                    [ 
                        -77.0322804898023610, 
                        -12.1271067552781560
                    ]
                ]
            }, 
            {
                "id" : NumberLong(1442366496200),
                "name" : "triangle",
                "type" : "Polygon",
                "center" : {
                    "lat" : -12.1254749913046230,
                    "lng" : -77.0316598936915400
                },
                "zoom" : 17.0000000000000000,
                "coordinates" : [ 
                    [ 
                        -77.0313568040728570, 
                        -12.1266573492018090
                    ], 
                    [ 
                        -77.0325788855552670, 
                        -12.1246968022373030
                    ], 
                    [ 
                        -77.0300653204321860, 
                        -12.1246233756874440
                    ], 
                    [ 
                        -77.0313568040728570, 
                        -12.1266573492018090
                    ]
                ]
            }
        ]
    }
}

which represents an array of google map regions stored as polygons. I am trying to query them with several alternatives but none seems to work so I am wondering if geospatial MongoDB queries works on arrays.

My Java code is: DBCursor docs = getCollection().find(search);

All the following 6 'search' queries return no results when geometrically I would expect a match:

  • { "user.regions" : { "$geoIntersects" : { "$geometry" : { "type" : "Polygon" , "coordinates" : [ [ [ -77.02877718955278 , -12.123750122669545] , [ -77.03457042574883 , -12.123750122669545] , [ -77.03457042574883 , -12.12736341792724] , [ -77.02877718955278 , -12.12736341792724] , [ -77.02877718955278 , -12.123750122669545]]]}}}}

  • { "user.regions.0" : { "$geoIntersects" : { "$geometry" : { "type" : "Polygon" , "coordinates" : [ [ [ -77.02877718955278 , -12.123750122669545] , [ -77.03457042574883 , -12.123750122669545] , [ -77.03457042574883 , -12.12736341792724] , [ -77.02877718955278 , -12.12736341792724] , [ -77.02877718955278 , -12.123750122669545]]]}}}}

  • { "user.regions.0.center" : { "$geoIntersects" : { "$geometry" : { "type" : "Polygon" , "coordinates" : [ [ [ -77.02877718955278 , -12.123750122669545] , [ -77.03457042574883 , -12.123750122669545] , [ -77.03457042574883 , -12.12736341792724] , [ -77.02877718955278 , -12.12736341792724] , [ -77.02877718955278 , -12.123750122669545]]]}}}}

  • { "user.regions" : { "$geoWithin" : { "$geometry" : { "type" : "Polygon" , "coordinates" : [ [ [ -77.02877718955278 , -12.123750122669545] , [ -77.03457042574883 , -12.123750122669545] , [ -77.03457042574883 , -12.12736341792724] , [ -77.02877718955278 , -12.12736341792724] , [ -77.02877718955278 , -12.123750122669545]]]}}}}

  • { "user.regions.0" : { "$geoWithin" : { "$geometry" : { "type" : "Polygon" , "coordinates" : [ [ [ -77.02877718955278 , -12.123750122669545] , [ -77.03457042574883 , -12.123750122669545] , [ -77.03457042574883 , -12.12736341792724] , [ -77.02877718955278 , -12.12736341792724] , [ -77.02877718955278 , -12.123750122669545]]]}}}}

  • { "user.regions.0.center" : { "$geoWithin" : { "$geometry" : { "type" : "Polygon" , "coordinates" : [ [ [ -77.02877718955278 , -12.123750122669545] , [ -77.03457042574883 , -12.123750122669545] , [ -77.03457042574883 , -12.12736341792724] , [ -77.02877718955278 , -12.12736341792724] , [ -77.02877718955278 , -12.123750122669545]]]}}}}

I think I am respecting the long/lat order in MongoDB, I repeat the closing point in my polygons, I have added type="Polygon" and in the case of the center point it is under lat/lng params. But no results.

I have not created any indices and I think I am matching the syntax for both params geoWithin ( http://docs.mongodb.org/manual/reference/operator/query/geoWithin/ ) and geoIntercepts ( http://docs.mongodb.org/manual/reference/operator/query/geoIntersects/ )

{
   <location field>: {
      $geoWithin: {
         $geometry: {
            type: <"Polygon" or "MultiPolygon"> ,
            coordinates: [ <coordinates> ]
         }
      }
   }
}

{
  <location field>: {
     $geoIntersects: {
        $geometry: {
           type: "<GeoJSON object type>" ,
           coordinates: [ <coordinates> ]
        }
     }
  }
}

Regarding 'location field' I followed instructions for arrays as said here http://docs.mongodb.org/manual/tutorial/query-documents/

But nothing makes the queries get any result. I googled for same case and I just could find this unsolved question from 2013: How do I perform a find using $geoIntersects / 2dsphere in an array subfield?

So, unless I am doing a common mistake I am wondering if is possible to make geospatial queries to array params?

Any suggestions?

Thanks

This is one of those both yes and no questions to answer, as yes an array is supported for matching results in but it is also probably not what you really want considering the restrictions on how the matching is done.

The notable change you need here is that the objects themselves are not defined in a way that MongoDB is going to recognise them as you currently have them formed. There are two index and general lookup forms being either with legacy coordinate pairs ( which is just an x,y point ) or as GeoJSON with supported GeoJSON objects. Your problem is you have a "psuedo" GeoJSON format that does not really conform to the spec, and that you are trying to access the "coordinates" directly, where you need a top level object like so:

{
    "regions": [
        {
            "name": "penta",
            "geometry": {
                "type": "Polygon",
                "coordinates": [[
                    [ 
                        -77.0322804898023610, 
                        -12.1271067552781560
                    ], 
                    [ 
                        -77.0336792618036270, 
                        -12.1255133434450870
                    ], 
                    [ 
                        -77.0326449349522590, 
                        -12.1239143495252150
                    ], 
                    [ 
                        -77.0300991833209990, 
                        -12.1238251884504540
                    ], 
                    [ 
                        -77.0299865305423740, 
                        -12.1262000752832540
                    ], 
                    [ 
                        -77.0322804898023610, 
                        -12.1271067552781560
                    ]
                ]]
            }
        },
        {
            "name": "triangle",
            "geometry": {
                "type": "Polygon",
                "coordinates": [[
                    [ 
                        -77.0313568040728570, 
                        -12.1266573492018090
                    ], 
                    [ 
                        -77.0325788855552670, 
                        -12.1246968022373030
                    ], 
                    [ 
                        -77.0300653204321860, 
                        -12.1246233756874440
                    ], 
                    [ 
                        -77.0313568040728570, 
                        -12.1266573492018090
                    ]
                ]]
            }
        }
    ]
}

So that abstracts the GeoJSON part to be both well formed and separated from the other meta data that is not part of the spec. Ideally you would index as well, though not required for $geoWithin or $geoIntersects it certainly helps:

db.regions.createIndex({ "regions.geometry": "2dsphere" })

Defining the full path to the GeoJSON defintion within the array element.

Then queries work properly:

db.regions.find({
    "regions.geometry" : { 
        "$geoIntersects" : { 
            "$geometry" : { 
                "type" : "Polygon" , 
                "coordinates" : [[
                    [ -77.02877718955278 , -12.123750122669545],
                    [ -77.03457042574883 , -12.123750122669545],
                    [ -77.03457042574883 , -12.12736341792724],
                    [ -77.02877718955278 , -12.12736341792724], 
                    [ -77.02877718955278 , -12.123750122669545]
                ]]
            }
        }
    }
})

Which matches the document above. But of course there are multiple objects in the array, so the question is, which of these matched? To which there is no supported answer, as MongoDB is matching the "document" and not indicating in any way which array element was matched.

There is an option in the aggregation $geoNear that does allow the matched object to be returned, where in this case it would be "nearest". And with detail like that is is then possible to use that information to match which array element with full meta data contains the element that was found for "nearest" and extract that data. But again it is "near" only and can also never return more than one result from an array.

But generally speaking it is better to just but the separate objects as documents in their own collection, where the match to distinct object is just a matter of matching the document. So with the array above in it's own collection, you just issue the query for the matching geometry:

db.shapes.find({
    "geometry" : { 
        "$geoIntersects" : { 
            "$geometry" : { 
                "type" : "Polygon" , 
                "coordinates" : [ [ 
                    [ -77.02877718955278 , -12.123750122669545],
                    [ -77.03457042574883 , -12.123750122669545],
                    [ -77.03457042574883 , -12.12736341792724],
                    [ -77.02877718955278 , -12.12736341792724], 
                    [ -77.02877718955278 , -12.123750122669545]
                ]]
            }
        }
    }
})

Which gives the correct object(s) as in this case the shape intersects both:

{
    "_id" : ObjectId("55f8d2fa66c2e7c750414b7a"),
    "name" : "penta",
    "geometry" : {
        "type" : "Polygon",
        "coordinates" : [[
            [
                    -77.03228048980236,
                    -12.127106755278156
            ],
            [
                    -77.03367926180363,
                    -12.125513343445087
            ],
            [
                    -77.03264493495226,
                    -12.123914349525215
            ],
            [
                    -77.030099183321,
                    -12.123825188450454
            ],
            [
                    -77.02998653054237,
                    -12.126200075283254
            ],
            [
                    -77.03228048980236,
                    -12.127106755278156
            ]
        ]]
    }
}
{
    "_id" : ObjectId("55f8d2fa66c2e7c750414b7b"),
    "name" : "triangle",
    "geometry" : {
        "type" : "Polygon",
        "coordinates" : [[
            [
                    -77.03135680407286,
                    -12.126657349201809
            ],
            [
                    -77.03257888555527,
                    -12.124696802237303
            ],
            [
                    -77.03006532043219,
                    -12.124623375687444
            ],
            [
                    -77.03135680407286,
                    -12.126657349201809
            ]
        ]]
    }
}

So you can use arrays but you can only really match document and not the individual array members that were part of the match, so this will return of course documents as a whole and you would need to work out which members matched the criteria in client code.

On another note, several of your query attempts seek to "break up" the object coordinates array into individual elements. This is not supported at all as the object can only be dealt with as a whole and not as it's "Point" parts.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM