---
title: "Geospatial index and query"
description: "Learn how to implement Aerospike geospatial indexing and queries using GeoJSON points, polygons, and AeroCircle."
---

# Geospatial index and query

> For the complete documentation index see: [llms.txt](https://aerospike.com/docs/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](https://www.rfc-editor.org/rfc/rfc7946#section-3), and achieve data standardization and interoperability. See the [GeoJSON Format Specification](https://www.rfc-editor.org/rfc/rfc7946) and [GeoJSON IETF WG](https://github.com/geojson/draft-geojson).
-   The Aerospike [**AeroCircle**](#aerospike-geojson-extension) 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.

::: note
The [S2 Geometry Library](https://code.google.com/archive/p/s2-geometry-library/) is a spherical geometry library, very useful for manipulating regions on a sphere (commonly on Earth) and indexing geographic data. The [S2 blog](http://blog.christianperone.com/2015/08/googles-s2-geometry-on-the-sphere-cells-and-hilbert-curve/) provides more details about S2, Hilbert Curve, and geospatial mapping.
:::

-   Aerospike [secondary indexes](https://aerospike.com/docs/database/learn/architecture/data-storage/secondary-index) and [queries](https://aerospike.com/docs/develop/learn/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](https://github.com/aerospike/geospatial-samples).

## 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-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.

-   [Java](#tab-panel-2144)
-   [Python](#tab-panel-2145)
-   [Go](#tab-panel-2146)
-   [C](#tab-panel-2147)
-   [C#](#tab-panel-2148)
-   [Node.js](#tab-panel-2149)

```java
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));
```

```python
key = ("test", "geo", "loc1")

loc = aerospike.GeoJSON({

    "type": "Point",

    "coordinates": [-122.0862, 37.4220]

})

client.put(key, {"name": "Googleplex", "loc_bin": loc})
```

```go
key, _ := as.NewKey("test", "geo", "loc1")

point := as.NewGeoJSONValue(

    `{"type": "Point", "coordinates": [-122.0862, 37.4220]}`)

client.PutBins(nil, key,

    as.NewBin("name", "Googleplex"),

    as.NewBin("loc_bin", point))
```

```c
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);
```

```csharp
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));
```

```js
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 });
```

## 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 objects such as points, lines, polygons, and circles. 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](https://aerospike.com/docs/database/tools/asadm) to create and manage secondary indexes in an Aerospike cluster. For instructions, see [Secondary Index (SI) Query](https://aerospike.com/docs/develop/learn/queries).

The following command creates a secondary index called `geo-index` using `geo2dsphere` data on the namespace `user-profile` using the set name `geo-set` and the bin `geo-bin`.

Terminal window

```bash
Admin+> manage sindex create geo2dsphere geo-index ns user_profile set geo-set bin geo-bin
```

Indexes can also be created programmatically:

-   [Java](#tab-panel-2150)
-   [Python](#tab-panel-2151)
-   [Go](#tab-panel-2152)
-   [C](#tab-panel-2153)
-   [C#](#tab-panel-2154)
-   [Node.js](#tab-panel-2155)

```java
IndexTask task = client.createIndex(null, "test", "geo", "geo-loc-idx",

    "loc_bin", IndexType.GEO2DSPHERE);

task.waitTillComplete();
```

```python
client.index_single_value_create(

    "test", "geo", "loc_bin",

    aerospike.INDEX_GEO2DSPHERE, "geo-loc-idx")
```

```go
task, _ := client.CreateIndex(nil, "test", "geo", "geo-loc-idx",

    "loc_bin", as.GEO2DSPHERE)

<-task.OnComplete()
```

```c
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);
```

```csharp
IndexTask task = client.CreateIndex(null, "test", "geo", "geo-loc-idx",

    "loc_bin", IndexType.GEO2DSPHERE);

task.Wait();
```

```js
await client.createIndex({

  ns: "test", set: "geo", bin: "loc_bin",

  index: "geo-loc-idx",

  datatype: Aerospike.indexDataType.GEO2DSPHERE,

});
```

## 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`:

```json
{ "name": "Googleplex",      "loc_bin": {"type": "Point", "coordinates": [-122.0862, 37.4220]} }

{ "name": "Ferry Building",  "loc_bin": {"type": "Point", "coordinates": [-122.3936, 37.7956]} }

{ "name": "UC Berkeley",     "loc_bin": {"type": "Point", "coordinates": [-122.2727, 37.8716]} }

{ "name": "NYC Times Sq",    "loc_bin": {"type": "Point", "coordinates": [-73.9857,  40.7580]} }
```

### 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.

-   [Java](#tab-panel-2156)
-   [Python](#tab-panel-2157)
-   [Go](#tab-panel-2158)
-   [C](#tab-panel-2159)
-   [C#](#tab-panel-2160)
-   [Node.js](#tab-panel-2161)

```java
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();
```

```python
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"])
```

```go
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, _ := client.Query(nil, stmt)

for rec := range rs.Results() {

    fmt.Println(rec.Record.Bins["name"])

}
```

```c
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);
```

```csharp
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();
```

```js
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 stream = query.foreach();

stream.on("data", (rec) => console.log(rec.bins.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.

-   [Java](#tab-panel-2162)
-   [Python](#tab-panel-2163)
-   [Go](#tab-panel-2164)
-   [C](#tab-panel-2165)
-   [C#](#tab-panel-2166)
-   [Node.js](#tab-panel-2167)

```java
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();
```

```python
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"])
```

```go
stmt := as.NewStatement("test", "geo")

stmt.SetFilter(as.NewGeoWithinRadiusFilter("loc_bin",

    -122.0862, 37.4220, 50000))

rs, _ := client.Query(nil, stmt)

for rec := range rs.Results() {

    fmt.Println(rec.Record.Bins["name"])

}
```

```c
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);
```

```csharp
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();
```

```js
const query = client.query("test", "geo");

query.where(Aerospike.filter.geoWithinRadius("loc_bin",

    -122.0862, 37.4220, 50000));

const stream = query.foreach();

stream.on("data", (rec) => console.log(rec.bins.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.

Given these region records stored in `rgn_bin`:

```json
{ "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.

-   [Java](#tab-panel-2168)
-   [Python](#tab-panel-2169)
-   [Go](#tab-panel-2170)
-   [C](#tab-panel-2171)
-   [C#](#tab-panel-2172)
-   [Node.js](#tab-panel-2173)

```java
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();
```

```python
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"])
```

```go
point := `{"type": "Point", "coordinates": [-122.4000, 37.7900]}`

stmt := as.NewStatement("test", "geo")

stmt.SetFilter(as.NewGeoRegionsContainingPointFilter("rgn_bin", point))

rs, _ := client.Query(nil, stmt)

for rec := range rs.Results() {

    fmt.Println(rec.Record.Bins["name"])

}
```

```c
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);
```

```csharp
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();
```

```js
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 stream = query.foreach();

stream.on("data", (rec) => console.log(rec.bins.name));
```

## Query filters

To extend the capabilities of geospatial queries, apply a [filter expression](https://aerospike.com/docs/develop/expressions/#record-filtering-with-expressions) to narrow down results. This example finds points within a 50 km radius that also have an `amenity` bin equal to `"cafe"`.

-   [Java](#tab-panel-2174)
-   [Python](#tab-panel-2175)
-   [Go](#tab-panel-2176)
-   [C](#tab-panel-2177)
-   [C#](#tab-panel-2178)
-   [Node.js](#tab-panel-2179)

```java
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();
```

```python
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"])
```

```go
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, _ := client.Query(qp, stmt)

for rec := range rs.Results() {

    fmt.Println(rec.Record.Bins["name"])

}
```

```c
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);
```

```csharp
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();
```

```js
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 stream = query.foreach(queryPolicy);

stream.on("data", (rec) => console.log(rec.bins.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:

-   [Java](#tab-panel-2180)
-   [Python](#tab-panel-2181)
-   [Go](#tab-panel-2182)
-   [C](#tab-panel-2183)
-   [C#](#tab-panel-2184)
-   [Node.js](#tab-panel-2185)

```java
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();
```

```python
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"])
```

```go
task, _ := client.CreateComplexIndex(nil, "test", "geo",

    "geo-points-idx", "stops", as.GEO2DSPHERE, as.ICT_LIST)

<-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, _ := client.Query(nil, stmt)

for rec := range rs.Results() {

    fmt.Println(rec.Record.Bins["name"])

}
```

```c
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);
```

```csharp
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();
```

```js
await client.createIndex({

  ns: "test", set: "geo", bin: "stops",

  index: "geo-points-idx",

  datatype: Aerospike.indexDataType.GEO2DSPHERE,

  type: Aerospike.indexType.LIST,

});

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 stream = query.foreach();

stream.on("data", (rec) => console.log(rec.bins.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`.

```json
{"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](http://geojson.org/geojson-spec#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](http://geojson.org/geojson-spec#polygon).

::: note
`Polygon` loop definitions must wind counter-clockwise.
:::

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`](https://aerospike.com/docs/database/reference/config#namespace__max-cells) | Integer | 8 | Defines the maximum number of cells used in the approximation. Increasing this value improves accuracy but may affect query performance. |
| [`max-level`](https://aerospike.com/docs/database/reference/config#namespace__max-level) | Integer | 1 | Defines the minimum size of the cell to be used in the approximation. Tuning this can make query results more accurate. |
| [`min-level`](https://aerospike.com/docs/database/reference/config#namespace__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`](https://aerospike.com/docs/database/reference/config#namespace__earth-radius-meters) | Integer | 6371000 | Specifies Earth’s radius in meters. Used for geographical calculations. |
| [`level-mod`](https://aerospike.com/docs/database/reference/config#namespace__level-mod) | Integer | 1 | Specifies the multiple for levels to be used, effectively increasing the branching factor of the S2 Cell Id hierarchy. |
| [`strict`](https://aerospike.com/docs/database/reference/config#namespace__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:

 ![max-cells = 10](https://aerospike.com/docs/_astro/Geo10maxcells.NicZFRmL_Z1hvpcl.png)

With `max-cells` = 30:

 ![max-cells = 30](https://aerospike.com/docs/_astro/Geo30maxcells.BiGIPquK_Z9uLso.png)

With `max-cells` = 100:

 ![max-cells = 100](https://aerospike.com/docs/_astro/Geo100maxcells.CcapkB9U_ZT4kry.png)

### `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,

 ![max-level = 12](https://aerospike.com/docs/_astro/Geo12maxlevel.CxsVHHD8_20uIBK.png)

With `max-level` = 30,

 ![max-level = 30](https://aerospike.com/docs/_astro/Geo30maxlevel.DhHgFqWT_ZCELoH.png)

## Create a geospatial application

To develop a geospatial application:

1.  [Install](https://aerospike.com/docs/database/install) and configure the Aerospike server.
2.  Create a Geo2DSphere index on a namespace-set-bin combination.
3.  Construct and insert GeoJSON `Point` data.
4.  Construct a Points-within-Region predicate (`where` clause), make a query request, and process the records returned.
5.  (alternate) Construct and insert GeoJSON `Polygon`/`MultiPolygon` data.
6.  (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.