25. GeoMesa GeoJSON

Warning

The GeoMesa GeoJSON API is deprecated and will be removed in a future version.

GeoMesa provides built-in integration with GeoJSON. GeoMesa provides a GeoJSON API that allows for the indexing and querying of GeoJSON data. The GeoJSON API does not use GeoTools - all data and operations are pure JSON. The API also includes a REST endpoint for web integration.

In addition, when working with GeoTools, JSON can be stored as an attribute in a simple feature and queried using CQL.

GeoMesa’s JSON processing uses JSONPath for selecting JSON elements.

25.1. GeoJSON API

The GeoJSON API provides a simplified interface for spatially indexing GeoJSON. GeoJSON is ingested by indexing the embedded geometry. Additionally, a date field can be indexed by specifying a custom field in the properties element, which allows arbitrary JSON extensions.

Data may be accessed programmatically or through a REST endpoint.

25.1.1. Adding and Updating Features

Data is added to the index as GeoJSON strings. When creating the index, you may optionally specify an ID field and a date field using JSONPath expressions. The ID field is required in order to update or modify features. The date field will allow for optimized spatio-temporal queries.

Features can be added to the index by passing in GeoJSON objects of type Feature or FeatureCollection. Updating or deleting features requires the corresponding feature IDs.

25.1.2. Querying Features

Features can be queried using a MongoDB-like JSON syntax. Results will be the original GeoJSON, or may be transformed into arbitrary JSON py passing in a projection.

25.1.2.1. Predicates

Predicate

Syntax

include

{}

equals

{ "foo" : "bar" }

greater-than

{ "foo" : { "$gt" : 10 } }
{ "foo" : { "$gte" : 10 } }

less-than

{ "foo" : { "$lt" : 10 } }
{ "foo" : { "$lte" : 10 } }

bounding box

{ "geometry" : { "$bbox" : [-180, -90, 180, 90] }}

spatial intersects

{
  "geometry" : {
    "$intersects" : {
      "$geometry" : { "type" : "Point", "coordinates" : [30, 10] }
    }
  }
}

spatial within

{
  "geometry" : {
    "$within" : {
      "$geometry" : {
        "type" : "Polygon",
        "coordinates": [ [ [0,0], [3,6], [6,1], [0,0] ] ]
      }
    }
  }
}

spatial contains

{
  "geometry" : {
    "$contains" : {
      "$geometry" : {  "type" : "Point", "coordinates" : [30, 10] }
    }
  }
}

and

{ "foo" : "bar", "baz" : 10 }

or

{ "$or" : [ { "foo" : "bar" }, { "baz" : 10 } ] }

25.1.2.2. Transformations

The JSON being returned can be transformed by specifying element mappings. The transform is defined by a map where the keys define the element in the returned JSON using dot notation, and the values specify the JSONPath expression used to extract the value from the original GeoJSON.

For example, to return just the geometry you could use a mapping of "geom" -> "geometry":

{"geom":{"type":"Point","coordinates":[30,10]}}

25.1.3. Programmatic Access

The main interface for programmatic access is the GeoJsonIndex. It can be instantiated by wrapping a GeoMesa DataStore. The module is available through maven:

<dependency>
    <groupId>org.locationtech.geomesa</groupId>
    <artifactId>geomesa-geojson-api_2.12</artifactId>
    <version>1.3.0</version>
</dependency>

Example code in scala:

import org.locationtech.geomesa.geojson.{GeoJsonGtIndex, GeoJsonIndex}

val ds = DataStoreFinder.getDataStore(...) // ensure this is a GeoMesa data store
val index: GeoJsonIndex = new GeoJsonGtIndex(ds)
index.createIndex("test", Some("$.properties.id"), points = true)

val features =
    s"""{ "type": "FeatureCollection",
       |  "features": [
       |    {"type":"Feature","geometry":{"type":"Point","coordinates":[30,10]},"properties":{"id":"0","name":"n0"}},
       |    {"type":"Feature","geometry":{"type":"Point","coordinates":[31,10]},"properties":{"id":"1","name":"n1"}},
       |    {"type":"Feature","geometry":{"type":"Point","coordinates":[32,10]},"properties":{"id":"2","name":"n2"}}
       |  ]
       |}""".stripMargin

index.add(name, features)

// query by bounding box
index.query(name, """{ "geometry" : { "$bbox" : [29, 9, 31.5, 11] }}""").toList
// result:
// {"type":"Feature","geometry":{"type":"Point","coordinates":[30,10]},"properties":{"id":"0","name":"n0"}}
// {"type":"Feature","geometry":{"type":"Point","coordinates":[31,10]},"properties":{"id":"1","name":"n1"}}

// query for all, transform JSON coming back
index.query(name, "{}", Map("foo.bar" -> "geometry", "foo.baz" -> "properties.name")).toList
// result:
// {"foo":{"bar":{"type":"Point","coordinates":[30,10]},"baz":"n0"}}
// {"foo":{"bar":{"type":"Point","coordinates":[31,10]},"baz":"n1"}}
// {"foo":{"bar":{"type":"Point","coordinates":[32,10]},"baz":"n2"}}

25.1.4. REST Access

The GeoJsonIndex is also exposed through a REST endpoint. Currently, the REST endpoint does not support transformation of responses. Furthermore, it requires Accumulo as the backing data store. It may be installed in GeoServer by extracting the install file into geoserver/WEB-INF/lib:

$ tar -xf geomesa-geojson/geomesa-geojson-gs-plugin/target/geomesa-geojson-gs-plugin_2.12-$VERSION-install.tar.gz -C <dest>

Note that this also requires the AccumuloDataStore to be installed. See Installing GeoMesa Accumulo in GeoServer.

The REST endpoint will be available at <host>:<port>/geoserver/geomesa/geojson/.

25.1.5. Methods

25.1.5.1. Get Registered DataStores

Returns a list of data stores available for querying.

URL

/ds

Method

GET

URL Params

None

Data Params

None

Success Response

Code: 200

Content:

{
  "mycloud": {
    "accumulo.instance.id":"foo",
    "accumulo.zookeepers":"foo1,foo2,foo3",
    "accumulo.catalog":"foo.bar",
    "accumulo.user":"foo",
    "accumulo.password":"***"
  }
}

Error Response

N/A

Sample Call

curl 'localhost:8080/geoserver/geomesa/geojson/ds'

Notes

An entry will be returned for each registered data store

25.1.5.2. Register a DataStore

Registers a data store to make it available for querying.

URL

/ds/:alias

Method

POST

URL Params

Required

  • alias=[alphanumeric] Alias used to reference the data store in future requests

Data Params

Required

  • accumulo.instance.id=[alphanumeric]

  • accumulo.zookeepers=[alphanumeric]

  • accumulo.user=[alphanumeric]

  • accumulo.catalog=[alphanumeric]

Optional

  • geomesa.security.auths=[alphanumeric]

  • geomesa.query.timeout=[alphanumeric]

  • geomesa.query.threads=[integer]

  • accumulo.query.record-threads=[integer]

  • accumulo.write.threads=[integer]

  • geomesa.query.loose-bounding-box=[Boolean]

  • geomesa.stats.generate=[Boolean]

  • geomesa.query.audit=[Boolean]

  • geomesa.query.caching=[Boolean]

  • geomesa.security.force-empty-auths=[Boolean]

Success Response

Code: 200

Content: empty

Error Response

Code: 400 - if data store can not be created with the provided parameters

Content: empty

Sample Call

curl \
  'localhost:8080/geoserver/geomesa/geojson/ds/myds' \
  -d accumulo.user=foo -d accumulo.password=foo -d accumulo.catalog=foo.bar \
  -d accumulo.zookeepers=foo1,foo2,foo3 -d accumulo.instance.id=foo

Notes

Parameters correspond to the AccumuloDataStore connection parameters used by DataStoreFinder

25.1.5.3. Create GeoJSON Index

Creates a new index under an existing data store.

URL

/index/:alias/:index

Method

POST

URL Params

Required

  • alias=[alphanumeric] Reference to a previously registered data store

  • index=[alphanumeric] Unique name of the GeoJSON index to create

Data Params

Optional

  • points=[Boolean] Optimization hint if all geometries will be points

  • date=[alphanumeric] JSONPath expression to a date field for temporal indexing

  • id=[alphanumeric] JSONPath expression to an ID field to uniquely identify each record

Success Response

Code: 201

Content: empty

Error Response

Code: 400 - if a required parameter is not specified

Content: empty

Sample Call

curl \
  'localhost:8080/geoserver/geomesa/geojson/index/myds/test' \
  -d id=properties.id

25.1.5.4. Delete GeoJSON Index

Deletes an existing index and all features it contains.

URL

/index/:alias/:index

Method

DELETE

URL Params

Required

  • alias=[alphanumeric] Reference to a previously registered data store

  • index=[alphanumeric] Unique name of the GeoJSON index to create

Success Response

Code: 204

Content: empty

Error Response

Code: 400 - if a required parameter is not specified

Content: empty

Sample Call

curl \
  'localhost:8080/geoserver/geomesa/geojson/index/myds/test' \
  -X DELETE

25.1.5.5. Add Features

Add features to the index with GeoJSON.

URL

/index/:alias/:index/features

Method

POST

URL Params

Required

  • alias=[alphanumeric] Reference to a previously registered data store

  • index=[alphanumeric] Unique name of a GeoJSON index

Body

  • [alphanumeric] GeoJSON Feature or FeatureCollection

Success Response

Code: 200

Content: ["1","2"] List of ids for the added features

Error Response

Code: 400 - if a required parameter is not specified

Content: empty

Sample Call

echo '{"type":"Feature","geometry":{"type":"Point",' \
  '"coordinates":[30,10]},"properties":{"id":"0","name":"n0"}}' \
  > feature.json
curl \
  'localhost:8080/geoserver/geomesa/geojson/index/myds/test/features' \
  -H 'Content-type: application/json' \
  -d @feature.json

echo '{"type":"FeatureCollection","features":[' \
  '{"type":"Feature","geometry":{"type":"Point",' \
  '"coordinates":[32,10]},"properties":{"id":"1","name":"n1"}},' \
  '{"type":"Feature","geometry":{"type":"Point",' \
  '"coordinates":[34,10]},"properties":{"id":"2","name":"n2"}}]}' \
  > features.json
curl \
  'localhost:8080/geoserver/geomesa/geojson/index/myds/test/features' \
  -H 'Content-type: application/json' \
  -d @features.json

25.1.5.6. Update Features

Update existing features in the index. Feature IDs will be extracted from the GeoJSON submitted.

URL

/index/:alias/:index/features

Method

PUT

URL Params

Required

  • alias=[alphanumeric] Reference to a previously registered data store

  • index=[alphanumeric] Unique name of a GeoJSON index

Body

  • [alphanumeric] GeoJSON Feature or FeatureCollection

Success Response

Code: 200

Content: empty

Error Response

Code: 400 - if a required parameter is not specified

Content: empty

Code: 400 - if ID field was not specified when creating the index

Content: empty

Sample Call

echo '{"type":"Feature","geometry":{"type":"Point",' \
  '"coordinates":[30,10]},"properties":{"id":"0","name":"n0-updated"}}' \
  > feature.json
curl \
  'localhost:8080/geoserver/geomesa/geojson/index/myds/test/features' \
  -H 'Content-type: application/json' \
  --upload-file feature.json

echo '{"type":"FeatureCollection","features":[' \
  '{"type":"Feature","geometry":{"type":"Point",' \
  '"coordinates":[32,10]},"properties":{"id":"1","name":"n1-updated"}},' \
  '{"type":"Feature","geometry":{"type":"Point",' \
  '"coordinates":[34,10]},"properties":{"id":"2","name":"n2-updated"}}]}' \
  > features.json
curl \
  'localhost:8080/geoserver/geomesa/geojson/index/myds/test/features' \
  -H 'Content-type: application/json' \
  --upload-file features.json

25.1.5.7. Update Features by ID

Update existing features in the index, explicitly specifying the feature IDs.

URL

/index/:alias/:index/features/:ids

Method

PUT

URL Params

Required

  • alias=[alphanumeric] Reference to a previously registered data store

  • index=[alphanumeric] Unique name of a GeoJSON index

  • id=[alphanumeric] Feature IDs to update, comma-separated

Body

  • [alphanumeric] GeoJSON Feature or FeatureCollection

Success Response

Code: 200

Content: empty

Error Response

Code: 400 - if a required parameter is not specified

Content: empty

Sample Call

echo '{"type":"Feature","geometry":{"type":"Point",' \
  '"coordinates":[30,10]},"properties":{"id":"0","name":"n0-updated"}}' \
  > feature.json
curl \
  'localhost:8080/geoserver/geomesa/geojson/index/myds/test/features/0' \
  -H 'Content-type: application/json' \
  --upload-file feature.json

echo '{"type":"FeatureCollection","features":[' \
  '{"type":"Feature","geometry":{"type":"Point",' \
  '"coordinates":[32,10]},"properties":{"id":"1","name":"n1-updated"}},' \
  '{"type":"Feature","geometry":{"type":"Point",' \
  '"coordinates":[34,10]},"properties":{"id":"2","name":"n2-updated"}}]}' \
  > features.json
curl \
  'localhost:8080/geoserver/geomesa/geojson/index/myds/test/features/1,2' \
  -H 'Content-type: application/json' \
  --upload-file features.json

25.1.5.8. Delete Features by ID

Delete existing features in the index by feature IDs.

URL

/index/:alias/:index/features/:ids

Method

DELETE

URL Params

Required

  • alias=[alphanumeric] Reference to a previously registered data store

  • index=[alphanumeric] Unique name of a GeoJSON index

  • id=[alphanumeric] Feature IDs to delete, comma-separated

Success Response

Code: 200

Content: empty

Error Response

Code: 400 - if a required parameter is not specified

Content: empty

Sample Call

curl \
  'localhost:8080/geoserver/geomesa/geojson/index/myds/test/features/1,2' \
  -X DELETE

25.1.5.9. Query Features by ID

Query features in the index by feature IDs.

URL

/index/:alias/:index/features/:ids

Method

GET

URL Params

Required

  • alias=[alphanumeric] Reference to a previously registered data store

  • index=[alphanumeric] Unique name of a GeoJSON index

  • id=[alphanumeric] Feature IDs to query, comma-separated

Success Response

Code: 200

Content: GeoJSON feature collection

Example:

{
  "type":"FeatureCollection",
  "features":[
    {
      "type":"Feature",
      "geometry":{"type":"Point","coordinates":[32,10]},
      "properties":{"id":"1","name":"n1"}
    }
  ]
}

Error Response

Code: 400 - if a required parameter is not specified

Content: empty

Sample Call

curl \
  'localhost:8080/geoserver/geomesa/geojson/index/myds/test/features/1,2'

25.1.5.10. Query Features

Query features with a predicate.

URL

/index/:alias/:index/features

Method

GET

URL Params

Required

  • alias=[alphanumeric] Reference to a previously registered data store

  • index=[alphanumeric] Unique name of a GeoJSON index

Optional

  • q=[alphanumeric] JSON query predicate

Success Response

Code: 200

Content: GeoJSON feature collection

Example:

{
  "type":"FeatureCollection",
  "features":[
    {
      "type":"Feature",
      "geometry":{"type":"Point","coordinates":[32,10]},
      "properties":{"id":"1","name":"n1"}
    }
  ]
}

Error Response

Code: 400 - if a required parameter is not specified

Content: empty

Sample Call

# return all features in the index 'test'
curl \
  'localhost:8080/geoserver/geomesa/geojson/index/myds/test/features'

# query by feature id
curl \
  'localhost:8080/geoserver/geomesa/geojson/index/myds/test/features' \
  --get --data-urlencode 'q={"properties.id":"0"}'

# query by bounding box
curl \
  'localhost:8080/geoserver/geomesa/geojson/index/myds/test/features' \
  --get --data-urlencode 'q={"geometry":{"$bbox":[33,9,35,11]}}'

# query by property
curl \
  'localhost:8080/geoserver/geomesa/geojson/index/myds/test/features' \
  --get --data-urlencode 'q={"properties.name":"n1"}'

Notes

See Querying Features for full query syntax

25.2. JSON Attributes

In addition to the GeoJSON API, GeoMesa allows for JSON integration with GeoTools data stores. Simple feature String-type attributes can be marked as JSON and then queried using CQL. JSON attributes must be specified when creating a simple feature type:

import org.locationtech.geomesa.utils.interop.SimpleFeatureTypes;

// append the json hint after the attribute type, separated by a colon
String spec = "json:String:json=true,dtg:Date,*geom:Point:srid=4326"
SimpleFeatureType sft = SimpleFeatureTypes.createType("mySft", spec);
dataStore.createSchema(sft);

JSON attributes are still strings, and are set as any other strings:

String json = "{ \"foo\" : \"bar\" }";
SimpleFeature sf = ...
sf.setAttribute("json", json);

JSON attributes can be queried using JSONPath expressions. The first part of the path refers to the simple feature attribute name, and the rest of the path is applied to the JSON attribute. Note that in ECQL, path expressions must be enclosed in double quotes.

Filter filter = ECQL.toFilter("\"$.json.foo\" = 'bar'")
SimpleFeature sf = ...
sf.setAttribute("json", "{ \"foo\" : \"bar\" }");
filter.evaluate(sf); // returns true
sf.getAttribute("\"$.json.foo\""); // returns "bar"
sf.setAttribute("json", "{ \"foo\" : \"baz\" }");
filter.evaluate(sf); // returns false
sf.getAttribute("\"$.json.foo\""); // returns "baz"
sf.getAttribute("\"$.json.bar\""); // returns null

25.2.1. JSONPath CQL Filter Function

JSON attributes can contain periods and spaces. In order to query these attributes through an ECQL filter use the jsonPath CQL filter function. This passes the path to an internal interpreter function that understands how to handle these attribute names.

Filter filter = ECQL.toFilter("jsonPath('$.json.foo') = 'bar'")
SimpleFeature sf = ...
sf.setAttribute("json", "{ \"foo\" : \"bar\" }");
filter.evaluate(sf); // returns true

To handle periods and spaces in attribute names, enclose the attribute in the standard bracket notation. However, since the path is being passed to the jsonPath function as a string literal parameter, the single quotes need to be escaped with an additional single quote.

Filter filter = ECQL.toFilter("jsonPath('$.json.[''foo.bar'']') = 'bar'")
SimpleFeature sf = ...
sf.setAttribute("json", "{ \"foo.bar\" : \"bar\" }");
filter.evaluate(sf); // returns true

Similarly for spaces:

Filter filter = ECQL.toFilter("jsonPath('$.json.[''foo bar'']') = 'bar'")
SimpleFeature sf = ...
sf.setAttribute("json", "{ \"foo bar\" : \"bar\" }");
filter.evaluate(sf); // returns true

25.2.2. JSONPath With GeoServer Styles

When using JSON path in GeoServer styles (SLD or CSS), the attribute and path must be separated out in order for the GeoTools renderer to work correctly. In this case, pass in two arguments, the first being a property expression in double quotes of the JSON-type attribute name, and the second being the path:

* {
  mark: symbol(arrow);
  mark-size: 12px;
  mark-rotation: [ jsonPath("json", 'foo') ];
  :mark {
    fill: #009900;
  }
}