Geospatial index and query
For the complete documentation index see: llms.txt
All documentation pages available in markdown.
Use Aerospike geospatial storage and indexing to enable fast queries on points within a region, on a region containing points, and points within a radius.
Underlying technologies
The Aerospike geospatial feature relies on these technologies:
- GeoJSON format to specify and store GeoJSON geometry objects, and achieve data standardization and interoperability. See the GeoJSON Format Specification and GeoJSON IETF WG.
- The Aerospike AeroCircle data type extends the GeoJSON format to store circles as well as polygons.
- S2 Spherical Geometry Library to map points and regions in single-dimension, 64-bit CellID representation.
- Aerospike secondary indexes and queries to achieve performance and scale for index inserts/updates and queries.
Use cases
- Vehicle tracking systems that require high-throughput updates of vehicle location and frequently query vehicles within a region.
- A mapping application could find different amenities within a certain distance of a given location.
- Location-targeted bidding transactions to discover persons or devices within the location with an active ad campaign.
See these Aerospike examples.
Geospatial data
Aerospike supports the GeoJSON geospatial data type. All geospatial functionality (indexing and querying) only execute on GeoJSON data types.
GeoJSON data incurs this additional processing on data reads:
- GeoJSON text is parsed for validity and support (see GeoJSON Parsing).
- GeoJSON text is converted into S2 CellID coverings.
- Aerospike saves both the covering CellIDs and the original GeoJSON in the database.
- Only GeoJSON data is accessible to the application through the client APIs and the UDF subsystem.
Store GeoJSON data
Store GeoJSON Point and Polygon objects in bins using each client’s GeoJSON data type.
Key key = DataSet.of("test", "geo").id("loc1");String point = "{\"type\": \"Point\", \"coordinates\": [-122.0862, 37.4220]}";session.upsert(key) .bin("name").setTo("Googleplex") .bin("loc_bin").setToGeoJson(point) .execute();key = DataSet.of("test", "geo").id("loc1")point = '{"type": "Point", "coordinates": [-122.0862, 37.4220]}'( session.upsert(key) .bin("name").set_to("Googleplex") .bin("loc_bin").set_to_geo_json(point) .execute())let key = as_key!("test", "geo", "loc1");let point = r#"{"type": "Point", "coordinates": [-122.0862, 37.4220]}"#;client.put(&WritePolicy::default(), &key, &[ as_bin!("name", "Googleplex"), as_bin!("loc_bin", as_geo!(point)),]).await?;Key key = new Key("test", "geo", "loc1");string point = "{\"type\": \"Point\", \"coordinates\": [-122.0862, 37.4220]}";client.Put(null, key, new Bin("name", "Googleplex"), Bin.AsGeoJSON("loc_bin", point));// Requires: import as "github.com/aerospike/aerospike-client-go/v8"key, _ := as.NewKey("test", "geo", "loc1")point := as.NewGeoJSONValue( `{"type": "Point", "coordinates": [-122.0862, 37.4220]}`)err := client.PutBins(nil, key, as.NewBin("name", "Googleplex"), as.NewBin("loc_bin", point))if err != nil { log.Fatal(err)}const key = new Aerospike.Key("test", "geo", "loc1");const point = new Aerospike.GeoJSON({ type: "Point", coordinates: [-122.0862, 37.4220],});await client.put(key, { name: "Googleplex", loc_bin: point });as_key key;as_key_init_str(&key, "test", "geo", "loc1");
as_record rec;as_record_inita(&rec, 2);as_record_set_str(&rec, "name", "Googleplex");as_record_set_geojson_strp(&rec, "loc_bin", "{\"type\": \"Point\", \"coordinates\": [-122.0862, 37.4220]}", false);
aerospike_key_put(&as, &err, NULL, &key, &rec);as_record_destroy(&rec);Key key = new Key("test", "geo", "loc1");String point = "{\"type\": \"Point\", \"coordinates\": [-122.0862, 37.4220]}";client.put(null, key, new Bin("name", "Googleplex"), Bin.asGeoJSON("loc_bin", point));key = ("test", "geo", "loc1")loc = aerospike.GeoJSON({ "type": "Point", "coordinates": [-122.0862, 37.4220]})client.put(key, {"name": "Googleplex", "loc_bin": loc})Geospatial index
In addition to integer and string indexes, Aerospike supports GEO2DSPHERE secondary indexes. With this index type, Aerospike can efficiently parse, index, and query GeoJSON Point, Polygon, MultiPolygon, and AeroCircle objects. Aerospike maps these shapes onto Google’s S2 spherical geometry library internally, providing the ultra-low latency required for high-throughput proximity searches and real-time location tracking.
Use asadm to create and manage secondary indexes in an Aerospike cluster. For instructions, see Secondary Index (SI) Query.
The following command creates a secondary index called geo-index using geo2dsphere data on the namespace test using the set name geo-set and the bin geo-bin.
Admin+> manage sindex create geo2dsphere geo-index ns test set geo-set bin geo-binIndexes can also be created programmatically:
session.createIndex(DataSet.of("test", "geo"), "geo-loc-idx", "loc_bin", IndexType.GEO2DSPHERE, IndexCollectionType.DEFAULT) .waitTillComplete();( session.index("test", "geo") .on_bin("loc_bin") .named("geo-loc-idx") .geo2dsphere() .create() .wait_till_complete_blocking())use aerospike::Task;use std::time::Duration;
let task = client .create_index_on_bin(&AdminPolicy::default(), "test", "geo", "loc_bin", "geo-loc-idx", IndexType::Geo2DSphere, CollectionIndexType::Default, None) .await?;task.wait_till_complete(Some(Duration::from_secs(30))).await?;IndexTask task = client.CreateIndex(null, "test", "geo", "geo-loc-idx", "loc_bin", IndexType.GEO2DSPHERE);task.Wait();// Requires: import as "github.com/aerospike/aerospike-client-go/v8"task, err := client.CreateIndex(nil, "test", "geo", "geo-loc-idx", "loc_bin", as.GEO2DSPHERE)if err != nil { log.Fatal(err)}<-task.OnComplete()const job = await client.createIndex({ ns: "test", set: "geo", bin: "loc_bin", index: "geo-loc-idx", datatype: Aerospike.indexDataType.GEO2DSPHERE,});await job.wait();as_index_task task;aerospike_index_create(&as, &err, &task, NULL, "test", "geo", "loc_bin", "geo-loc-idx", AS_INDEX_GEO2DSPHERE);aerospike_index_create_wait(&err, &task, 0);IndexTask task = client.createIndex(null, "test", "geo", "geo-loc-idx", "loc_bin", IndexType.GEO2DSPHERE);task.waitTillComplete();client.index_single_value_create( "test", "geo", "loc_bin", aerospike.INDEX_GEO2DSPHERE, "geo-loc-idx")Geospatial query
Aerospike supports two Geospatial queries:
- Points exist within a region (including circle)
- Region contains point
The following examples use these sample records, each containing a GeoJSON Point in loc_bin and a string in amenity:
{ "name": "Googleplex", "loc_bin": {"type": "Point", "coordinates": [-122.0862, 37.4220]}, "amenity": "campus" }{ "name": "Ferry Building", "loc_bin": {"type": "Point", "coordinates": [-122.3936, 37.7956]}, "amenity": "cafe" }{ "name": "UC Berkeley", "loc_bin": {"type": "Point", "coordinates": [-122.2727, 37.8716]}, "amenity": "cafe" }{ "name": "NYC Times Sq", "loc_bin": {"type": "Point", "coordinates": [-73.9857, 40.7580]}, "amenity": "cafe" }Points-within-region query
Find all points that fall within a given polygon. With the geo-loc-idx index on loc_bin, querying a Bay Area polygon returns the three California points but not New York.
String region = "{\"type\": \"Polygon\", \"coordinates\": [[" + "[-122.500, 37.000], [-121.000, 37.000], " + "[-121.000, 38.080], [-122.500, 38.080], " + "[-122.500, 37.000]]]}";try (RecordStream rs = session.query(DataSet.of("test", "geo")) .where("geoCompare($.loc_bin, geoJson('" + region + "'))") .execute()) { rs.forEach(result -> System.out.println(result.recordOrThrow().getString("name")));}region = ( '{"type": "Polygon", "coordinates": [[' '[-122.500, 37.000], [-121.000, 37.000], ' '[-121.000, 38.080], [-122.500, 38.080], ' '[-122.500, 37.000]]]}')
stream = ( session.query(DataSet.of("test", "geo")) .where(f"geoCompare($.loc_bin, geoJson('{region}'))") .execute())for row in stream: print(row.record_or_raise().bins["name"])stream.close()use aerospike::query::{Filter, PartitionFilter};use aerospike::{Bins, QueryPolicy, Statement};use futures::StreamExt;
let region = concat!( r#"{"type": "Polygon", "coordinates": [["#, "[-122.500, 37.000], [-121.000, 37.000], ", "[-121.000, 38.080], [-122.500, 38.080], ", "[-122.500, 37.000]]]}",);
let mut stmt = Statement::new("test", "geo", Bins::All);stmt.add_filter(Filter::geo_within_region("loc_bin", region));
let qp = QueryPolicy::default();let rs = client.query(&qp, PartitionFilter::all(), stmt).await?;let mut stream = rs.into_stream();while let Some(record) = stream.next().await { println!("{}", record?.bins["name"]);}string region = "{\"type\": \"Polygon\", \"coordinates\": [[" + "[-122.500, 37.000], [-121.000, 37.000], " + "[-121.000, 38.080], [-122.500, 38.080], " + "[-122.500, 37.000]]]}";
Statement stmt = new();stmt.SetNamespace("test");stmt.SetSetName("geo");stmt.SetFilter(Filter.GeoWithinRegion("loc_bin", region));
RecordSet rs = client.Query(null, stmt);while (rs.Next()){ Console.WriteLine(rs.Record.GetString("name"));}rs.Close();// Requires: import as "github.com/aerospike/aerospike-client-go/v8"region := `{"type": "Polygon", "coordinates": [[` + `[-122.500, 37.000], [-121.000, 37.000], ` + `[-121.000, 38.080], [-122.500, 38.080], ` + `[-122.500, 37.000]]]}`
stmt := as.NewStatement("test", "geo")stmt.SetFilter(as.NewGeoWithinRegionFilter("loc_bin", region))
rs, err := client.Query(nil, stmt)if err != nil { log.Fatal(err)}for rec := range rs.Results() { if rec.Err != nil { log.Fatal(rec.Err) } fmt.Println(rec.Record.Bins["name"])}const region = new Aerospike.GeoJSON({ type: "Polygon", coordinates: [[ [-122.500, 37.000], [-121.000, 37.000], [-121.000, 38.080], [-122.500, 38.080], [-122.500, 37.000], ]],});
const query = client.query("test", "geo");query.where(Aerospike.filter.geoWithinGeoJSONRegion("loc_bin", region));
const results = await query.results();for (const rec of results) { console.log(rec.bins.name);}// Print the name of each record the query returnsstatic boolquery_cb(const as_val* val, void* udata){ if (! val) { return false; // The query is complete. } as_record* rec = as_record_fromval(val); printf("%s\n", as_record_get_str(rec, "name")); return true;}
const char* region = "{\"type\": \"Polygon\", \"coordinates\": [[" "[-122.500, 37.000], [-121.000, 37.000], " "[-121.000, 38.080], [-122.500, 38.080], " "[-122.500, 37.000]]]}";
as_query q;as_query_init(&q, "test", "geo");as_query_where_inita(&q, 1);as_query_where(&q, "loc_bin", as_geo_within(region));
aerospike_query_foreach(&as, &err, NULL, &q, query_cb, NULL);as_query_destroy(&q);String region = "{\"type\": \"Polygon\", \"coordinates\": [[" + "[-122.500, 37.000], [-121.000, 37.000], " + "[-121.000, 38.080], [-122.500, 38.080], " + "[-122.500, 37.000]]]}";
Statement stmt = new Statement();stmt.setNamespace("test");stmt.setSetName("geo");stmt.setFilter(Filter.geoWithinRegion("loc_bin", region));
RecordSet rs = client.query(null, stmt);while (rs.next()) { System.out.println(rs.getRecord().getString("name"));}rs.close();from aerospike import predicates as p
region = aerospike.GeoJSON({ "type": "Polygon", "coordinates": [[ [-122.500, 37.000], [-121.000, 37.000], [-121.000, 38.080], [-122.500, 38.080], [-122.500, 37.000] ]]})
query = client.query("test", "geo")query.where(p.geo_within_geojson_region("loc_bin", region.dumps()))
for _, _, rec in query.results(): print(rec["name"])Points-within-radius query (circle)
Find all points within a given radius (in meters) of a longitude/latitude. The client constructs an AeroCircle GeoJSON internally. This example finds points within 50 km of the Googleplex. It returns the Googleplex and the Ferry Building.
// AEL takes the circle as an AeroCircle GeoJSON object: a center and a radius in metersString circle = "{\"type\": \"AeroCircle\", \"coordinates\": [[-122.0862, 37.4220], 50000]}";try (RecordStream rs = session.query(DataSet.of("test", "geo")) .where("geoCompare($.loc_bin, geoJson('" + circle + "'))") .execute()) { rs.forEach(result -> System.out.println(result.recordOrThrow().getString("name")));}# AEL takes the circle as an AeroCircle GeoJSON object: a center and a radius in meterscircle = '{"type": "AeroCircle", "coordinates": [[-122.0862, 37.4220], 50000]}'
stream = ( session.query(DataSet.of("test", "geo")) .where(f"geoCompare($.loc_bin, geoJson('{circle}'))") .execute())for row in stream: print(row.record_or_raise().bins["name"])stream.close()use aerospike::query::{Filter, PartitionFilter};use aerospike::{Bins, QueryPolicy, Statement};use futures::StreamExt;
let mut stmt = Statement::new("test", "geo", Bins::All);stmt.add_filter(Filter::geo_within_radius("loc_bin", -122.0862, 37.4220, 50000.0));
let qp = QueryPolicy::default();let rs = client.query(&qp, PartitionFilter::all(), stmt).await?;let mut stream = rs.into_stream();while let Some(record) = stream.next().await { println!("{}", record?.bins["name"]);}Statement stmt = new();stmt.SetNamespace("test");stmt.SetSetName("geo");stmt.SetFilter(Filter.GeoWithinRadius("loc_bin", -122.0862, 37.4220, 50000));
RecordSet rs = client.Query(null, stmt);while (rs.Next()){ Console.WriteLine(rs.Record.GetString("name"));}rs.Close();// Requires: import as "github.com/aerospike/aerospike-client-go/v8"stmt := as.NewStatement("test", "geo")stmt.SetFilter(as.NewGeoWithinRadiusFilter("loc_bin", -122.0862, 37.4220, 50000))
rs, err := client.Query(nil, stmt)if err != nil { log.Fatal(err)}for rec := range rs.Results() { if rec.Err != nil { log.Fatal(rec.Err) } fmt.Println(rec.Record.Bins["name"])}const query = client.query("test", "geo");query.where(Aerospike.filter.geoWithinRadius("loc_bin", -122.0862, 37.4220, 50000));
const results = await query.results();for (const rec of results) { console.log(rec.bins.name);}char circle[256];snprintf(circle, sizeof(circle), "{\"type\": \"AeroCircle\", \"coordinates\": " "[[-122.0862, 37.4220], 50000]}");
as_query q;as_query_init(&q, "test", "geo");as_query_where_inita(&q, 1);as_query_where(&q, "loc_bin", as_geo_within(circle));
aerospike_query_foreach(&as, &err, NULL, &q, query_cb, NULL);as_query_destroy(&q);Statement stmt = new Statement();stmt.setNamespace("test");stmt.setSetName("geo");stmt.setFilter(Filter.geoWithinRadius("loc_bin", -122.0862, 37.4220, 50000));
RecordSet rs = client.query(null, stmt);while (rs.next()) { System.out.println(rs.getRecord().getString("name"));}rs.close();from aerospike import predicates as p
query = client.query("test", "geo")query.where(p.geo_within_radius("loc_bin", -122.0862, 37.4220, 50000))
for _, _, rec in query.results(): print(rec["name"])Region-contains-point query
Find all stored regions that contain a given point. This requires a geo index on the bin storing the Polygon data. Create one on rgn_bin as in Geospatial index.
Given these region records stored in rgn_bin:
{ "name": "SF Bay Area", "rgn_bin": {"type": "Polygon", "coordinates": [[[-122.500,37.000],[-121.000,37.000],[-121.000,38.080],[-122.500,38.080],[-122.500,37.000]]]} }{ "name": "Downtown SF", "rgn_bin": {"type": "Polygon", "coordinates": [[[-122.420,37.770],[-122.390,37.770],[-122.390,37.800],[-122.420,37.800],[-122.420,37.770]]]} }Query for regions containing a point in Downtown San Francisco. Both regions are returned because the point falls inside the smaller Downtown SF polygon, which is itself inside the larger Bay Area polygon.
String point = "{\"type\": \"Point\", \"coordinates\": [-122.4000, 37.7900]}";try (RecordStream rs = session.query(DataSet.of("test", "geo")) .where("geoCompare($.rgn_bin, geoJson('" + point + "'))") .execute()) { rs.forEach(result -> System.out.println(result.recordOrThrow().getString("name")));}point = '{"type": "Point", "coordinates": [-122.4000, 37.7900]}'
stream = ( session.query(DataSet.of("test", "geo")) .where(f"geoCompare($.rgn_bin, geoJson('{point}'))") .execute())for row in stream: print(row.record_or_raise().bins["name"])stream.close()use aerospike::query::{Filter, PartitionFilter};use aerospike::{Bins, QueryPolicy, Statement};use futures::StreamExt;
let point = r#"{"type": "Point", "coordinates": [-122.4000, 37.7900]}"#;
let mut stmt = Statement::new("test", "geo", Bins::All);stmt.add_filter(Filter::geo_contains("rgn_bin", point));
let qp = QueryPolicy::default();let rs = client.query(&qp, PartitionFilter::all(), stmt).await?;let mut stream = rs.into_stream();while let Some(record) = stream.next().await { println!("{}", record?.bins["name"]);}string point = "{\"type\": \"Point\", \"coordinates\": [-122.4000, 37.7900]}";
Statement stmt = new();stmt.SetNamespace("test");stmt.SetSetName("geo");stmt.SetFilter(Filter.GeoContains("rgn_bin", point));
RecordSet rs = client.Query(null, stmt);while (rs.Next()){ Console.WriteLine(rs.Record.GetString("name"));}rs.Close();// Requires: import as "github.com/aerospike/aerospike-client-go/v8"point := `{"type": "Point", "coordinates": [-122.4000, 37.7900]}`
stmt := as.NewStatement("test", "geo")stmt.SetFilter(as.NewGeoRegionsContainingPointFilter("rgn_bin", point))
rs, err := client.Query(nil, stmt)if err != nil { log.Fatal(err)}for rec := range rs.Results() { if rec.Err != nil { log.Fatal(rec.Err) } fmt.Println(rec.Record.Bins["name"])}const point = new Aerospike.GeoJSON({ type: "Point", coordinates: [-122.4000, 37.7900],});
const query = client.query("test", "geo");query.where(Aerospike.filter.geoContainsGeoJSONPoint("rgn_bin", point));
const results = await query.results();for (const rec of results) { console.log(rec.bins.name);}const char* point = "{\"type\": \"Point\", \"coordinates\": [-122.4000, 37.7900]}";
as_query q;as_query_init(&q, "test", "geo");as_query_where_inita(&q, 1);as_query_where(&q, "rgn_bin", as_geo_contains(point));
aerospike_query_foreach(&as, &err, NULL, &q, query_cb, NULL);as_query_destroy(&q);String point = "{\"type\": \"Point\", \"coordinates\": [-122.4000, 37.7900]}";
Statement stmt = new Statement();stmt.setNamespace("test");stmt.setSetName("geo");stmt.setFilter(Filter.geoContains("rgn_bin", point));
RecordSet rs = client.query(null, stmt);while (rs.next()) { System.out.println(rs.getRecord().getString("name"));}rs.close();from aerospike import predicates as p
point = aerospike.GeoJSON({ "type": "Point", "coordinates": [-122.4000, 37.7900]})
query = client.query("test", "geo")query.where(p.geo_contains_geojson_point("rgn_bin", point.dumps()))
for _, _, rec in query.results(): print(rec["name"])Query filters
To extend the capabilities of geospatial queries, apply a filter expression to narrow down results. This example finds points within a 50 km radius that also have an amenity bin equal to "cafe". It returns the Ferry Building.
String circle = "{\"type\": \"AeroCircle\", \"coordinates\": [[-122.0862, 37.4220], 50000]}";// The index serves the geoCompare, and the rest of the where filters its recordstry (RecordStream rs = session.query(DataSet.of("test", "geo")) .where("geoCompare($.loc_bin, geoJson('" + circle + "'))" + " and $.amenity == 'cafe'") .execute()) { rs.forEach(result -> System.out.println(result.recordOrThrow().getString("name")));}circle = '{"type": "AeroCircle", "coordinates": [[-122.0862, 37.4220], 50000]}'
# The index serves the geoCompare, and the rest of the where filters its recordsstream = ( session.query(DataSet.of("test", "geo")) .where(f"geoCompare($.loc_bin, geoJson('{circle}'))" " and $.amenity == 'cafe'") .execute())for row in stream: print(row.record_or_raise().bins["name"])stream.close()use aerospike::expressions::{eq, string_bin, string_val};use aerospike::query::{Filter, PartitionFilter};use aerospike::{Bins, QueryPolicy, Statement};use futures::StreamExt;
let mut stmt = Statement::new("test", "geo", Bins::All);stmt.add_filter(Filter::geo_within_radius("loc_bin", -122.0862, 37.4220, 50000.0));
let mut qp = QueryPolicy::default();qp.base_policy.filter_expression = Some(eq( string_bin("amenity".to_string()), string_val("cafe".to_string()),));let rs = client.query(&qp, PartitionFilter::all(), stmt).await?;let mut stream = rs.into_stream();while let Some(record) = stream.next().await { println!("{}", record?.bins["name"]);}Statement stmt = new();stmt.SetNamespace("test");stmt.SetSetName("geo");stmt.SetFilter(Filter.GeoWithinRadius("loc_bin", -122.0862, 37.4220, 50000));
QueryPolicy queryPolicy = new(){ filterExp = Exp.Build( Exp.EQ(Exp.StringBin("amenity"), Exp.Val("cafe")))};
RecordSet rs = client.Query(queryPolicy, stmt);while (rs.Next()){ Console.WriteLine(rs.Record.GetString("name"));}rs.Close();// Requires: import as "github.com/aerospike/aerospike-client-go/v8"stmt := as.NewStatement("test", "geo")stmt.SetFilter(as.NewGeoWithinRadiusFilter("loc_bin", -122.0862, 37.4220, 50000))
qp := as.NewQueryPolicy()qp.FilterExpression = as.ExpEq( as.ExpStringBin("amenity"), as.ExpStringVal("cafe"),)
rs, err := client.Query(qp, stmt)if err != nil { log.Fatal(err)}for rec := range rs.Results() { if rec.Err != nil { log.Fatal(rec.Err) } fmt.Println(rec.Record.Bins["name"])}const query = client.query("test", "geo");query.where(Aerospike.filter.geoWithinRadius("loc_bin", -122.0862, 37.4220, 50000));
const exp = Aerospike.exp;const queryPolicy = new Aerospike.QueryPolicy({ filterExpression: exp.eq(exp.binStr("amenity"), exp.str("cafe")),});
const results = await query.results(queryPolicy);for (const rec of results) { console.log(rec.bins.name);}char circle[256];snprintf(circle, sizeof(circle), "{\"type\": \"AeroCircle\", \"coordinates\": " "[[-122.0862, 37.4220], 50000]}");
as_query q;as_query_init(&q, "test", "geo");as_query_where_inita(&q, 1);as_query_where(&q, "loc_bin", as_geo_within(circle));
as_exp_build(filter, as_exp_cmp_eq(as_exp_bin_str("amenity"), as_exp_str("cafe")));
as_policy_query qp;as_policy_query_init(&qp);qp.base.filter_exp = filter;
aerospike_query_foreach(&as, &err, &qp, &q, query_cb, NULL);as_exp_destroy(filter);as_query_destroy(&q);Statement stmt = new Statement();stmt.setNamespace("test");stmt.setSetName("geo");stmt.setFilter(Filter.geoWithinRadius("loc_bin", -122.0862, 37.4220, 50000));
QueryPolicy queryPolicy = new QueryPolicy();queryPolicy.filterExp = Exp.build( Exp.eq(Exp.stringBin("amenity"), Exp.val("cafe")));
RecordSet rs = client.query(queryPolicy, stmt);while (rs.next()) { System.out.println(rs.getRecord().getString("name"));}rs.close();from aerospike import predicates as pfrom aerospike_helpers import expressions as exp
query = client.query("test", "geo")query.where(p.geo_within_radius("loc_bin", -122.0862, 37.4220, 50000))
expr = exp.Eq(exp.StrBin("amenity"), exp.Val("cafe")).compile()policy = {"expressions": expr}
for _, _, rec in query.results(policy): print(rec["name"])Index on list/map
You can index and query GeoJSON elements stored inside list or map bins.
This example creates a geo index on list elements and queries for routes with stops inside a region. A route can be returned once for each of its stops inside the region (see Known limitations).
from aerospike_async import CollectionIndexType, Filter
( session.index("test", "geo") .on_bin("stops") .named("geo-points-idx") .geo2dsphere() .collection(CollectionIndexType.LIST) .create() .wait_till_complete_blocking())
region = ( '{"type": "Polygon", "coordinates": [[' '[-122.500, 37.000], [-121.000, 37.000], ' '[-121.000, 38.080], [-122.500, 38.080], ' '[-122.500, 37.000]]]}')
# An AEL where can't reach an index on List elements, so name the bin and collection type in# an index filterstream = ( session.query(DataSet.of("test", "geo")) .filter(Filter.within_region("stops", region, CollectionIndexType.LIST)) .execute())for row in stream: print(row.record_or_raise().bins["name"])stream.close()use aerospike::query::{Filter, PartitionFilter};use aerospike::{Bins, QueryPolicy, Statement};use futures::StreamExt;use aerospike::Task;use std::time::Duration;
let task = client .create_index_on_bin(&AdminPolicy::default(), "test", "geo", "stops", "geo-points-idx", IndexType::Geo2DSphere, CollectionIndexType::List, None) .await?;task.wait_till_complete(Some(Duration::from_secs(30))).await?;
let region = concat!( r#"{"type": "Polygon", "coordinates": [["#, "[-122.500, 37.000], [-121.000, 37.000], ", "[-121.000, 38.080], [-122.500, 38.080], ", "[-122.500, 37.000]]]}",);
let mut stmt = Statement::new("test", "geo", Bins::All);stmt.add_filter(Filter::geo_within_region_cit("stops", region, CollectionIndexType::List));
let rs = client.query(&QueryPolicy::default(), PartitionFilter::all(), stmt).await?;let mut stream = rs.into_stream();while let Some(record) = stream.next().await { println!("{}", record?.bins["name"]);}IndexTask task = client.CreateIndex(null, "test", "geo", "geo-points-idx", "stops", IndexType.GEO2DSPHERE, IndexCollectionType.LIST);task.Wait();
string region = "{\"type\": \"Polygon\", \"coordinates\": [[" + "[-122.500, 37.000], [-121.000, 37.000], " + "[-121.000, 38.080], [-122.500, 38.080], " + "[-122.500, 37.000]]]}";
Statement stmt = new();stmt.SetNamespace("test");stmt.SetSetName("geo");stmt.SetFilter(Filter.GeoWithinRegion("stops", IndexCollectionType.LIST, region));
RecordSet rs = client.Query(null, stmt);while (rs.Next()){ Console.WriteLine(rs.Record.GetString("name"));}rs.Close();// Requires: import as "github.com/aerospike/aerospike-client-go/v8"task, err := client.CreateComplexIndex(nil, "test", "geo", "geo-points-idx", "stops", as.GEO2DSPHERE, as.ICT_LIST)if err != nil { log.Fatal(err)}<-task.OnComplete()
region := `{"type": "Polygon", "coordinates": [[` + `[-122.500, 37.000], [-121.000, 37.000], ` + `[-121.000, 38.080], [-122.500, 38.080], ` + `[-122.500, 37.000]]]}`
stmt := as.NewStatement("test", "geo")stmt.SetFilter(as.NewGeoWithinRegionForCollectionFilter( "stops", as.ICT_LIST, region))
rs, err := client.Query(nil, stmt)if err != nil { log.Fatal(err)}for rec := range rs.Results() { if rec.Err != nil { log.Fatal(rec.Err) } fmt.Println(rec.Record.Bins["name"])}const job = await client.createIndex({ ns: "test", set: "geo", bin: "stops", index: "geo-points-idx", datatype: Aerospike.indexDataType.GEO2DSPHERE, type: Aerospike.indexType.LIST,});await job.wait();
const region = new Aerospike.GeoJSON({ type: "Polygon", coordinates: [[ [-122.500, 37.000], [-121.000, 37.000], [-121.000, 38.080], [-122.500, 38.080], [-122.500, 37.000], ]],});
const query = client.query("test", "geo");query.where(Aerospike.filter.geoWithinGeoJSONRegion( "stops", region, Aerospike.indexType.LIST));
const results = await query.results();for (const rec of results) { console.log(rec.bins.name);}as_index_task task;aerospike_index_create_complex(&as, &err, &task, NULL, "test", "geo", "stops", "geo-points-idx", AS_INDEX_TYPE_LIST, AS_INDEX_GEO2DSPHERE);aerospike_index_create_wait(&err, &task, 0);
const char* region = "{\"type\": \"Polygon\", \"coordinates\": [[" "[-122.500, 37.000], [-121.000, 37.000], " "[-121.000, 38.080], [-122.500, 38.080], " "[-122.500, 37.000]]]}";
as_query q;as_query_init(&q, "test", "geo");as_query_where_inita(&q, 1);as_query_where(&q, "stops", AS_PREDICATE_RANGE, AS_INDEX_TYPE_LIST, AS_INDEX_GEO2DSPHERE, region);
aerospike_query_foreach(&as, &err, NULL, &q, query_cb, NULL);as_query_destroy(&q);IndexTask task = client.createIndex(null, "test", "geo", "geo-points-idx", "stops", IndexType.GEO2DSPHERE, IndexCollectionType.LIST);task.waitTillComplete();
String region = "{\"type\": \"Polygon\", \"coordinates\": [[" + "[-122.500, 37.000], [-121.000, 37.000], " + "[-121.000, 38.080], [-122.500, 38.080], " + "[-122.500, 37.000]]]}";
Statement stmt = new Statement();stmt.setNamespace("test");stmt.setSetName("geo");stmt.setFilter(Filter.geoWithinRegion("stops", IndexCollectionType.LIST, region));
RecordSet rs = client.query(null, stmt);while (rs.next()) { System.out.println(rs.getRecord().getString("name"));}rs.close();from aerospike import predicates as p
client.index_list_create( "test", "geo", "stops", aerospike.INDEX_GEO2DSPHERE, "geo-points-idx")
region = aerospike.GeoJSON({ "type": "Polygon", "coordinates": [[ [-122.500, 37.000], [-121.000, 37.000], [-121.000, 38.080], [-122.500, 38.080], [-122.500, 37.000] ]]})
query = client.query("test", "geo")query.where(p.geo_within_geojson_region( "stops", region.dumps(), aerospike.INDEX_TYPE_LIST))
for _, _, rec in query.results(): print(rec["name"])Aerospike GeoJSON extension
Use the Aerospike AeroCircle geometry object to store circles along with regular polygons.
This example specifies a circle with a radius of 300 meters at longitude/latitude -122.250629, 37.871022.
{"type": "AeroCircle", "coordinates": [[-122.250629, 37.871022], 300]}GeoJSON parsing
On data insert/update, Aerospike only recognizes Point, Polygon, MultiPolygon, and AeroCircle GeoJSON geometry objects, which are indexable objects. Unsupported GeoJSON objects return an AEROSPIKE_ERR_GEO_INVALID_GEOJSON result code 160 (for example, LineString or MultiLineString fail on insert). Holes can be Polygon objects, per the GeoJSON Format Specification.
Aerospike supports the Feature operator, which allows groups of geometry objects and user-specified properties; however, Feature Collection is not supported.
Invalid GeoJSON objects are caught on insert/update. For example, an object defined as point instead of Point fails.
Per the GeoJSON IETF recommendation, the Coordinate System is WGS84. Explicit specification of a coordinate reference system (CRS) is ignored.
Configuration parameters
| Parameter | Datatype | Default | Description |
|---|---|---|---|
max-cells | Integer | 12 | Defines the maximum number of cells used in the approximation. Increasing this value improves accuracy but may affect query performance. |
max-level | Integer | 20 | Defines the minimum size of the cell to be used in the approximation. Tuning this can make query results more accurate. |
min-level | Integer | 1 | Defines the size of the maximum cell to be used in the approximation. Should generally be set to 1; increasing too much may cause queries to fail. |
earth-radius-meters | Integer | 6371000 | Specifies Earth’s radius in meters. Used for geographical calculations. |
level-mod | Integer | 1 | Specifies the multiple for levels to be used, effectively increasing the branching factor of the S2 Cell Id hierarchy. |
strict | Boolean | true | When true, performs additional validation on results to ensure they fall within the query region. When false, returns results as-is, which may include points outside the query region. |
max-cells visualization
Here’s an example that shows how RegionCoverer covers a specified region with max-cells set to different values. With a higher value of max-cells, the approximation becomes more accurate.
With max-cells = 10:
With max-cells = 30:
With max-cells = 100:
max-level visualization
Here’s an example to see RegionCoverer covering a specified region and how tuning max-level can make query results more accurate.
For this example, min-level is set to 1, and max-cells is set to 10.
With max-level = 12,
With max-level = 30,
Create a geospatial application
To develop a geospatial application:
- Install and configure the Aerospike server.
- Create a Geo2DSphere index on a namespace-set-bin combination.
- Construct and insert GeoJSON
Pointdata. - Construct a Points-within-Region predicate (
whereclause), make a query request, and process the records returned. - (alternate) Construct and insert GeoJSON
Polygon/MultiPolygondata. - (alternate) Construct a Region-contains-Point predicate, make a query request, and process the records returned.
Known limitations
- Using UDFs to insert or update GeoJSON data types is not supported.
- Duplicate records can be returned.
- For namespaces with
data-in-memory true, GeoJSON particles allocate up to 2KB more than the reported particle size, which can lead to high memory consumption in some cases. This problem was corrected in Aerospike Database versions 4.9.0 and later.