Geo-Spatial Indexes

ArangoDB features a Google S2 based geospatial indexsince version 3.4.0, which supersedes the previous geo index implementation.Indexing is supported for a subset of the GeoJSON geometry typesas well as simple latitude longitude pairs.

AQL’s geospatial functions and GeoJSON constructors are described inGeo functions.

Using a Geo-Spatial Index

The geospatial index supports containment and intersectionqueries for various geometric 2D shapes. You should be mainly using AQL queriesto perform these types of operations. The index can operate in two differentmodes, depending on if you want to use the GeoJSON data-format or not. The modesare mainly toggled by using the geoJson field when creating the index.

This index assumes coordinates with the latitude between -90 and 90 degrees and thelongitude between -180 and 180 degrees. A geo index will ignore all documents which do not fulfill these requirements.

GeoJSON Mode

To create an index in GeoJSON mode execute:

  1. collection.ensureIndex({ type: "geo", fields: [ "geometry" ], geoJson:true })

This creates the index on all documents and uses geometry as the attributedfield where the value is either a GeometryObjector a coordinatearray. The array must contain at least two numeric values with longitude (firstvalue) and the latitude (second value). This corresponds to the formatdescribed in RFC 7946Position

All documents, which do not have the attribute path or have a non-conformvalue in it, are excluded from the index.

A geo index is implicitly sparse, and there is no way to control its sparsity.In case that the index was successfully created, an object with the indexdetails, including the index-identifier, is returned.

Non-GeoJSON mode

This index mode exclusively supports indexing on coordinate arrays. Values thatcontain GeoJSON or other types of data will be ignored. In the non-GeoJSON modethe index can be created on one or two fields.

The following examples will work in the arangosh command shell.

To create a geo-spatial index on all documents using latitude andlongitude as separate attribute paths, two paths need to be specifiedin the fields array:

collection.ensureIndex({ type: "geo", fields: [ "latitude", "longitude" ] })

The first field is always defined to be the latitude and the second is thelongitude. The geoJson flag is implicitly false in this mode.

Alternatively you can specify only one field:

collection.ensureIndex({ type: "geo", fields: [ "location" ], geoJson:false })

It creates a geospatial index on all documents using location as the path to thecoordinates. The value of the attribute has to be an array with at least twonumeric values. The array must contain the latitude (first value) and thelongitude (second value).

All documents, which do not have the attribute path(s) or have a non-conformingvalue in it, are excluded from the index.

A geo index is implicitly sparse, and there is no way to control its sparsity.In case that the index was successfully created, an object with the indexdetails, including the index-identifier, is returned.

In case that the index was successfully created, an object with the indexdetails, including the index-identifier, is returned.

Indexed GeoSpatial Queries

The geospatial index supports a variety of AQL queries, which can be built with the helpof the geo utility functions. There are three specificgeo functions that can be optimized, provided that they are used correctly:GEODISTANCE, GEO_CONTAINS, GEO_INTERSECTS. Additionally, there is a built-in support to optimizethe older geo functions DISTANCE, NEAR and WITHIN (the last two only if they areused in their 4 argument version, without _distanceName).

When in doubt whether your query is being properly optimized, check the AQL explainoutput to check for index usage.

Query for Results near Origin (NEAR type query)

A basic example of a query for results near an origin point:

  1. FOR x IN geo_collection
  2. FILTER GEO_DISTANCE([@lng, @lat], x.geometry) <= 100000
  3. RETURN x._key

The first parameter can be a GeoJSON object or a coordinate array in [longitude, latitude] ordering.The second parameter is the document field on which the index was created. The functionGEODISTANCE always returns the distance in meters, so will receive resultsup until _100km.

Query for Sorted Results near Origin (NEAR type query)

A basic example of a query for the 1000 nearest results to an origin point (ascending sorting):

  1. FOR x IN geo_collection
  2. SORT GEO_DISTANCE([@lng, @lat], x.geometry) ASC
  3. LIMIT 1000
  4. RETURN x._key

The first parameter can be a GeoJSON object or a coordinate array in [longitude, latitude] ordering.The second parameter is the documents field on which the index was created.

You may also get results farthest away (distance sorted in descending order):

  1. FOR x IN geo_collection
  2. SORT GEO_DISTANCE([@lng, @lat], x.geometry) DESC
  3. LIMIT 1000
  4. RETURN x._key

Query for Results within Distance

A query which returns documents at a distance of 1km or farther away,up to 100km from the origin. This will return the documents with a GeoJSONvalue that is located in the specified search annulus.

  1. FOR x IN geo_collection
  2. FILTER GEO_DISTANCE([@lng, @lat], x.geometry) <= 100000
  3. FILTER GEO_DISTANCE([@lng, @lat], x.geometry) >= 1000
  4. RETURN x

Query for Results contained in Polygon

A query which returns documents whose stored geometry is contained within aGeoJSON Polygon.

  1. LET polygon = GEO_POLYGON([[[60,35],[50,5],[75,10],[70,35]]])
  2. FOR x IN geo_collection
  3. FILTER GEO_CONTAINS(polygon, x.geometry)
  4. RETURN x

The first parameter of GEO_CONTAINS must be a polygon. Other types are not valid. The second parameter must contain the document field on which the index was created.

Query for Results Intersecting a Polygon

A query which returns documents with an intersection of their stored geometry and aGeoJSON Polygon.

  1. LET polygon = GEO_POLYGON([[[60,35],[50,5],[75,10],[70,35]]])
  2. FOR x IN geo_collection
  3. FILTER GEO_INTERSECTS(polygon, x.geometry)
  4. RETURN x

The first parameter of GEO_INTERSECTS must be a polygon. Other types are not valid. The second parameter must contain the document field on which the index was created.

GeoJSON

GeoJSON is a geospatial data format based on JSON. It defines several differenttypes of JSON objects and the way in which they can be combined to representdata about geographic shapes on the earth surface. GeoJSON uses a geographiccoordinate reference system, World Geodetic System 1984 (WGS 84), and units of decimaldegrees.

Internally ArangoDB maps all coordinates onto a unit sphere. Distances areprojected onto a sphere with the Earth’s Volumetric mean radius of 6371km. ArangoDB implements a useful subset of the GeoJSON format (RFC7946). We do not support Feature Objectsor the GeometryCollection type. Supported geometry object types are:

  • Point
  • MultiPoint
  • LineString
  • MultiLineString
  • Polygon

Point

A GeoJSON Point is aposition comprised ofa longitude and a latitude:

  1. {
  2. "type": "Point",
  3. "coordinates": [100.0, 0.0]
  4. }

MultiPoint

A GeoJSON MultiPoint isan array of positions:

  1. {
  2. "type": "MultiPoint",
  3. "coordinates": [
  4. [100.0, 0.0],
  5. [101.0, 1.0]
  6. ]
  7. }

LineString

A GeoJSON LineString isan array of two or more positions:

  1. {
  2. "type": "LineString",
  3. "coordinates": [
  4. [100.0, 0.0],
  5. [101.0, 1.0]
  6. ]
  7. }

MultiLineString

A GeoJSON MultiLineString isan array of LineString coordinate arrays:

  1. {
  2. "type": "MultiLineString",
  3. "coordinates": [
  4. [
  5. [100.0, 0.0],
  6. [101.0, 1.0]
  7. ],
  8. [
  9. [102.0, 2.0],
  10. [103.0, 3.0]
  11. ]
  12. ]
  13. }

Polygon

A GeoJSON Polygon consistsof a series of closed LineString objects (ring-like). These Linear Ring objectsconsist of four or more vertices with the first and last coordinate pairsbeing equal. Coordinates of a Polygon are an array of linear ring coordinatearrays. The first element in the array represents the exterior ring.Any subsequent elements represent interior rings (holes within the surface).

  • A linear ring may not be empty, it needs at least three distinct coordinates
  • Within the same linear ring consecutive coordinates may be the same, otherwise(except the first and last one) all coordinates need to be distinct
  • A linear ring defines two regions on the sphere. ArangoDB will always interpretthe region of smaller area to be the interior of the ring. This introduces apractical limitation that no polygon may have an outer ring enclosing morethan half the Earth’s surfaceNo Holes:
  1. {
  2. "type": "Polygon",
  3. "coordinates": [
  4. [
  5. [100.0, 0.0],
  6. [101.0, 0.0],
  7. [101.0, 1.0],
  8. [100.0, 1.0],
  9. [100.0, 0.0]
  10. ]
  11. ]
  12. }

With Holes:

  • The exterior ring should not self-intersect.
  • The interior rings must be contained in the outer ring
  • No two rings can cross each other, i.e. no ring may intersect boththe interior and exterior face of another ring
  • Rings cannot share edges, they may however share vertices
  • No ring may be empty
  • Polygon rings should follow the right-hand rule for orientation(counterclockwise external rings, clockwise internal rings).
  1. {
  2. "type": "Polygon",
  3. "coordinates": [
  4. [
  5. [100.0, 0.0],
  6. [101.0, 0.0],
  7. [101.0, 1.0],
  8. [100.0, 1.0],
  9. [100.0, 0.0]
  10. ],
  11. [
  12. [100.8, 0.8],
  13. [100.8, 0.2],
  14. [100.2, 0.2],
  15. [100.2, 0.8],
  16. [100.8, 0.8]
  17. ]
  18. ]
  19. }

MultiPolygon

A GeoJSON MultiPolygon consistsof multiple polygons. The “coordinates” member is an array ofPolygon coordinate arrays.

  • Polygons in the same MultiPolygon may not share edges, they may share coordinates
  • Polygons and rings must not be empty
  • A linear ring defines two regions on the sphere. ArangoDB will always interpretthe region of smaller area to be the interior of the ring. This introduces apractical limitation that no polygon may have an outer ring enclosing morethan half the Earth’s surface
  • Linear rings must follow the right-hand rule for orientation(counterclockwise external rings, clockwise internal rings).Example with two polygons, the second one with a hole:
  1. {
  2. "type": "MultiPolygon",
  3. "coordinates": [
  4. [
  5. [
  6. [102.0, 2.0],
  7. [103.0, 2.0],
  8. [103.0, 3.0],
  9. [102.0, 3.0],
  10. [102.0, 2.0]
  11. ]
  12. ],
  13. [
  14. [
  15. [100.0, 0.0],
  16. [101.0, 0.0],
  17. [101.0, 1.0],
  18. [100.0, 1.0],
  19. [100.0, 0.0]
  20. ],
  21. [
  22. [100.2, 0.2],
  23. [100.2, 0.8],
  24. [100.8, 0.8],
  25. [100.8, 0.2],
  26. [100.2, 0.2]
  27. ]
  28. ]
  29. ]
  30. }

Arangosh Examples

ensures that a geo index existscollection.ensureIndex({ type: "geo", fields: [ "location" ] })

Creates a geospatial index on all documents using location as the path to thecoordinates. The value of the attribute has to be an array with at least twonumeric values. The array must contain the latitude (first value) and thelongitude (second value).

All documents, which do not have the attribute path or have a non-conformingvalue in it, are excluded from the index.

A geo index is implicitly sparse, and there is no way to control its sparsity.

In case that the index was successfully created, an object with the indexdetails, including the index-identifier, is returned.

To create a geo index on an array attribute that contains longitude first, setthe geoJson attribute to true. This corresponds to the format described inRFC 7946 Position

collection.ensureIndex({ type: "geo", fields: [ "location" ], geoJson: true })

To create a geo-spatial index on all documents using latitude and _longitude_as separate attribute paths, two paths need to be specified in the _fields_array:

collection.ensureIndex({ type: "geo", fields: [ "latitude", "longitude" ] })

In case that the index was successfully created, an object with the indexdetails, including the index-identifier, is returned.

Examples

Create a geo index for an array attribute:

  1. arangosh> db.geo.ensureIndex({ type: "geo", fields: [ "loc" ] });
  2. arangosh> for (i = -90; i <= 90; i += 10) {
  3. ........> for (j = -180; j <= 180; j += 10) {
  4. ........> db.geo.save({ name : "Name/" + i + "/" + j, loc: [ i, j ] });
  5. ........> }
  6. ........> }
  7. arangosh> db.geo.count();
  8. arangosh> db.geo.near(0, 0).limit(3).toArray();
  9. arangosh> db.geo.near(0, 0).count();

Show execution results

  1. {
  2. "bestIndexedLevel" : 17,
  3. "fields" : [
  4. "loc"
  5. ],
  6. "geoJson" : false,
  7. "id" : "geo/80359",
  8. "isNewlyCreated" : true,
  9. "maxNumCoverCells" : 8,
  10. "name" : "idx_1642473902232829954",
  11. "sparse" : true,
  12. "type" : "geo",
  13. "unique" : false,
  14. "worstIndexedLevel" : 4,
  15. "code" : 201
  16. }
  17. 703
  18. [
  19. {
  20. "_key" : "81065",
  21. "_id" : "geo/81065",
  22. "_rev" : "_ZJNS9ay--A",
  23. "name" : "Name/0/0",
  24. "loc" : [
  25. 0,
  26. 0
  27. ]
  28. },
  29. {
  30. "_key" : "81139",
  31. "_id" : "geo/81139",
  32. "_rev" : "_ZJNS9bW---",
  33. "name" : "Name/10/0",
  34. "loc" : [
  35. 10,
  36. 0
  37. ]
  38. },
  39. {
  40. "_key" : "81067",
  41. "_id" : "geo/81067",
  42. "_rev" : "_ZJNS9ay--C",
  43. "name" : "Name/0/10",
  44. "loc" : [
  45. 0,
  46. 10
  47. ]
  48. }
  49. ]
  50. null

Hide execution results

Create a geo index for a hash array attribute:

  1. arangosh> db.geo2.ensureIndex({ type: "geo", fields: [ "location.latitude", "location.longitude" ] });
  2. arangosh> for (i = -90; i <= 90; i += 10) {
  3. ........> for (j = -180; j <= 180; j += 10) {
  4. ........> db.geo2.save({ name : "Name/" + i + "/" + j, location: { latitude : i, longitude : j } });
  5. ........> }
  6. ........> }
  7. arangosh> db.geo2.near(0, 0).limit(3).toArray();

Show execution results

  1. {
  2. "bestIndexedLevel" : 17,
  3. "fields" : [
  4. "location.latitude",
  5. "location.longitude"
  6. ],
  7. "geoJson" : false,
  8. "id" : "geo2/81785",
  9. "isNewlyCreated" : true,
  10. "maxNumCoverCells" : 8,
  11. "name" : "idx_1642473902428913666",
  12. "sparse" : true,
  13. "type" : "geo",
  14. "unique" : false,
  15. "worstIndexedLevel" : 4,
  16. "code" : 201
  17. }
  18. [
  19. {
  20. "_key" : "82491",
  21. "_id" : "geo2/82491",
  22. "_rev" : "_ZJNS9me--A",
  23. "name" : "Name/0/0",
  24. "location" : {
  25. "latitude" : 0,
  26. "longitude" : 0
  27. }
  28. },
  29. {
  30. "_key" : "82565",
  31. "_id" : "geo2/82565",
  32. "_rev" : "_ZJNS9nC---",
  33. "name" : "Name/10/0",
  34. "location" : {
  35. "latitude" : 10,
  36. "longitude" : 0
  37. }
  38. },
  39. {
  40. "_key" : "82493",
  41. "_id" : "geo2/82493",
  42. "_rev" : "_ZJNS9me--C",
  43. "name" : "Name/0/10",
  44. "location" : {
  45. "latitude" : 0,
  46. "longitude" : 10
  47. }
  48. }
  49. ]

Hide execution results

Use GeoIndex with AQL SORT statement:

  1. arangosh> db.geoSort.ensureIndex({ type: "geo", fields: [ "latitude", "longitude" ] });
  2. arangosh> for (i = -90; i <= 90; i += 10) {
  3. ........> for (j = -180; j <= 180; j += 10) {
  4. ........> db.geoSort.save({ name : "Name/" + i + "/" + j, latitude : i, longitude : j });
  5. ........> }
  6. ........> }
  7. arangosh> var query = "FOR doc in geoSort SORT DISTANCE(doc.latitude, doc.longitude, 0, 0) LIMIT 5 RETURN doc"
  8. arangosh> db._explain(query, {}, {colors: false});
  9. arangosh> db._query(query);

Show execution results

  1. {
  2. "bestIndexedLevel" : 17,
  3. "fields" : [
  4. "latitude",
  5. "longitude"
  6. ],
  7. "geoJson" : false,
  8. "id" : "geoSort/86056",
  9. "isNewlyCreated" : true,
  10. "maxNumCoverCells" : 8,
  11. "name" : "idx_1642473902998290434",
  12. "sparse" : true,
  13. "type" : "geo",
  14. "unique" : false,
  15. "worstIndexedLevel" : 4,
  16. "code" : 201
  17. }
  18. Query String:
  19. FOR doc in geoSort SORT DISTANCE(doc.latitude, doc.longitude, 0, 0) LIMIT 5 RETURN doc
  20.  
  21. Execution plan:
  22. Id NodeType Est. Comment
  23. 1 SingletonNode 1 * ROOT
  24. 7 IndexNode 703 - FOR doc IN geoSort /* geo index scan */
  25. 5 LimitNode 5 - LIMIT 0, 5
  26. 6 ReturnNode 5 - RETURN doc
  27.  
  28. Indexes used:
  29. By Name Type Collection Unique Sparse Selectivity Fields Ranges
  30. 7 idx_1642473902998290434 geo geoSort false true n/a [ `latitude`, `longitude` ] (GEO_DISTANCE([ 0, 0 ], [ doc.`longitude`, doc.`latitude` ]) < "unlimited")
  31.  
  32. Optimization rules applied:
  33. Id RuleName
  34. 1 geo-index-optimizer
  35. 2 remove-unnecessary-calculations-2
  36.  
  37.  
  38. [
  39. {
  40. "_key" : "86762",
  41. "_id" : "geoSort/86762",
  42. "_rev" : "_ZJNT-IO--E",
  43. "name" : "Name/0/0",
  44. "latitude" : 0,
  45. "longitude" : 0
  46. },
  47. {
  48. "_key" : "86836",
  49. "_id" : "geoSort/86836",
  50. "_rev" : "_ZJNT-Iy--A",
  51. "name" : "Name/10/0",
  52. "latitude" : 10,
  53. "longitude" : 0
  54. },
  55. {
  56. "_key" : "86764",
  57. "_id" : "geoSort/86764",
  58. "_rev" : "_ZJNT-IS---",
  59. "name" : "Name/0/10",
  60. "latitude" : 0,
  61. "longitude" : 10
  62. },
  63. {
  64. "_key" : "86688",
  65. "_id" : "geoSort/86688",
  66. "_rev" : "_ZJNT-Hu--A",
  67. "name" : "Name/-10/0",
  68. "latitude" : -10,
  69. "longitude" : 0
  70. },
  71. {
  72. "_key" : "86760",
  73. "_id" : "geoSort/86760",
  74. "_rev" : "_ZJNT-IO--C",
  75. "name" : "Name/0/-10",
  76. "latitude" : 0,
  77. "longitude" : -10
  78. }
  79. ]
  80. [object ArangoQueryCursor, count: 5, cached: false, hasMore: false]

Hide execution results

Use GeoIndex with AQL FILTER statement:

  1. arangosh> db.geoFilter.ensureIndex({ type: "geo", fields: [ "latitude", "longitude" ] });
  2. arangosh> for (i = -90; i <= 90; i += 10) {
  3. ........> for (j = -180; j <= 180; j += 10) {
  4. ........> db.geoFilter.save({ name : "Name/" + i + "/" + j, latitude : i, longitude : j });
  5. ........> }
  6. ........> }
  7. arangosh> var query = "FOR doc in geoFilter FILTER DISTANCE(doc.latitude, doc.longitude, 0, 0) < 2000 RETURN doc"
  8. arangosh> db._explain(query, {}, {colors: false});
  9. arangosh> db._query(query);

Show execution results

  1. {
  2. "bestIndexedLevel" : 17,
  3. "fields" : [
  4. "latitude",
  5. "longitude"
  6. ],
  7. "geoJson" : false,
  8. "id" : "geoFilter/83206",
  9. "isNewlyCreated" : true,
  10. "maxNumCoverCells" : 8,
  11. "name" : "idx_1642473902616608768",
  12. "sparse" : true,
  13. "type" : "geo",
  14. "unique" : false,
  15. "worstIndexedLevel" : 4,
  16. "code" : 201
  17. }
  18. Query String:
  19. FOR doc in geoFilter FILTER DISTANCE(doc.latitude, doc.longitude, 0, 0) < 2000 RETURN doc
  20.  
  21. Execution plan:
  22. Id NodeType Est. Comment
  23. 1 SingletonNode 1 * ROOT
  24. 6 IndexNode 703 - FOR doc IN geoFilter /* geo index scan */
  25. 5 ReturnNode 703 - RETURN doc
  26.  
  27. Indexes used:
  28. By Name Type Collection Unique Sparse Selectivity Fields Ranges
  29. 6 idx_1642473902616608768 geo geoFilter false true n/a [ `latitude`, `longitude` ] (GEO_DISTANCE([ 0, 0 ], [ doc.`longitude`, doc.`latitude` ]) < 2000)
  30.  
  31. Optimization rules applied:
  32. Id RuleName
  33. 1 geo-index-optimizer
  34. 2 remove-unnecessary-calculations-2
  35.  
  36.  
  37. [
  38. {
  39. "_key" : "83912",
  40. "_id" : "geoFilter/83912",
  41. "_rev" : "_ZJNS9xi--A",
  42. "name" : "Name/0/0",
  43. "latitude" : 0,
  44. "longitude" : 0
  45. }
  46. ]
  47. [object ArangoQueryCursor, count: 1, cached: false, hasMore: false]

Hide 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 orwithin operators can then be used to execute a geo-spatial query onthis 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 orwithin operators can then be used to execute a geo-spatial query onthis 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 orwithin operators can then be used to execute a geo-spatial query onthis 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 ArangoDB2.6. The function may be removed in future versions of ArangoDB. The preferredway 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 thegeo operator to select which geo-spatial attributes (and thus whichindex) to use in a near query.

  1. arangosh> for (i = -90; i <= 90; i += 10) {
  2. ........> for (j = -180; j <= 180; j += 10) {
  3. ........> db.complex.save({ name : "Name/" + i + "/" + j,
  4. ........> home : [ i, j ],
  5. ........> work : [ -i, -j ] });
  6. ........> }
  7. ........> }
  8. ........>
  9. arangosh> db.complex.near(0, 170).limit(5);
  10. [ArangoError 1570: no suitable geo index found for geo restriction on 'complex']
  11. arangosh> db.complex.ensureIndex({ type: "geo", fields: [ "home" ] });
  12. {
  13. "bestIndexedLevel" : 17,
  14. "fields" : [
  15. "home"
  16. ],
  17. "geoJson" : false,
  18. "id" : "complex/86032",
  19. "isNewlyCreated" : true,
  20. "maxNumCoverCells" : 8,
  21. "name" : "idx_1642473902976270338",
  22. "sparse" : true,
  23. "type" : "geo",
  24. "unique" : false,
  25. "worstIndexedLevel" : 4,
  26. "code" : 201
  27. }
  28. arangosh> db.complex.near(0, 170).limit(5).toArray();
  29. [
  30. {
  31. "_key" : "85361",
  32. "_id" : "complex/85361",
  33. "_rev" : "_ZJNS982---",
  34. "name" : "Name/0/170",
  35. "home" : [
  36. 0,
  37. 170
  38. ],
  39. "work" : [
  40. 0,
  41. -170
  42. ]
  43. },
  44. {
  45. "_key" : "85363",
  46. "_id" : "complex/85363",
  47. "_rev" : "_ZJNS982--A",
  48. "name" : "Name/0/180",
  49. "home" : [
  50. 0,
  51. 180
  52. ],
  53. "work" : [
  54. 0,
  55. -180
  56. ]
  57. },
  58. {
  59. "_key" : "85435",
  60. "_id" : "complex/85435",
  61. "_rev" : "_ZJNS99W--E",
  62. "name" : "Name/10/170",
  63. "home" : [
  64. 10,
  65. 170
  66. ],
  67. "work" : [
  68. -10,
  69. -170
  70. ]
  71. },
  72. {
  73. "_key" : "85287",
  74. "_id" : "complex/85287",
  75. "_rev" : "_ZJNS98S--A",
  76. "name" : "Name/-10/170",
  77. "home" : [
  78. -10,
  79. 170
  80. ],
  81. "work" : [
  82. 10,
  83. -170
  84. ]
  85. },
  86. {
  87. "_key" : "85291",
  88. "_id" : "complex/85291",
  89. "_rev" : "_ZJNS98S--E",
  90. "name" : "Name/0/-180",
  91. "home" : [
  92. 0,
  93. -180
  94. ],
  95. "work" : [
  96. 0,
  97. 180
  98. ]
  99. }
  100. ]
  101. arangosh> db.complex.geo("work").near(0, 170).limit(5);
  102. [ArangoError 1570: no suitable geo index found for geo restriction on 'complex']
  103. arangosh> db.complex.ensureIndex({ type: "geo", fields: [ "work" ] });
  104. {
  105. "bestIndexedLevel" : 17,
  106. "fields" : [
  107. "work"
  108. ],
  109. "geoJson" : false,
  110. "id" : "complex/86041",
  111. "isNewlyCreated" : true,
  112. "maxNumCoverCells" : 8,
  113. "name" : "idx_1642473902982561792",
  114. "sparse" : true,
  115. "type" : "geo",
  116. "unique" : false,
  117. "worstIndexedLevel" : 4,
  118. "code" : 201
  119. }
  120. arangosh> db.complex.geo("work").near(0, 170).limit(5).toArray();
  121. [
  122. {
  123. "_key" : "85361",
  124. "_id" : "complex/85361",
  125. "_rev" : "_ZJNS982---",
  126. "name" : "Name/0/170",
  127. "home" : [
  128. 0,
  129. 170
  130. ],
  131. "work" : [
  132. 0,
  133. -170
  134. ]
  135. },
  136. {
  137. "_key" : "85363",
  138. "_id" : "complex/85363",
  139. "_rev" : "_ZJNS982--A",
  140. "name" : "Name/0/180",
  141. "home" : [
  142. 0,
  143. 180
  144. ],
  145. "work" : [
  146. 0,
  147. -180
  148. ]
  149. },
  150. {
  151. "_key" : "85435",
  152. "_id" : "complex/85435",
  153. "_rev" : "_ZJNS99W--E",
  154. "name" : "Name/10/170",
  155. "home" : [
  156. 10,
  157. 170
  158. ],
  159. "work" : [
  160. -10,
  161. -170
  162. ]
  163. },
  164. {
  165. "_key" : "85287",
  166. "_id" : "complex/85287",
  167. "_rev" : "_ZJNS98S--A",
  168. "name" : "Name/-10/170",
  169. "home" : [
  170. -10,
  171. 170
  172. ],
  173. "work" : [
  174. 10,
  175. -170
  176. ]
  177. },
  178. {
  179. "_key" : "85291",
  180. "_id" : "complex/85291",
  181. "_rev" : "_ZJNS98S--E",
  182. "name" : "Name/0/-180",
  183. "home" : [
  184. 0,
  185. -180
  186. ],
  187. "work" : [
  188. 0,
  189. 180
  190. ]
  191. }
  192. ]

Hide execution results

  1. arangosh> for (i = -90; i <= 90; i += 10) {
  2. ........> for (j = -180; j <= 180; j += 10) {
  3. ........> db.complex.save({ name : "Name/" + i + "/" + j,
  4. ........> home : [ i, j ],
  5. ........> work : [ -i, -j ] });
  6. ........> }
  7. ........> }
  8. ........>
  9. arangosh> db.complex.near(0, 170).limit(5);
  10. arangosh> db.complex.ensureIndex({ type: "geo", fields: [ "home" ] });
  11. arangosh> db.complex.near(0, 170).limit(5).toArray();
  12. arangosh> db.complex.geo("work").near(0, 170).limit(5);
  13. arangosh> db.complex.ensureIndex({ type: "geo", fields: [ "work" ] });
  14. arangosh> db.complex.geo("work").near(0, 170).limit(5).toArray();

Show execution results

constructs a near query for a collectioncollection.near(latitude, longitude)

The returned list is sorted according to the distance, with the nearestdocument to the coordinate (latitude, longitude) coming first.If there are near documents of equal distance, documents are chosen randomlyfrom this set until the limit is reached. It is possible to change the limitusing the limit operator.

In order to use the near operator, a geo index must be defined for thecollection. This index also defines which attribute holds the coordinatesfor the document. If you have more then one geo-spatial index, you can usethe 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 raisethe implicit default limit imposed by within.

collection.near(latitude, longitude).distance()

This will add an attribute distance to all documents returned, whichcontains 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, whichcontains 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 near simple query function is deprecated as of ArangoDB 2.6.The function may be removed in future versions of ArangoDB. The preferredway for retrieving documents from a collection using the near operator isto use the AQL NEAR function in an AQL query as follows:

  1. FOR doc IN NEAR(@@collection, @latitude, @longitude, @limit)
  2. RETURN doc

Examples

To get the nearest two locations:

  1. arangosh> db.geo.ensureIndex({ type: "geo", fields: [ "loc" ] });
  2. {
  3. "bestIndexedLevel" : 17,
  4. "fields" : [
  5. "loc"
  6. ],
  7. "geoJson" : false,
  8. "id" : "geo/216",
  9. "isNewlyCreated" : true,
  10. "maxNumCoverCells" : 8,
  11. "name" : "idx_1642473871424618496",
  12. "sparse" : true,
  13. "type" : "geo",
  14. "unique" : false,
  15. "worstIndexedLevel" : 4,
  16. "code" : 201
  17. }
  18. arangosh> for (var i = -90; i <= 90; i += 10) {
  19. ........> for (var j = -180; j <= 180; j += 10) {
  20. ........> db.geo.save({
  21. ........> name : "Name/" + i + "/" + j,
  22. ........> loc: [ i, j ] });
  23. ........> } }
  24. arangosh> db.geo.near(0, 0).limit(2).toArray();
  25. [
  26. {
  27. "_key" : "922",
  28. "_id" : "geo/922",
  29. "_rev" : "_ZJNSgty--E",
  30. "name" : "Name/0/0",
  31. "loc" : [
  32. 0,
  33. 0
  34. ]
  35. },
  36. {
  37. "_key" : "996",
  38. "_id" : "geo/996",
  39. "_rev" : "_ZJNSguS--C",
  40. "name" : "Name/10/0",
  41. "loc" : [
  42. 10,
  43. 0
  44. ]
  45. }
  46. ]

Hide execution results

  1. arangosh> db.geo.ensureIndex({ type: "geo", fields: [ "loc" ] });
  2. arangosh> for (var i = -90; i <= 90; i += 10) {
  3. ........> for (var j = -180; j <= 180; j += 10) {
  4. ........> db.geo.save({
  5. ........> name : "Name/" + i + "/" + j,
  6. ........> loc: [ i, j ] });
  7. ........> } }
  8. arangosh> db.geo.near(0, 0).limit(2).toArray();

Show execution results

If you need the distance as well, then you can use the distanceoperator:

  1. arangosh> db.geo.ensureIndex({ type: "geo", fields: [ "loc" ] });
  2. {
  3. "bestIndexedLevel" : 17,
  4. "fields" : [
  5. "loc"
  6. ],
  7. "geoJson" : false,
  8. "id" : "geo/1637",
  9. "isNewlyCreated" : true,
  10. "maxNumCoverCells" : 8,
  11. "name" : "idx_1642473871598682112",
  12. "sparse" : true,
  13. "type" : "geo",
  14. "unique" : false,
  15. "worstIndexedLevel" : 4,
  16. "code" : 201
  17. }
  18. arangosh> for (var i = -90; i <= 90; i += 10) {
  19. ........> for (var j = -180; j <= 180; j += 10) {
  20. ........> db.geo.save({
  21. ........> name : "Name/" + i + "/" + j,
  22. ........> loc: [ i, j ] });
  23. ........> } }
  24. arangosh> db.geo.near(0, 0).distance().limit(2).toArray();
  25. [
  26. {
  27. "_id" : "geo/2343",
  28. "_key" : "2343",
  29. "_rev" : "_ZJNSg42--A",
  30. "loc" : [
  31. 0,
  32. 0
  33. ],
  34. "name" : "Name/0/0",
  35. "distance" : 0
  36. },
  37. {
  38. "_id" : "geo/2417",
  39. "_key" : "2417",
  40. "_rev" : "_ZJNSg5a--A",
  41. "loc" : [
  42. 10,
  43. 0
  44. ],
  45. "name" : "Name/10/0",
  46. "distance" : 1111949.2664455872
  47. }
  48. ]

Hide execution results

  1. arangosh> db.geo.ensureIndex({ type: "geo", fields: [ "loc" ] });
  2. arangosh> for (var i = -90; i <= 90; i += 10) {
  3. ........> for (var j = -180; j <= 180; j += 10) {
  4. ........> db.geo.save({
  5. ........> name : "Name/" + i + "/" + j,
  6. ........> loc: [ i, j ] });
  7. ........> } }
  8. 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 thecollection. This index also defines which attribute holds the coordinatesfor the document. If you have more then one geo-spatial index, you can usethe geo operator to select a particular index.

collection.within(latitude, longitude, radius).distance()

This will add an attribute _distance to all documents returned, whichcontains 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, whichcontains 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 preferredway for retrieving documents from a collection using the within operator isto use the AQL WITHIN function in an AQL query as follows:

  1. FOR doc IN WITHIN(@@collection, @latitude, @longitude, @radius, @distanceAttributeName)
  2. RETURN doc

Examples

To find all documents within a radius of 2000 km use:

  1. arangosh> for (var i = -90; i <= 90; i += 10) {
  2. ........> for (var j = -180; j <= 180; j += 10) {
  3. ........> db.geo.save({ name : "Name/" + i + "/" + j, loc: [ i, j ] }); } }
  4. arangosh> db.geo.within(0, 0, 2000 * 1000).distance().toArray();
  5. [
  6. {
  7. "_id" : "geo/3764",
  8. "_key" : "3764",
  9. "_rev" : "_ZJNShEK--C",
  10. "loc" : [
  11. 0,
  12. 0
  13. ],
  14. "name" : "Name/0/0",
  15. "distance" : 0
  16. },
  17. {
  18. "_id" : "geo/3838",
  19. "_key" : "3838",
  20. "_rev" : "_ZJNShEu--C",
  21. "loc" : [
  22. 10,
  23. 0
  24. ],
  25. "name" : "Name/10/0",
  26. "distance" : 1111949.2664455872
  27. },
  28. {
  29. "_id" : "geo/3766",
  30. "_key" : "3766",
  31. "_rev" : "_ZJNShEK--E",
  32. "loc" : [
  33. 0,
  34. 10
  35. ],
  36. "name" : "Name/0/10",
  37. "distance" : 1111949.2664455872
  38. },
  39. {
  40. "_id" : "geo/3690",
  41. "_key" : "3690",
  42. "_rev" : "_ZJNShDm--C",
  43. "loc" : [
  44. -10,
  45. 0
  46. ],
  47. "name" : "Name/-10/0",
  48. "distance" : 1111949.2664455872
  49. },
  50. {
  51. "_id" : "geo/3762",
  52. "_key" : "3762",
  53. "_rev" : "_ZJNShEK--A",
  54. "loc" : [
  55. 0,
  56. -10
  57. ],
  58. "name" : "Name/0/-10",
  59. "distance" : 1111949.2664455872
  60. },
  61. {
  62. "_id" : "geo/3688",
  63. "_key" : "3688",
  64. "_rev" : "_ZJNShDm--A",
  65. "loc" : [
  66. -10,
  67. -10
  68. ],
  69. "name" : "Name/-10/-10",
  70. "distance" : 1568520.556798576
  71. },
  72. {
  73. "_id" : "geo/3840",
  74. "_key" : "3840",
  75. "_rev" : "_ZJNShEu--E",
  76. "loc" : [
  77. 10,
  78. 10
  79. ],
  80. "name" : "Name/10/10",
  81. "distance" : 1568520.556798576
  82. },
  83. {
  84. "_id" : "geo/3836",
  85. "_key" : "3836",
  86. "_rev" : "_ZJNShEu--A",
  87. "loc" : [
  88. 10,
  89. -10
  90. ],
  91. "name" : "Name/10/-10",
  92. "distance" : 1568520.556798576
  93. },
  94. {
  95. "_id" : "geo/3692",
  96. "_key" : "3692",
  97. "_rev" : "_ZJNShDm--E",
  98. "loc" : [
  99. -10,
  100. 10
  101. ],
  102. "name" : "Name/-10/10",
  103. "distance" : 1568520.556798576
  104. }
  105. ]

Hide execution results

  1. arangosh> for (var i = -90; i <= 90; i += 10) {
  2. ........> for (var j = -180; j <= 180; j += 10) {
  3. ........> db.geo.save({ name : "Name/" + i + "/" + j, loc: [ i, j ] }); } }
  4. arangosh> db.geo.within(0, 0, 2000 * 1000).distance().toArray();

Show execution results

ensures that a geo index existscollection.ensureIndex({ type: "geo", fields: [ "location" ] })

Since ArangoDB 2.5, this method is an alias for ensureGeoIndex sincegeo indexes are always sparse, meaning that documents that do not containthe index attributes or has non-numeric values in the index attributeswill not be indexed. ensureGeoConstraint is deprecated and _ensureGeoIndex_should be used instead.

The index does not provide a unique option because of its limited usability.It would prevent identical coordinates from being inserted only, but even aslightly different location (like 1 inch or 1 cm off) would be unique again andnot considered a duplicate, although it probably should. The desired thresholdfor detecting duplicates may vary for every project (including how to calculatethe distance even) and needs to be implemented on the application layer asneeded. You can write a Foxx service for this purpose andmake use of the AQL geo functions to find nearbycoordinates supported by a geo index.