23. 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.
23.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.
23.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.
23.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.
23.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 } ] }
|
23.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]}}
23.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"}}
23.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/
.
23.1.5. Methods¶
23.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 |
23.1.5.2. Register a DataStore¶
Registers a data store to make it available for querying.
URL | /ds/:alias |
Method | POST |
URL Params | Required
|
Data Params | Required
Optional
|
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 |
23.1.5.3. Create GeoJSON Index¶
Creates a new index under an existing data store.
URL | /index/:alias/:index |
Method | POST |
URL Params | Required
|
Data Params | Optional
|
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
|
23.1.5.4. Delete GeoJSON Index¶
Deletes an existing index and all features it contains.
URL | /index/:alias/:index |
Method | DELETE |
URL Params | Required
|
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
|
23.1.5.5. Add Features¶
Add features to the index with GeoJSON.
URL | /index/:alias/:index/features |
Method | POST |
URL Params | Required
|
Body |
|
Success Response | Code: 200 Content: |
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
|
23.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
|
Body |
|
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
|
23.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
|
Body |
|
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
|
23.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
|
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
|
23.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
|
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'
|
23.1.5.10. Query Features¶
Query features with a predicate.
URL | /index/:alias/:index/features |
Method | GET |
URL Params | Required
Optional
|
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 |
23.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
23.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