24. GeoMesa GeoJSON

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.

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

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

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

24.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 } ] }

24.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]}}

24.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.11</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"}}

24.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.11-$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/.

24.1.5. Methods

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

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

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

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

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

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

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

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

24.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'

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

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

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

24.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;
  }
}