Geo Queries
The ArangoDB allows to select documents based on geographic coordinates. In order for this to work, a geo-spatial index must be defined. This index will use a very elaborate algorithm to lookup neighbors that is a magnitude faster than a simple R* index.
In general a geo coordinate is a pair of latitude and longitude, which must both be specified as numbers. A geo index can be created on coordinates that are stored in a single list attribute with two elements like [-10, +30] (latitude first, followed by longitude) or on coordinates stored in two separate attributes.
For example, to index the following documents, an index can be created on the position attribute of the documents:
If coordinates are stored in two distinct attributes, the index must be created on the two attributes:
db.test.save({ latitude: 10, longitude: 45.5 });
db.test.ensureIndex({ type: "geo", fields: [ "latitude", "longitude" ] });
In order to find all documents within a given radius around a coordinate use the within operator. In order to find all documents near a given document use the near operator.
It is possible to define more than one geo-spatial index per collection. In this case you must give a hint using the geo operator which of indexes should be used in a query.
constructs a near query for a collectioncollection.near(latitude, longitude)
The returned list is sorted according to the distance, with the nearest document to the coordinate (latitude, longitude) coming first. If there are near documents of equal distance, documents are chosen randomly from this set until the limit is reached. It is possible to change the limit using the limit operator.
In order to use the near operator, a geo index must be defined for the collection. This index also defines which attribute holds the coordinates for the document. If you have more then one geo-spatial index, you can use the geo operator to select a particular index.
Note: near
does not support negative skips. // However, you can still use limit
followed to skip.
collection.near(latitude, longitude).limit(limit)
Limits the result to limit documents instead of the default 100.
Note: Unlike with multiple explicit limits, limit
will raise the implicit default limit imposed by within
.
collection.near(latitude, longitude).distance()
This will add an attribute distance
to all documents returned, which contains the distance between the given point and the document in meters.
collection.near(latitude, longitude).distance(name)
This will add an attribute name to all documents returned, which contains the distance between the given point and the document in meters.
Note: this method is not yet supported by the RocksDB storage engine.
FOR doc IN NEAR(@@collection, @latitude, @longitude, @limit)
RETURN doc
Examples
To get the nearest two locations:
arangosh> db.geo.ensureIndex({ type: "geo", fields: [ "loc" ] });
{
"bestIndexedLevel" : 17,
"fields" : [
"loc"
],
"geoJson" : false,
"id" : "geo/234",
"isNewlyCreated" : true,
"maxNumCoverCells" : 8,
"name" : "idx_1712165072828104704",
"sparse" : true,
"type" : "geo",
"unique" : false,
"worstIndexedLevel" : 4,
"code" : 201
}
arangosh> for (var i = -90; i <= 90; i += 10) {
........> for (var j = -180; j <= 180; j += 10) {
........> db.geo.save({
........> name : "Name/" + i + "/" + j,
........> loc: [ i, j ] });
........> } }
arangosh> db.geo.near(0, 0).limit(2).toArray();
[
{
"_key" : "940",
"_id" : "geo/940",
"_rev" : "_dAzO94u---",
"name" : "Name/0/0",
"loc" : [
0,
0
]
},
{
"_key" : "1014",
"_id" : "geo/1014",
"_rev" : "_dAzO95a---",
"name" : "Name/10/0",
"loc" : [
10,
0
]
}
]
Hide execution results
Show execution results
If you need the distance as well, then you can use the distance
operator:
arangosh> db.geo.ensureIndex({ type: "geo", fields: [ "loc" ] });
{
"bestIndexedLevel" : 17,
"fields" : [
"loc"
],
"geoJson" : false,
"id" : "geo/1656",
"isNewlyCreated" : true,
"maxNumCoverCells" : 8,
"name" : "idx_1712165073088151552",
"sparse" : true,
"type" : "geo",
"unique" : false,
"worstIndexedLevel" : 4,
"code" : 201
}
arangosh> for (var i = -90; i <= 90; i += 10) {
........> for (var j = -180; j <= 180; j += 10) {
........> db.geo.save({
........> name : "Name/" + i + "/" + j,
........> loc: [ i, j ] });
........> } }
arangosh> db.geo.near(0, 0).distance().limit(2).toArray();
[
{
"_id" : "geo/2362",
"_key" : "2362",
"_rev" : "_dAzP-Gy--C",
"loc" : [
0,
0
],
"name" : "Name/0/0",
"distance" : 0
},
{
"_id" : "geo/2436",
"_key" : "2436",
"_rev" : "_dAzP-Hm--E",
"loc" : [
10,
0
],
"name" : "Name/10/0",
"distance" : 1111949.2664455872
}
]
Hide execution results
arangosh> db.geo.ensureIndex({ type: "geo", fields: [ "loc" ] });
arangosh> for (var i = -90; i <= 90; i += 10) {
........> for (var j = -180; j <= 180; j += 10) {
........> db.geo.save({
........> } }
arangosh> db.geo.near(0, 0).distance().limit(2).toArray();
Show execution results
constructs a within query for a collectioncollection.within(latitude, longitude, radius)
This will find all documents within a given radius around the coordinate (latitude, longitude). The returned array is sorted by distance, beginning with the nearest document.
In order to use the within operator, a geo index must be defined for the collection. This index also defines which attribute holds the coordinates for the document. If you have more then one geo-spatial index, you can use the geo
operator to select a particular index.
collection.within(latitude, longitude, radius).distance()
This will add an attribute _distance
to all documents returned, which contains the distance between the given point and the document in meters.
collection.within(latitude, longitude, radius).distance(name)
This will add an attribute name to all documents returned, which contains the distance between the given point and the document in meters.
Note: this method is not yet supported by the RocksDB storage engine.
Note: the within simple query function is deprecated as of ArangoDB 2.6. The function may be removed in future versions of ArangoDB. The preferred way for retrieving documents from a collection using the within operator is to use the AQL WITHIN function in an AQL query as follows:
FOR doc IN WITHIN(@@collection, @latitude, @longitude, @radius, @distanceAttributeName)
RETURN doc
Examples
To find all documents within a radius of 2000 km use:
arangosh> for (var i = -90; i <= 90; i += 10) {
........> for (var j = -180; j <= 180; j += 10) {
........> db.geo.save({ name : "Name/" + i + "/" + j, loc: [ i, j ] }); } }
arangosh> db.geo.within(0, 0, 2000 * 1000).distance().toArray();
Show execution results
constructs a geo index selectioncollection.geo(location-attribute)
Looks up a geo index defined on attribute location_attribute.
Returns a geo index object if an index was found. The near
or within
operators can then be used to execute a geo-spatial query on this particular index.
This is useful for collections with multiple defined geo indexes.
collection.geo(location_attribute, true)
Looks up a geo index on a compound attribute location_attribute.
Returns a geo index object if an index was found. The near
or within
operators can then be used to execute a geo-spatial query on this particular index.
collection.geo(latitude_attribute, longitude_attribute)
Looks up a geo index defined on the two attributes latitude_attribute and longitude-attribute.
Returns a geo index object if an index was found. The near
or within
operators can then be used to execute a geo-spatial query on this particular index.
Note: this method is not yet supported by the RocksDB storage engine.
Note: the geo simple query helper function is deprecated as of ArangoDB 2.6. The function may be removed in future versions of ArangoDB. The preferred way for running geo queries is to use their AQL equivalents.
Examples
Assume you have a location stored as list in the attribute home and a destination stored in the attribute work. Then you can use the geo
operator to select which geo-spatial attributes (and thus which index) to use in a near
query.
arangosh> for (i = -90; i <= 90; i += 10) {
........> for (j = -180; j <= 180; j += 10) {
........> db.complex.save({ name : "Name/" + i + "/" + j,
........> home : [ i, j ],
........> work : [ -i, -j ] });
........> }
........> }
........>
arangosh> db.complex.near(0, 170).limit(5);
[ArangoError 1570: no suitable geo index found for geo restriction on 'complex']
arangosh> db.complex.ensureIndex({ type: "geo", fields: [ "home" ] });
{
"bestIndexedLevel" : 17,
"fields" : [
"home"
],
"geoJson" : false,
"id" : "complex/89170",
"isNewlyCreated" : true,
"maxNumCoverCells" : 8,
"name" : "idx_1712165134763294720",
"sparse" : true,
"type" : "geo",
"unique" : false,
"worstIndexedLevel" : 4,
"code" : 201
}
arangosh> db.complex.near(0, 170).limit(5).toArray();
[
{
"_key" : "88499",
"_id" : "complex/88499",
"_rev" : "_dAzP3Xm--I",
"name" : "Name/0/170",
"home" : [
0,
170
],
"work" : [
0,
-170
]
},
{
"_key" : "88501",
"_id" : "complex/88501",
"_rev" : "_dAzP3Xm--K",
"name" : "Name/0/180",
"home" : [
0,
180
],
"work" : [
0,
-180
]
},
{
"_key" : "88573",
"_id" : "complex/88573",
"_rev" : "_dAzP3YC--E",
"name" : "Name/10/170",
"home" : [
10,
170
],
"work" : [
-10,
-170
]
},
{
"_key" : "88425",
"_id" : "complex/88425",
"_rev" : "_dAzP3XC--C",
"name" : "Name/-10/170",
"home" : [
-10,
170
],
"work" : [
10,
-170
]
},
{
"_key" : "88429",
"_id" : "complex/88429",
"_rev" : "_dAzP3XG---",
"name" : "Name/0/-180",
0,
-180
],
"work" : [
0,
180
]
}
arangosh> db.complex.geo("work").near(0, 170).limit(5);
[ArangoError 1570: no suitable geo index found for geo restriction on 'complex']
arangosh> db.complex.ensureIndex({ type: "geo", fields: [ "work" ] });
{
"bestIndexedLevel" : 17,
"fields" : [
"work"
],
"geoJson" : false,
"id" : "complex/89180",
"isNewlyCreated" : true,
"maxNumCoverCells" : 8,
"name" : "idx_1712165134769586176",
"sparse" : true,
"type" : "geo",
"unique" : false,
"worstIndexedLevel" : 4,
"code" : 201
}
arangosh> db.complex.geo("work").near(0, 170).limit(5).toArray();
[
{
"_key" : "88499",
"_id" : "complex/88499",
"_rev" : "_dAzP3Xm--I",
"name" : "Name/0/170",
"home" : [
0,
170
],
"work" : [
0,
-170
]
},
{
"_key" : "88501",
"_id" : "complex/88501",
"_rev" : "_dAzP3Xm--K",
"name" : "Name/0/180",
"home" : [
0,
180
],
"work" : [
0,
-180
]
},
{
"_key" : "88573",
"_id" : "complex/88573",
"_rev" : "_dAzP3YC--E",
"name" : "Name/10/170",
"home" : [
10,
170
],
"work" : [
-10,
-170
]
},
{
"_key" : "88425",
"_id" : "complex/88425",
"_rev" : "_dAzP3XC--C",
"name" : "Name/-10/170",
"home" : [
-10,
170
],
"work" : [
10,
-170
]
},
{
"_key" : "88429",
"_id" : "complex/88429",
"_rev" : "_dAzP3XG---",
"name" : "Name/0/-180",
"home" : [
0,
-180
],
"work" : [
0,
180
]
}
]
Hide execution results
arangosh> for (i = -90; i <= 90; i += 10) {
........> for (j = -180; j <= 180; j += 10) {
........> db.complex.save({ name : "Name/" + i + "/" + j,
........> home : [ i, j ],
........> work : [ -i, -j ] });
........> }
........> }
........>
arangosh> db.complex.near(0, 170).limit(5);
arangosh> db.complex.ensureIndex({ type: "geo", fields: [ "home" ] });
arangosh> db.complex.near(0, 170).limit(5).toArray();
arangosh> db.complex.geo("work").near(0, 170).limit(5);
arangosh> db.complex.ensureIndex({ type: "geo", fields: [ "work" ] });
Show execution results
Other ArangoDB geographic features are described in: