Geospatial Search with ArangoSearch
ArangoSearch supports geospatial queries like finding locations and GeoJSON shapes within a radius or area
ArangoSearch can accelerate various types of geospatial queries for data that is indexed by a View. The regular geospatial index can do most of this too, but ArangoSearch allows you to combine geospatial requests with other kinds of searches, like full-text search.
Creating geospatial Analyzers
Geospatial data that can be indexed:
-
GeoJSON features such as Points and Polygons (with coordinates in
[longitude, latitude]
order), for example:{ "location": { "type": "Point", "coordinates": [ -73.983, 40.764 ] } }
-
Coordinates using an array with two numbers in
[longitude, latitude]
order, for example:{ "location": [ -73.983, 40.764 ] }
-
Coordinates using an array with two numbers in
[latitude, longitude]
order, for example:{ "location": [ 40.764, -73.983 ] }
-
Coordinates using two separate numeric attributes, for example:
{ "location": { "lat": 40.764, "lng": -73.983 } }
You need to create Geo Analyzers manually. There are no pre-configured (built-in) Geo Analyzers.
-
The data needs to be pre-processed with a
geojson
orgeo_s2
Analyzer in case of GeoJSON or coordinate arrays in[longitude, latitude]
order. -
For coordinate arrays in
[latitude, longitude]
order or coordinate pairs using separate attributes, you need to use ageopoint
Analyzer.
Custom Analyzers:
Create a geojson
Analyzer in arangosh to pre-process arbitrary
GeoJSON features or [longitude, latitude]
arrays.
The default properties are usually what you want, therefore an empty object
is passed:
//db._useDatabase("your_database"); // Analyzer will be created in current database
var analyzers = require("@arangodb/analyzers");
analyzers.save("geojson", "geojson", {}, ["frequency", "norm", "position"]);
See geojson
Analyzer for details.
In the Enterprise Edition, you can use the geo_s2
Analyzer instead of the geojson
Analyzer to more efficiently index geo-spatial data. It is mostly a drop-in replacement, but you can choose between different binary formats. See Analyzers for details.
Create a geopoint
Analyzer in arangosh using the default properties
(empty object) to pre-process coordinate arrays in [latitude, longitude]
order:
//db._useDatabase("your_database"); // Analyzer will be created in current database
var analyzers = require("@arangodb/analyzers");
analyzers.save("geo_pair", "geopoint", {}, ["frequency", "norm", "position"]);
Create a geopoint
Analyzer in arangosh to pre-process coordinates with
latitude and longitude stored in two different attributes. These attributes
cannot be at the top-level of the document, but must be nested in an object,
e.g. { location: { lat: 40.78, lon: -73.97 } }
. The path relative to the
parent attribute (here: location
) needs to be described in the Analyzer
properties for each of the coordinate attributes:
//db._useDatabase("your_database"); // Analyzer will be created in current database
var analyzers = require("@arangodb/analyzers");
analyzers.save("geo_latlng", "geopoint", { latitude: ["lat"], longitude: ["lng"] }, ["frequency", "norm", "position"]);
Using the example dataset
Load the dataset into an ArangoDB instance and create a View restaurantsViews
as described below:
Dataset: Demo Geo S2 dataset
View definition
search-alias
View
db.restaurants.ensureIndex({ name: "inv-rest", type: "inverted", fields: [ { name: "location", analyzer: "geojson" } ] });
db.neighborhoods.ensureIndex({ name: "inv-hood", type: "inverted", fields: [ "name", { name: "geometry", analyzer: "geojson" } ] });
db._createView("restaurantsViewAlias", "search-alias", { indexes: [
{ collection: "restaurants", index: "inv-rest" },
{ collection: "neighborhoods", index: "inv-hood" }
] });
arangosearch
View
{
"links": {
"restaurants": {
"fields": {
"location": {
"analyzers": [
"geojson"
]
}
}
}
},
"neighborhoods": {
"fields": {
"name": {
"analyzers": [
"identity"
]
},
"geometry": {
"analyzers": [
"geojson"
]
}
}
}
}
Search for points within a radius
Using the Museum of Modern Arts as reference location, find restaurants within a 100 meter radius. Return the matches sorted by distance and include how far away they are from the reference point in the result.
search-alias
View:
LET moma = GEO_POINT(-73.983, 40.764)
FOR doc IN restaurantsViewAlias
SEARCH GEO_DISTANCE(doc.location, moma) < 100
LET distance = GEO_DISTANCE(doc.location, moma)
SORT distance
RETURN {
geometry: doc.location,
distance
}
arangosearch
View:
LET moma = GEO_POINT(-73.983, 40.764)
FOR doc IN restaurantsView
SEARCH ANALYZER(GEO_DISTANCE(doc.location, moma) < 100, "geojson")
LET distance = GEO_DISTANCE(doc.location, moma)
SORT distance
RETURN {
geometry: doc.location,
distance
}
Search for restaurants with Cafe
in their name within a radius of 1000 meters
and return the ten closest matches:
search-alias
View:
LET moma = GEO_POINT(-73.983, 40.764)
FOR doc IN restaurantsViewAlias
SEARCH LIKE(doc.name, "%Cafe%")
AND GEO_DISTANCE(doc.location, moma) < 1000
LET distance = GEO_DISTANCE(doc.location, moma)
SORT distance
LIMIT 10
RETURN {
geometry: doc.location,
name: doc.name,
distance
}
arangosearch
View:
LET moma = GEO_POINT(-73.983, 40.764)
FOR doc IN restaurantsView
SEARCH LIKE(doc.name, "%Cafe%")
AND ANALYZER(GEO_DISTANCE(doc.location, moma) < 1000, "geojson")
LET distance = GEO_DISTANCE(doc.location, moma)
SORT distance
LIMIT 10
RETURN {
geometry: doc.location,
name: doc.name,
distance
}
Search for points within a polygon
First off, search for the neighborhood Upper West Side
in a subquery and
return its GeoJSON Polygon. Then search for restaurants that are contained
in this polygon and return them together with the polygon itself:
search-alias
View:
LET upperWestSide = FIRST(
FOR doc IN restaurantsViewAlias
SEARCH doc.name == "Upper West Side"
RETURN doc.geometry
)
FOR result IN PUSH(
FOR doc IN restaurantsViewAlias
SEARCH GEO_CONTAINS(upperWestSide, doc.location)
RETURN doc.location,
upperWestSide
)
RETURN result
arangosearch
View:
LET upperWestSide = FIRST(
FOR doc IN restaurantsView
SEARCH doc.name == "Upper West Side"
RETURN doc.geometry
)
FOR result IN PUSH(
FOR doc IN restaurantsView
SEARCH ANALYZER(GEO_CONTAINS(upperWestSide, doc.location), "geojson")
RETURN doc.location,
upperWestSide
)
RETURN result
You do not have to look up the polygon, you can also provide one inline. It is also not necessary to return the polygon, you can return the matches only:
LET upperWestSide = {
"coordinates": [
[
[-73.9600301843709, 40.79803810789689], [-73.96052271669541, 40.797368469462334],
[-73.96097971807933, 40.79673864404529], [-73.96144060655736, 40.79611082718394],
[-73.96189985460951, 40.79547927006112], [-73.96235980150668, 40.79485206056065],
[-73.96280590635729, 40.79423581323211], [-73.96371096541819, 40.79301293488322],
[-73.9641759852132, 40.79236204502772], [-73.96468540739478, 40.79166402679883],
[-73.96517705499011, 40.79099034109932], [-73.96562799538655, 40.790366117129004],
[-73.96609500572444, 40.78973438976665], [-73.96655226678917, 40.78910715282553],
[-73.96700977073398, 40.78847679074218], [-73.96744908373155, 40.78786072059045],
[-73.96792696354466, 40.78722157112602], [-73.96838479313664, 40.78659569652393],
[-73.96884378957469, 40.78596738856434], [-73.96933573318945, 40.78529327955705],
[-73.96983225556819, 40.7846109105862], [-73.97030068162124, 40.78397541394847],
[-73.97076013116715, 40.783340137553594], [-73.97122292220932, 40.782706256089995],
[-73.9717230555586, 40.78202147595964], [-73.97357117423289, 40.7794778616211],
[-73.97406668257638, 40.77880541672153], [-73.97453231422314, 40.77816778452296],
[-73.97499744020544, 40.777532546222], [-73.97783054404911, 40.77872973181589],
[-73.98067365344895, 40.7799251824873], [-73.98140948736065, 40.780235418619405],
[-73.98151911347311, 40.78028175751621], [-73.9816278736105, 40.780328934969766],
[-73.98232616371553, 40.78062377270337], [-73.9835260146705, 40.781130011022704],
[-73.98507184345014, 40.781779680969194], [-73.98536952677372, 40.781078372362586],
[-73.98567936117642, 40.78031263333493], [-73.98654378951805, 40.780657980791055],
[-73.98707137465644, 40.78090638159226], [-73.98730772854313, 40.781041303287786],
[-73.98736363983177, 40.78106280511045], [-73.98741432690473, 40.7810875110951],
[-73.98746219857024, 40.7811086095956], [-73.98799363156404, 40.78134281734761],
[-73.98812746102577, 40.78140179644223], [-73.98804128806725, 40.78158596085119],
[-73.9881002938246, 40.78160287830527], [-73.98807644914505, 40.78165093500162],
[-73.98801805997222, 40.78163418881042], [-73.98796079284213, 40.781770987031514],
[-73.98791968459247, 40.78183347771321], [-73.98787728725019, 40.78189205083216],
[-73.98647480368592, 40.783916573718706], [-73.98625187003543, 40.78423876424543],
[-73.98611372725294, 40.78443891187735], [-73.98561580396368, 40.78514186259503],
[-73.98546581197026, 40.78536070057543], [-73.98617270496544, 40.786068452258675],
[-73.98645586240198, 40.7859192190814], [-73.98707234561569, 40.78518963831753],
[-73.98711901394266, 40.78521031850151], [-73.98649778102359, 40.78595120288725],
[-73.98616462880626, 40.786121882448306], [-73.98612842248588, 40.78623900133112],
[-73.98607113521973, 40.78624070602659], [-73.98602727478911, 40.78622896423671],
[-73.98609763784941, 40.786058225697936], [-73.98542932126942, 40.78541394218462],
[-73.98508113773205, 40.785921935110444], [-73.98519883325449, 40.785966552197756],
[-73.98517050238989, 40.786013334158156], [-73.98521621867376, 40.786030501313824],
[-73.98525509797992, 40.78597620551157], [-73.98524273937655, 40.78597257215073],
[-73.98524962933016, 40.78596313985583], [-73.98528177918672, 40.785978620950054],
[-73.9852400328845, 40.786035858136785], [-73.98568388524215, 40.78622212391968],
[-73.98571752900456, 40.78617599466878], [-73.98576566029752, 40.786196274858625],
[-73.9856828719225, 40.78630978621313], [-73.98563627093053, 40.786290150146684],
[-73.98567072256468, 40.786242911993796], [-73.98561523764435, 40.78621964571528],
[-73.98520511880037, 40.78604766921276], [-73.98521103560748, 40.78603955488367],
[-73.98516263994709, 40.78602099926717], [-73.98513163631205, 40.786060297019965],
[-73.98501696406497, 40.78601423719563], [-73.98493597820354, 40.786130720650974],
[-73.98465507883022, 40.78653474180794], [-73.98574378790113, 40.78657008235215],
[-73.98589227228327, 40.78642652901958], [-73.98594285499497, 40.78645284788032],
[-73.98594956155667, 40.786487113463934], [-73.98581237352651, 40.78661686535709],
[-73.98513520970327, 40.7865876183929], [-73.98461942858408, 40.78658601634982],
[-73.98369521623664, 40.7879152813127], [-73.98234664147564, 40.789854780772636],
[-73.98188645946546, 40.79051658043251], [-73.98139174468567, 40.79122824550256],
[-73.9812893737095, 40.79137550943302], [-73.9809470835408, 40.79186789327993],
[-73.980537679464, 40.79245681262498], [-73.98043434256003, 40.79259428309673],
[-73.98013222578662, 40.79299376538315], [-73.98004684398002, 40.79311352516391],
[-73.9792882208298, 40.79417763216331], [-73.97828949152755, 40.79558676046088],
[-73.97779475503205, 40.79628462977189], [-73.97685207845194, 40.79763134839318],
[-73.97639951930574, 40.79827321018994], [-73.97628527884252, 40.798435235410054],
[-73.97583055785255, 40.799092280410655], [-73.97578169321191, 40.799156256780286],
[-73.97579140130195, 40.799209627886206], [-73.97576219486481, 40.79926017354928],
[-73.97554385822018, 40.79952825063732], [-73.97526783234453, 40.79993284953172],
[-73.97508668067891, 40.800189533632995], [-73.97496436808184, 40.80036963419388],
[-73.97483924436003, 40.800558243262664], [-73.97466556722725, 40.80081351473415],
[-73.97448722520987, 40.801057428896804], [-73.97414361823468, 40.80151689534114],
[-73.97394098366709, 40.801809025361415], [-73.97389989052462, 40.80188986353119],
[-73.97377477246009, 40.802045845948555], [-73.97372060455763, 40.80216781528022],
[-73.97361322463904, 40.80229685988716], [-73.9735422772157, 40.802356411250294],
[-73.97336671067801, 40.80263011334645], [-73.97320518045738, 40.802830058276285],
[-73.97312859120993, 40.80297471550862], [-73.97307070537943, 40.8030555484474],
[-73.97303522902072, 40.803073973741895], [-73.97292317001968, 40.80324982284384],
[-73.97286807262155, 40.80332073417601], [-73.97287179081519, 40.80335618764528],
[-73.9727990775659, 40.803329159656634], [-73.9726574474597, 40.803276514162725],
[-73.97257779806121, 40.803247183771205], [-73.97250022180596, 40.80321661262814],
[-73.97150381003809, 40.80283773617443], [-73.97032589767365, 40.802384560870536],
[-73.9702740046587, 40.80235903699402], [-73.97021594793262, 40.80233585148787],
[-73.9700474216526, 40.8022650103398], [-73.9685836074654, 40.80163547026629],
[-73.96798415954912, 40.80139826627661], [-73.967873797219, 40.801351698184384],
[-73.96775900356977, 40.80130351598543], [-73.96571144280439, 40.80043806998765],
[-73.96286980146162, 40.79923967661966], [-73.96147779901374, 40.79865415643638],
[-73.9600301843709, 40.79803810789689]
]
],
"type": "Polygon"
}
FOR doc IN restaurantsView
SEARCH ANALYZER(GEO_CONTAINS(upperWestSide, doc.location), "geojson")
RETURN doc.location
/* `search-alias` View:
FOR doc IN restaurantsViewAlias
SEARCH GEO_CONTAINS(upperWestSide, doc.location)
RETURN doc.location
*/
Search for polygons within polygons
Define a GeoJSON polygon that is a rectangle, then search for neighborhoods that are fully contained in this area:
LET sides = {
left: -74,
top: 40.8,
right: -73.93,
bottom: 40.76
}
LET rect = GEO_POLYGON([
[sides.left, sides.bottom],
[sides.left, sides.top],
[sides.right, sides.top],
[sides.right, sides.bottom],
[sides.left, sides.bottom]
])
FOR result IN PUSH(
FOR doc IN restaurantsView
SEARCH ANALYZER(GEO_CONTAINS(rect, doc.geometry), "geojson")
/* `search-alias` View:
FOR doc IN restaurantsViewAlias
SEARCH GEO_CONTAINS(rect, doc.geometry)
*/
RETURN doc.geometry,
rect
)
RETURN result
Searching for geo features in a rectangle is something you can use together with an interactive map that the user can select the area of interest with. Take a look at the lunch break video about the ArangoBnB demo project to learn more.
Search for polygons intersecting polygons
Define a GeoJSON polygon that is a rectangle, then search for neighborhoods that intersect with this area:
LET sides = {
left: -74,
top: 40.8,
right: -73.93,
bottom: 40.76
}
LET rect = GEO_POLYGON([
[sides.left, sides.bottom],
[sides.left, sides.top],
[sides.right, sides.top],
[sides.right, sides.bottom],
[sides.left, sides.bottom]
])
FOR result IN PUSH(
FOR doc IN restaurantsView
SEARCH ANALYZER(GEO_INTERSECTS(rect, doc.geometry), "geojson")
/* `search-alias` View:
FOR doc IN restaurantsViewAlias
SEARCH GEO_INTERSECTS(rect, doc.geometry)
*/
RETURN doc.geometry,
rect
)
RETURN result