Introduction

Neo4j Spatial is a library of utilities for Neo4j that facilitate the enabling of spatial operations on data. In particular you can add spatial indexes to already located data, and perform spatial operations on the data like searching for data within specified regions or within a specified distance of a point of interest. In addition classes are provided to expose the data to GeoTools and thereby to GeoTools-enabled applications like GeoServer and uDig.

one-street.png

The key features include:

  • Utilities for importing from ESRI Shapefile as well as Open Street Map files

  • Support for all the common geometry types: Point, LineString, Polygon, etc.

  • An RTree index for fast searches on geometries

  • Support for topology operations during the search (contains, within, intersects, covers, disjoint, etc.)

  • The possibility to enable spatial operations on any graph of data, regardless of the way the spatial data is stored, as long as an adapter is provided to map from the graph to the geometries.

  • Ability to split a single layer or dataset into multiple sub-layers or views with pre-configured filters

Index and Querying

The current index is an RTree index, but it has been developed in an extensible way allowing for other indices to be added if necessary.

Loading and Indexing

It is possible to load data into the database and add it to the index during the load. It is also possible to add existing spatial data to the index later. These are two very different scenarios, and in fact can lead to different graph structures, so we will explain them each in turn.

To load data directly into the index, the simplest approach is to start by creating a Layer that suites your data. There are many possible choices built into Neo4j-Spatial, but two common ones would be:

  • SimplePointLayer - an editable layer that allows you to add only Points to the database. This is a good choice if you only have point data and are interested primarily in proximity searches. This layer includes utility methods specifically for that case.

  • EdiableLayerImpl - this is the default editable layer implementation and can handle any type of simple geometry, including Point, LineString and Polygon, as well as Multi-Point, Multi-LineString and Multi-Polygon. Since it is a generic implementation and cannot know about the topology of your data model, it stores each geometry separately in a single property of a single node. The storage format is WKB, or Well Known Binary, which is a binary format specific to geographic geometries, and also used by the popular open source spatial database PostGIS.

Simple Point Layer

simple point layer

EditableLayer

editable layer

Querying

JTS Queries

Neo4j-Spatial contains the Java Topology Suite, a library of geometries and geometry operations. In fact, whenever we use the term Geometry we are refering to the JTS class Geometry. Likewise the subclasses of Geometry: Point, LineString, Polygon and others are all from JTS. This means that you can use all the capabilities of JTS to operate on Geometry instances you obtain from the database. If, for example, you perform a search for geometries in a certain area, you will be able to iterate over the results and for each geometry returned, call JTS methods on that class. For example, you could call geometry.

But The spatial queries implemented are:

  • Contain

  • Cover

  • Covered By

  • Cross

  • Disjoint

  • Intersect

  • Intersect Window

  • Overlap

  • Touch

  • Within

  • Within Distance

CQL Queries

TBD

JSON Queries

TDB

Layers and GeometryEncoders

The primary type that defines a collection of geometries is the Layer. A layer contains an index for querying. In addition a Layer can be an EditableLayer if it is possible to add and modify geometries in the layer. The next most important interface is the GeometryEncoder.

The DefaultLayer is the standard layer, making use of the WKBGeometryEncoder for storing all geometry types as byte[] properties of one node per geometry instance.

The OSMLayer is a special layer supporting Open Street Map and storing the OSM model as a single fully connected graph. The set of Geometries provided by this layer includes Points, LineStrings and Polygons, and as such cannot be exported to Shapefile format, since that format only allows a single Geometry per layer. However, OMSLayer extends DynamicLayer, which allow it to provide any number of sub-layers, each with a specific geometry type and in addition based on a OSM tag filter. For example you can have a layer providing all cycle paths as LineStrings, or a layer providing all lakes as Polygons. Underneath these are all still backed by the same fully connected graph, but exposed dynamically as apparently separate geometry layers.

Using the Neo4j Spatial Server plugin

Neo4j Spatial is also packaged as a ZIP file that can be unzipped into the Neo4j Server /plugin directory. After restarting the server, you should be able to do things like the following REST calls

Finding the plugin

The Neo4j Spatial Server plugin, if installed, will be announced in the root representation for the Neo4j Server REST API.

Final-Graph-finding-the-plugin.svg
Figure 1. Final Graph

Example request

  • GET http://localhost:7575/db/data/ext/SpatialPlugin

  • Accept: application/json; charset=UTF-8

Example response

  • 200: OK

  • Content-Type: application/json; charset=UTF-8

{
  "graphdb" : {
    "addEditableLayer" : {
      "extends" : "graphdb",
      "description" : "add a new layer specialized at storing generic geometry data in WKB",
      "name" : "addEditableLayer",
      "parameters" : [ {
        "description" : "The layer to find or create.",
        "optional" : false,
        "name" : "layer",
        "type" : "string"
      }, {
        "description" : "The format for internal representation, either WKB or WKT",
        "optional" : false,
        "name" : "format",
        "type" : "string"
      }, {
        "description" : "The name of the node property carrying the spatial geometry info",
        "optional" : false,
        "name" : "nodePropertyName",
        "type" : "string"
      } ]
    },
    "addCQLDynamicLayer" : {
      "extends" : "graphdb",
      "description" : "add a new dynamic layer exposing a filtered view of an existing layer",
      "name" : "addCQLDynamicLayer",
      "parameters" : [ {
        "description" : "The master layer to find",
        "optional" : false,
        "name" : "master_layer",
        "type" : "string"
      }, {
        "description" : "The name for the new dynamic layer",
        "optional" : false,
        "name" : "name",
        "type" : "string"
      }, {
        "description" : "The type of geometry to use for streaming data from the new view",
        "optional" : true,
        "name" : "geometry",
        "type" : "string"
      }, {
        "description" : "The CQL query to use for defining this dynamic layer",
        "optional" : false,
        "name" : "layer",
        "type" : "string"
      } ]
    },
    "findGeometriesWithinDistance" : {
      "extends" : "graphdb",
      "description" : "search a layer for geometries within a distance of a point. To achieve more complex CQL searches, pre-define the dynamic layer with addCQLDynamicLayer.",
      "name" : "findGeometriesWithinDistance",
      "parameters" : [ {
        "description" : "The x value of a point",
        "optional" : false,
        "name" : "pointX",
        "type" : "double"
      }, {
        "description" : "The y value of a point",
        "optional" : false,
        "name" : "pointY",
        "type" : "double"
      }, {
        "description" : "The distance from the point to search",
        "optional" : false,
        "name" : "distanceInKm",
        "type" : "double"
      }, {
        "description" : "The layer to search. Can be a dynamic layer with pre-defined CQL filter.",
        "optional" : false,
        "name" : "layer",
        "type" : "string"
      } ]
    },
    "updateGeometryFromWKT" : {
      "extends" : "graphdb",
      "description" : "update an existing geometry specified in WKT format. The layer must already contain the record.",
      "name" : "updateGeometryFromWKT",
      "parameters" : [ {
        "description" : "The geometry in WKT to add to the layer",
        "optional" : false,
        "name" : "geometry",
        "type" : "string"
      }, {
        "description" : "The geometry node id",
        "optional" : false,
        "name" : "geometryNodeId",
        "type" : "long"
      }, {
        "description" : "The layer to add the node to.",
        "optional" : false,
        "name" : "layer",
        "type" : "string"
      } ]
    },
    "addGeometryWKTToLayer" : {
      "extends" : "graphdb",
      "description" : "add a geometry specified in WKT format to a layer, encoding in the specified layers encoding schemea.",
      "name" : "addGeometryWKTToLayer",
      "parameters" : [ {
        "description" : "The geometry in WKT to add to the layer",
        "optional" : false,
        "name" : "geometry",
        "type" : "string"
      }, {
        "description" : "The layer to add the node to.",
        "optional" : false,
        "name" : "layer",
        "type" : "string"
      } ]
    },
    "getLayer" : {
      "extends" : "graphdb",
      "description" : "find an existing layer",
      "name" : "getLayer",
      "parameters" : [ {
        "description" : "The layer to find.",
        "optional" : false,
        "name" : "layer",
        "type" : "string"
      } ]
    },
    "findClosestGeometries" : {
      "extends" : "graphdb",
      "description" : "search a layer for the closest geometries and return them.",
      "name" : "findClosestGeometries",
      "parameters" : [ {
        "description" : "The x value of a point",
        "optional" : false,
        "name" : "pointX",
        "type" : "double"
      }, {
        "description" : "The y value of a point",
        "optional" : false,
        "name" : "pointY",
        "type" : "double"
      }, {
        "description" : "The maximum distance in km",
        "optional" : false,
        "name" : "distanceInKm",
        "type" : "double"
      }, {
        "description" : "The layer to search. Can be a dynamic layer with pre-defined CQL filter.",
        "optional" : false,
        "name" : "layer",
        "type" : "string"
      } ]
    },
    "addSimplePointLayer" : {
      "extends" : "graphdb",
      "description" : "add a new layer specialized at storing simple point location data",
      "name" : "addSimplePointLayer",
      "parameters" : [ {
        "description" : "The layer to find or create.",
        "optional" : false,
        "name" : "layer",
        "type" : "string"
      }, {
        "description" : "The node property that contains the latitude. Default is 'lat'",
        "optional" : true,
        "name" : "lat",
        "type" : "string"
      }, {
        "description" : "The node property that contains the longitude. Default is 'lon'",
        "optional" : true,
        "name" : "lon",
        "type" : "string"
      } ]
    },
    "findGeometriesInBBox" : {
      "extends" : "graphdb",
      "description" : "search a layer for geometries in a bounding box. To achieve more complex CQL searches, pre-define the dynamic layer with addCQLDynamicLayer.",
      "name" : "findGeometriesInBBox",
      "parameters" : [ {
        "description" : "The minimum x value of the bounding box",
        "optional" : false,
        "name" : "minx",
        "type" : "double"
      }, {
        "description" : "The maximum x value of the bounding box",
        "optional" : false,
        "name" : "maxx",
        "type" : "double"
      }, {
        "description" : "The minimum y value of the bounding box",
        "optional" : false,
        "name" : "miny",
        "type" : "double"
      }, {
        "description" : "The maximum y value of the bounding box",
        "optional" : false,
        "name" : "maxy",
        "type" : "double"
      }, {
        "description" : "The layer to search. Can be a dynamic layer with pre-defined CQL filter.",
        "optional" : false,
        "name" : "layer",
        "type" : "string"
      } ]
    },
    "addNodeToLayer" : {
      "extends" : "graphdb",
      "description" : "add a geometry node to a layer, as long as the node contains the geometry information appropriate to this layer.",
      "name" : "addNodeToLayer",
      "parameters" : [ {
        "description" : "The node representing a geometry to add to the layer",
        "optional" : false,
        "name" : "node",
        "type" : "node"
      }, {
        "description" : "The layer to add the node to.",
        "optional" : false,
        "name" : "layer",
        "type" : "string"
      } ]
    }
  }
}

Create a pointlayer

Create a point layer, specifying the lon and lat node properties as the ones carrying the spatial information.

Final-Graph-create-a-pointlayer.svg
Figure 2. Final Graph

Example request

  • POST http://localhost:7575/db/data/ext/SpatialPlugin/graphdb/addSimplePointLayer

  • Accept: application/json; charset=UTF-8

  • Content-Type: application/json

{
  "layer" : "geom",
  "lat" : "lat",
  "lon" : "lon"
}

Example response

  • 200: OK

  • Content-Type: application/json; charset=UTF-8

[ {
  "labels" : "http://localhost:7575/db/data/node/39/labels",
  "outgoing_relationships" : "http://localhost:7575/db/data/node/39/relationships/out",
  "data" : {
    "layer_class" : "org.neo4j.gis.spatial.EditableLayerImpl",
    "layer" : "geom",
    "geomencoder" : "org.neo4j.gis.spatial.encoders.SimplePointEncoder",
    "geomencoder_config" : "lon:lat",
    "ctime" : 1384521977725
  },
  "traverse" : "http://localhost:7575/db/data/node/39/traverse/{returnType}",
  "all_typed_relationships" : "http://localhost:7575/db/data/node/39/relationships/all/{-list|&|types}",
  "property" : "http://localhost:7575/db/data/node/39/properties/{key}",
  "self" : "http://localhost:7575/db/data/node/39",
  "properties" : "http://localhost:7575/db/data/node/39/properties",
  "outgoing_typed_relationships" : "http://localhost:7575/db/data/node/39/relationships/out/{-list|&|types}",
  "incoming_relationships" : "http://localhost:7575/db/data/node/39/relationships/in",
  "extensions" : {
  },
  "create_relationship" : "http://localhost:7575/db/data/node/39/relationships",
  "paged_traverse" : "http://localhost:7575/db/data/node/39/paged/traverse/{returnType}{?pageSize,leaseTime}",
  "all_relationships" : "http://localhost:7575/db/data/node/39/relationships/all",
  "incoming_typed_relationships" : "http://localhost:7575/db/data/node/39/relationships/in/{-list|&|types}"
} ]

Create a WKT layer

Creates a layer with nodes that have a property containing WKT or WKB, returns the layer node containing the configuration for the newly created layer.

Final-Graph-create-a-WKT-layer.svg
Figure 3. Final Graph

Example request

  • POST http://localhost:7575/db/data/ext/SpatialPlugin/graphdb/addEditableLayer

  • Accept: application/json; charset=UTF-8

  • Content-Type: application/json

{
  "layer" : "geom",
  "format" : "WKT",
  "nodePropertyName" : "wkt"
}

Example response

  • 200: OK

  • Content-Type: application/json; charset=UTF-8

[ {
  "labels" : "http://localhost:7575/db/data/node/47/labels",
  "outgoing_relationships" : "http://localhost:7575/db/data/node/47/relationships/out",
  "data" : {
    "layer_class" : "org.neo4j.gis.spatial.EditableLayerImpl",
    "layer" : "geom",
    "geomencoder" : "org.neo4j.gis.spatial.WKTGeometryEncoder",
    "geomencoder_config" : "wkt",
    "ctime" : 1384521977841
  },
  "traverse" : "http://localhost:7575/db/data/node/47/traverse/{returnType}",
  "all_typed_relationships" : "http://localhost:7575/db/data/node/47/relationships/all/{-list|&|types}",
  "property" : "http://localhost:7575/db/data/node/47/properties/{key}",
  "self" : "http://localhost:7575/db/data/node/47",
  "properties" : "http://localhost:7575/db/data/node/47/properties",
  "outgoing_typed_relationships" : "http://localhost:7575/db/data/node/47/relationships/out/{-list|&|types}",
  "incoming_relationships" : "http://localhost:7575/db/data/node/47/relationships/in",
  "extensions" : {
  },
  "create_relationship" : "http://localhost:7575/db/data/node/47/relationships",
  "paged_traverse" : "http://localhost:7575/db/data/node/47/paged/traverse/{returnType}{?pageSize,leaseTime}",
  "all_relationships" : "http://localhost:7575/db/data/node/47/relationships/all",
  "incoming_typed_relationships" : "http://localhost:7575/db/data/node/47/relationships/in/{-list|&|types}"
} ]

Find layer

Find a layer by its name, returning th layer .

Final-Graph-find-layer.svg
Figure 4. Final Graph

Example request

  • POST http://localhost:7575/db/data/ext/SpatialPlugin/graphdb/getLayer

  • Accept: application/json; charset=UTF-8

  • Content-Type: application/json

{
  "layer" : "geom"
}

Example response

  • 200: OK

  • Content-Type: application/json; charset=UTF-8

[ {
  "labels" : "http://localhost:7575/db/data/node/29/labels",
  "outgoing_relationships" : "http://localhost:7575/db/data/node/29/relationships/out",
  "data" : {
    "layer_class" : "org.neo4j.gis.spatial.EditableLayerImpl",
    "layer" : "geom",
    "geomencoder" : "org.neo4j.gis.spatial.WKTGeometryEncoder",
    "geomencoder_config" : "wkt",
    "ctime" : 1384521977326
  },
  "traverse" : "http://localhost:7575/db/data/node/29/traverse/{returnType}",
  "all_typed_relationships" : "http://localhost:7575/db/data/node/29/relationships/all/{-list|&|types}",
  "property" : "http://localhost:7575/db/data/node/29/properties/{key}",
  "self" : "http://localhost:7575/db/data/node/29",
  "properties" : "http://localhost:7575/db/data/node/29/properties",
  "outgoing_typed_relationships" : "http://localhost:7575/db/data/node/29/relationships/out/{-list|&|types}",
  "incoming_relationships" : "http://localhost:7575/db/data/node/29/relationships/in",
  "extensions" : {
  },
  "create_relationship" : "http://localhost:7575/db/data/node/29/relationships",
  "paged_traverse" : "http://localhost:7575/db/data/node/29/paged/traverse/{returnType}{?pageSize,leaseTime}",
  "all_relationships" : "http://localhost:7575/db/data/node/29/relationships/all",
  "incoming_typed_relationships" : "http://localhost:7575/db/data/node/29/relationships/in/{-list|&|types}"
} ]

Add a WKT geometry to a layer

Add a geometry, encoded in WKT, to a layer.

Final-Graph-add-a-WKT-geometry-to-a-layer.svg
Figure 5. Final Graph

Example request

  • POST http://localhost:7575/db/data/ext/SpatialPlugin/graphdb/addGeometryWKTToLayer

  • Accept: application/json; charset=UTF-8

  • Content-Type: application/json

{
  "layer" : "geom",
  "geometry" : "LINESTRING (15.2 60.1, 15.3 60.1)"
}

Example response

  • 200: OK

  • Content-Type: application/json; charset=UTF-8

[ {
  "labels" : "http://localhost:7575/db/data/node/5/labels",
  "outgoing_relationships" : "http://localhost:7575/db/data/node/5/relationships/out",
  "data" : {
    "bbox" : [ 15.2, 60.1, 15.3, 60.1 ],
    "wkt" : "LINESTRING (15.2 60.1, 15.3 60.1)",
    "gtype" : 2
  },
  "traverse" : "http://localhost:7575/db/data/node/5/traverse/{returnType}",
  "all_typed_relationships" : "http://localhost:7575/db/data/node/5/relationships/all/{-list|&|types}",
  "property" : "http://localhost:7575/db/data/node/5/properties/{key}",
  "self" : "http://localhost:7575/db/data/node/5",
  "properties" : "http://localhost:7575/db/data/node/5/properties",
  "outgoing_typed_relationships" : "http://localhost:7575/db/data/node/5/relationships/out/{-list|&|types}",
  "incoming_relationships" : "http://localhost:7575/db/data/node/5/relationships/in",
  "extensions" : {
  },
  "create_relationship" : "http://localhost:7575/db/data/node/5/relationships",
  "paged_traverse" : "http://localhost:7575/db/data/node/5/paged/traverse/{returnType}{?pageSize,leaseTime}",
  "all_relationships" : "http://localhost:7575/db/data/node/5/relationships/all",
  "incoming_typed_relationships" : "http://localhost:7575/db/data/node/5/relationships/in/{-list|&|types}"
} ]

Update a WKT geometry in a layer

Update a geometry, encoded in WKT, on an existing geometry in a layer.

Final-Graph-update-a-WKT-geometry-in-a-layer.svg
Figure 6. Final Graph

Example request

  • POST http://localhost:7575/db/data/ext/SpatialPlugin/graphdb/updateGeometryFromWKT

  • Accept: application/json; charset=UTF-8

  • Content-Type: application/json

{
  "layer" : "geom",
  "geometry" : "LINESTRING (16.2 60.1, 15.3 60.1)",
  "geometryNodeId" : 77
}

Example response

  • 200: OK

  • Content-Type: application/json; charset=UTF-8

[ {
  "labels" : "http://localhost:7575/db/data/node/77/labels",
  "outgoing_relationships" : "http://localhost:7575/db/data/node/77/relationships/out",
  "data" : {
    "bbox" : [ 15.3, 60.1, 16.2, 60.1 ],
    "wkt" : "LINESTRING (16.2 60.1, 15.3 60.1)",
    "gtype" : 2
  },
  "traverse" : "http://localhost:7575/db/data/node/77/traverse/{returnType}",
  "all_typed_relationships" : "http://localhost:7575/db/data/node/77/relationships/all/{-list|&|types}",
  "property" : "http://localhost:7575/db/data/node/77/properties/{key}",
  "self" : "http://localhost:7575/db/data/node/77",
  "properties" : "http://localhost:7575/db/data/node/77/properties",
  "outgoing_typed_relationships" : "http://localhost:7575/db/data/node/77/relationships/out/{-list|&|types}",
  "incoming_relationships" : "http://localhost:7575/db/data/node/77/relationships/in",
  "extensions" : {
  },
  "create_relationship" : "http://localhost:7575/db/data/node/77/relationships",
  "paged_traverse" : "http://localhost:7575/db/data/node/77/paged/traverse/{returnType}{?pageSize,leaseTime}",
  "all_relationships" : "http://localhost:7575/db/data/node/77/relationships/all",
  "incoming_typed_relationships" : "http://localhost:7575/db/data/node/77/relationships/in/{-list|&|types}"
} ]

Create a spatial index

Add that node to the spatial layer.

Final-Graph-create-a-spatial-index.svg
Figure 7. Final Graph

Example request

  • POST http://localhost:7575/db/data/index/node/

  • Accept: application/json; charset=UTF-8

  • Content-Type: application/json

{
  "name" : "geom",
  "config" : {
    "provider" : "spatial",
    "geometry_type" : "point",
    "lat" : "lat",
    "lon" : "lon"
  }
}

Example response

  • 201: Created

  • Content-Type: application/json; charset=UTF-8

  • Location: http://localhost:7575/db/data/index/node/geom/

{
  "template" : "http://localhost:7575/db/data/index/node/geom/{key}/{value}",
  "provider" : "spatial",
  "geometry_type" : "point",
  "lat" : "lat",
  "lon" : "lon"
}

Create a node with spatial data

Create a node with some spatial data like lon and lat attached.

Final-Graph-create-a-node-with-spatial-data.svg
Figure 8. Final Graph

Example request

  • POST http://localhost:7575/db/data/node

  • Accept: application/json; charset=UTF-8

  • Content-Type: application/json

{
  "lat" : 60.1,
  "lon" : 15.2
}

Example response

  • 201: Created

  • Content-Type: application/json; charset=UTF-8

  • Location: http://localhost:7575/db/data/node/59

{
  "extensions" : {
  },
  "paged_traverse" : "http://localhost:7575/db/data/node/59/paged/traverse/{returnType}{?pageSize,leaseTime}",
  "labels" : "http://localhost:7575/db/data/node/59/labels",
  "outgoing_relationships" : "http://localhost:7575/db/data/node/59/relationships/out",
  "traverse" : "http://localhost:7575/db/data/node/59/traverse/{returnType}",
  "all_typed_relationships" : "http://localhost:7575/db/data/node/59/relationships/all/{-list|&|types}",
  "property" : "http://localhost:7575/db/data/node/59/properties/{key}",
  "all_relationships" : "http://localhost:7575/db/data/node/59/relationships/all",
  "self" : "http://localhost:7575/db/data/node/59",
  "properties" : "http://localhost:7575/db/data/node/59/properties",
  "outgoing_typed_relationships" : "http://localhost:7575/db/data/node/59/relationships/out/{-list|&|types}",
  "incoming_relationships" : "http://localhost:7575/db/data/node/59/relationships/in",
  "incoming_typed_relationships" : "http://localhost:7575/db/data/node/59/relationships/in/{-list|&|types}",
  "create_relationship" : "http://localhost:7575/db/data/node/59/relationships",
  "data" : {
    "lon" : 15.2,
    "lat" : 60.1
  }
}

Add a node to the spatial index

Add the node we created to the spatial index.

Final-Graph-add-a-node-to-the-spatial-index.svg
Figure 9. Final Graph

Example request

  • POST http://localhost:7575/db/data/ext/SpatialPlugin/graphdb/addNodeToLayer

  • Accept: application/json; charset=UTF-8

  • Content-Type: application/json

{
  "layer" : "geom",
  "node" : "http://localhost:7575/db/data/node/54"
}

Example response

  • 200: OK

  • Content-Type: application/json; charset=UTF-8

[ {
  "labels" : "http://localhost:7575/db/data/node/54/labels",
  "outgoing_relationships" : "http://localhost:7575/db/data/node/54/relationships/out",
  "data" : {
    "lon" : 15.2,
    "bbox" : [ 15.2, 60.1, 15.2, 60.1 ],
    "gtype" : 1,
    "lat" : 60.1
  },
  "traverse" : "http://localhost:7575/db/data/node/54/traverse/{returnType}",
  "all_typed_relationships" : "http://localhost:7575/db/data/node/54/relationships/all/{-list|&|types}",
  "property" : "http://localhost:7575/db/data/node/54/properties/{key}",
  "self" : "http://localhost:7575/db/data/node/54",
  "properties" : "http://localhost:7575/db/data/node/54/properties",
  "outgoing_typed_relationships" : "http://localhost:7575/db/data/node/54/relationships/out/{-list|&|types}",
  "incoming_relationships" : "http://localhost:7575/db/data/node/54/relationships/in",
  "extensions" : {
  },
  "create_relationship" : "http://localhost:7575/db/data/node/54/relationships",
  "paged_traverse" : "http://localhost:7575/db/data/node/54/paged/traverse/{returnType}{?pageSize,leaseTime}",
  "all_relationships" : "http://localhost:7575/db/data/node/54/relationships/all",
  "incoming_typed_relationships" : "http://localhost:7575/db/data/node/54/relationships/in/{-list|&|types}"
} ]

Find geometries in a bounding box

Find geometries in a bounding box.

Final-Graph-find-geometries-in-a-bounding-box.svg
Figure 10. Final Graph

Example request

  • POST http://localhost:7575/db/data/ext/SpatialPlugin/graphdb/findGeometriesInBBox

  • Accept: application/json; charset=UTF-8

  • Content-Type: application/json

{
  "layer" : "geom",
  "minx" : 15.0,
  "maxx" : 15.3,
  "miny" : 60.0,
  "maxy" : 61.0
}

Example response

  • 200: OK

  • Content-Type: application/json; charset=UTF-8

[ {
  "labels" : "http://localhost:7575/db/data/node/72/labels",
  "outgoing_relationships" : "http://localhost:7575/db/data/node/72/relationships/out",
  "data" : {
    "lon" : 15.2,
    "bbox" : [ 15.2, 60.1, 15.2, 60.1 ],
    "gtype" : 1,
    "lat" : 60.1
  },
  "traverse" : "http://localhost:7575/db/data/node/72/traverse/{returnType}",
  "all_typed_relationships" : "http://localhost:7575/db/data/node/72/relationships/all/{-list|&|types}",
  "property" : "http://localhost:7575/db/data/node/72/properties/{key}",
  "self" : "http://localhost:7575/db/data/node/72",
  "properties" : "http://localhost:7575/db/data/node/72/properties",
  "outgoing_typed_relationships" : "http://localhost:7575/db/data/node/72/relationships/out/{-list|&|types}",
  "incoming_relationships" : "http://localhost:7575/db/data/node/72/relationships/in",
  "extensions" : {
  },
  "create_relationship" : "http://localhost:7575/db/data/node/72/relationships",
  "paged_traverse" : "http://localhost:7575/db/data/node/72/paged/traverse/{returnType}{?pageSize,leaseTime}",
  "all_relationships" : "http://localhost:7575/db/data/node/72/relationships/all",
  "incoming_typed_relationships" : "http://localhost:7575/db/data/node/72/relationships/in/{-list|&|types}"
} ]

Find geometries within distance

Find geometries within a distance.

Final-Graph-find-geometries-within--distance.svg
Figure 11. Final Graph

Example request

  • POST http://localhost:7575/db/data/ext/SpatialPlugin/graphdb/findGeometriesWithinDistance

  • Accept: application/json; charset=UTF-8

  • Content-Type: application/json

{
  "layer" : "geom",
  "pointX" : 15.0,
  "pointY" : 60.0,
  "distanceInKm" : 100
}

Example response

  • 200: OK

  • Content-Type: application/json; charset=UTF-8

[ {
  "labels" : "http://localhost:7575/db/data/node/27/labels",
  "outgoing_relationships" : "http://localhost:7575/db/data/node/27/relationships/out",
  "data" : {
    "lon" : 15.2,
    "bbox" : [ 15.2, 60.1, 15.2, 60.1 ],
    "gtype" : 1,
    "lat" : 60.1
  },
  "traverse" : "http://localhost:7575/db/data/node/27/traverse/{returnType}",
  "all_typed_relationships" : "http://localhost:7575/db/data/node/27/relationships/all/{-list|&|types}",
  "property" : "http://localhost:7575/db/data/node/27/properties/{key}",
  "self" : "http://localhost:7575/db/data/node/27",
  "properties" : "http://localhost:7575/db/data/node/27/properties",
  "outgoing_typed_relationships" : "http://localhost:7575/db/data/node/27/relationships/out/{-list|&|types}",
  "incoming_relationships" : "http://localhost:7575/db/data/node/27/relationships/in",
  "extensions" : {
  },
  "create_relationship" : "http://localhost:7575/db/data/node/27/relationships",
  "paged_traverse" : "http://localhost:7575/db/data/node/27/paged/traverse/{returnType}{?pageSize,leaseTime}",
  "all_relationships" : "http://localhost:7575/db/data/node/27/relationships/all",
  "incoming_typed_relationships" : "http://localhost:7575/db/data/node/27/relationships/in/{-list|&|types}"
} ]

Find geometries in a bounding box using cypher

Find geometries in a bounding box.

Final-Graph-find-geometries-in-a-bounding-box-using-cypher.svg
Figure 12. Final Graph

Example request

  • POST http://localhost:7575/db/data/cypher

  • Accept: application/json; charset=UTF-8

  • Content-Type: application/json

{
  "query" : "start node = node:geom('bbox:[15.0,15.3,60.0,60.2]') return node"
}

Example response

  • 200: OK

  • Content-Type: application/json; charset=UTF-8

{
  "columns" : [ "node" ],
  "data" : [ [ {
    "labels" : "http://localhost:7575/db/data/node/10/labels",
    "outgoing_relationships" : "http://localhost:7575/db/data/node/10/relationships/out",
    "data" : {
      "lon" : 15.2,
      "lat" : 60.1
    },
    "traverse" : "http://localhost:7575/db/data/node/10/traverse/{returnType}",
    "all_typed_relationships" : "http://localhost:7575/db/data/node/10/relationships/all/{-list|&|types}",
    "property" : "http://localhost:7575/db/data/node/10/properties/{key}",
    "self" : "http://localhost:7575/db/data/node/10",
    "properties" : "http://localhost:7575/db/data/node/10/properties",
    "outgoing_typed_relationships" : "http://localhost:7575/db/data/node/10/relationships/out/{-list|&|types}",
    "incoming_relationships" : "http://localhost:7575/db/data/node/10/relationships/in",
    "extensions" : {
    },
    "create_relationship" : "http://localhost:7575/db/data/node/10/relationships",
    "paged_traverse" : "http://localhost:7575/db/data/node/10/paged/traverse/{returnType}{?pageSize,leaseTime}",
    "all_relationships" : "http://localhost:7575/db/data/node/10/relationships/all",
    "incoming_typed_relationships" : "http://localhost:7575/db/data/node/10/relationships/in/{-list|&|types}"
  } ] ]
}

Find geometries within distance using cypher

Find geometries within a distance, using Cypher.

Final-Graph-find-geometries-within--distance-using-cypher.svg
Figure 13. Final Graph

Example request

  • POST http://localhost:7575/db/data/cypher

  • Accept: application/json; charset=UTF-8

  • Content-Type: application/json

{
  "query" : "start node = node:geom('withinDistance:[60.0,15.0, 100.0]') return node"
}

Example response

  • 200: OK

  • Content-Type: application/json; charset=UTF-8

{
  "columns" : [ "node" ],
  "data" : [ [ {
    "labels" : "http://localhost:7575/db/data/node/21/labels",
    "outgoing_relationships" : "http://localhost:7575/db/data/node/21/relationships/out",
    "data" : {
      "lon" : 15.2,
      "lat" : 60.1
    },
    "traverse" : "http://localhost:7575/db/data/node/21/traverse/{returnType}",
    "all_typed_relationships" : "http://localhost:7575/db/data/node/21/relationships/all/{-list|&|types}",
    "property" : "http://localhost:7575/db/data/node/21/properties/{key}",
    "self" : "http://localhost:7575/db/data/node/21",
    "properties" : "http://localhost:7575/db/data/node/21/properties",
    "outgoing_typed_relationships" : "http://localhost:7575/db/data/node/21/relationships/out/{-list|&|types}",
    "incoming_relationships" : "http://localhost:7575/db/data/node/21/relationships/in",
    "extensions" : {
    },
    "create_relationship" : "http://localhost:7575/db/data/node/21/relationships",
    "paged_traverse" : "http://localhost:7575/db/data/node/21/paged/traverse/{returnType}{?pageSize,leaseTime}",
    "all_relationships" : "http://localhost:7575/db/data/node/21/relationships/all",
    "incoming_typed_relationships" : "http://localhost:7575/db/data/node/21/relationships/in/{-list|&|types}"
  } ] ]
}

include::dev/rest-api/find-geometries-close-to-a-point.asciidoc

Neo4j Spatial Geoserver Plugin

Important
Tested with: GeoServer 2.1.1

Building

mvn clean install

Deployment into Geoserver

  • unzip the target/xxxx-server-plugin.zip into $GEOSERVER_HOME/webapps/geoserver/WEB-INF/lib

  • restart geoserver

  • configure a new workspace

  • configure a new datasource neo4j in your workspace. Point the "The directory path of the Neo4j database:" parameter to the relative (form the GeoServer working dir) or aboslute path to a Neo4j Spatial database with layers (see Neo4j Spatial)

  • in Layers, do "Add new resource" and choose your Neo4j datastore to see the exisitng Neo4j Spatial layers and add them.

Testing in GeoServer trunk

  • check out the geoserver source

svn co https://svn.codehaus.org/geoserver/trunk geoserver-trunk
  • build the source

cd geoserver-trunk
mvn clean install
cd src/web/app
mvn jetty:run
    <profile>
      <id>neo4j</id>
      <dependencies>
        <dependency>
          <groupId>org.neo4j</groupId>
          <artifactId>neo4j-spatial</artifactId>
          <version>0.7-SNAPSHOT</version>
        </dependency>
      </dependencies>
    </profile>
  • start the GeoServer webapp again with the added neo4j profile

cd $GEOSERVER_SRC/src/web/app
mvn jetty:run -Pneo4j

For more info head over to Neo4j Wiki on Geoserver

Examples

Importing a shapefile

Neo4j-Spatial includes a utility for importing ESRI Shapefile data. The ShapefileImporter will create a new Layer for each Shapefile imported, and will store each Geometry as WKB in a single property of a single node. All attributes of the Feature will be stored as additional properties of that node. For more information on how this is implemented, you can refer to the classes WKBGeometryEncoder. However, you do not need to know any of that to use this feature. Consider the simple code below.

GraphDatabaseService database = new EmbeddedGraphDatabase(databasePath);
try {
    ShapefileImporter importer = new ShapefileImporter(database);
    importer.importFile("shp/highway.shp", "highway", Charset.forName("UTF-8"));
} finally {
    database.shutdown();
}

This code will import the layer highway from the file shp/highway.shp which is included in the source distribution. This layer will be indexed using an RTree and can therefore be queried using any of the supported spatial operations. See the example on querying layers below.

Importing an Open Street Map file

This is more complex because the current OSMImporter class runs in two phases, the first requiring a batch-inserter on the database. The is ongoing work to allow for a non-batch-inserter on the entire process, and possibly when you have read this that will already be available. Refer to the unit tests in classes TestDynamicLayers and TestOSMImport for the latest code for importing OSM data. For example:

OSMImporter importer = new OSMImporter("map.osm");
importer.setCharset(Charset.forName("UTF-8"));
BatchInserter batchInserter = BatchInserters.inserter(databasePath);
importer.importFile(batchInserter, "map.osm", false);
batchInserter.shutdown();

GraphDatabaseService db = new EmbeddedGraphDatabase(databasePath);
importer.reIndex(db);
db.shutdown();

This code will import the map.osm Open Street Map file, populating the database with a little over 200 geometries, including streets, buildings and points of interest.

Executing a spatial query

Assuming you imported the map.osm file as in the previous example, you can now perform spatial searches on the data. The following example will search within a rectangle for a whatever geometries it can find:

GraphDatabaseService database = new EmbeddedGraphDatabase(databasePath);
try {
    SpatialDatabaseService spatialService = new SpatialDatabaseService(database);
    Layer layer = spatialService.getLayer("map.osm");
    LayerIndexReader spatialIndex = layer.getIndex();
    System.out.println("Have " + spatialIndex.count() + " geometries in " + spatialIndex.getBoundingBox());

    Envelope bbox = new Envelope(12.94, 12.96, 56.04, 56.06);
    try (Transaction tx = database.beginTx()) {
        List<SpatialDatabaseRecord> results = GeoPipeline
            .startIntersectWindowSearch(layer, bbox)
            .toSpatialDatabaseRecordList();

        doGeometryTestsOnResults(bbox, results);
        tx.success();
    }
} finally {
    database.shutdown();
}

For more examples of query code, refer to the test code in the LayerTest and the SpatialTest classes. Also review the classes in the org.neo4j.gis.spatial.query package for the full range or search queries currently implemented.

Export a shapefile

The ESRI Shapefile that we imported in the first example above was actually created by Neo4j-Spatial. It is possible to use the results of a query, or a DynamicLayer, to create a new Shapefile using the ShapefileExporter. If we were to export the complete layer created from importing a shapefile, we would not achieve much, but we can could use this capability to create shapefiles that are subsets of other layers, or that are created from data in another format.

SpatialDatabaseService spatialService = new SpatialDatabaseService(database);
try (Transaction tx = database.beginTx()) {
    OSMLayer layer = (OSMLayer) spatialService.getLayer("map.osm");
    DynamicLayerConfig wayLayer = layer.addSimpleDynamicLayer(Constants.GTYPE_LINESTRING);
    ShapefileExporter shpExporter = new ShapefileExporter(database);
    shpExporter.exportLayer(wayLayer.getName());
    tx.success();
}

This example shows how we can import an OSM dataset, which contains data with a number of different Geometry types, and then by using a DynamicLayer to select for only geometries of type LineString, we can export all the OSM ways to a Shapefile. This is particularly important when you consider that ESRI Shapefile format does not allow more than one Geometry type per shapefile.

SpatialDatabaseService spatialService = new SpatialDatabaseService(database);
Layer layer = spatialService.getLayer("map.osm");
LayerIndexReader spatialIndex = layer.getIndex();
System.out.println("Have " + spatialIndex.count() + " geometries in " + spatialIndex.getBoundingBox());

Envelope bbox = new Envelope(12.94, 12.96, 56.04, 56.06);
try (Transaction tx = database.beginTx()) {
    List<SpatialDatabaseRecord> results = GeoPipeline
        .startIntersectWindowSearch(layer, bbox)
        .toSpatialDatabaseRecordList();

    spatialService.createResultsLayer("results", results);
    ShapefileExporter shpExporter = new ShapefileExporter(database);
    shpExporter.exportLayer("results");
    tx.success();

This time we import the same OSM model, but query for all geometries inside a envelope, and export that to a new Shapefile.

Using Neo4j Spatial with uDig

For more info head over to Neo4j Wiki on uDig

GeoPipes

Base Layers

These are the different base layers being used for the following examples:

OsmLayer:

osmLayer.png

IntersectionLayer:

intersectionLayer.png

LinesLayer:

linesLayer.png

BoxesLayer:

boxesLayer.png

ConcaveLayer:

concaveLayer.png

EqualLayer:

equalLayer.png

GeoPipes Examples

Below follows a non-exhaustive lists of interesting GeoPipes that can be chained together to contruct a geoprocessing system in while trying to keep the processing as lazy as possible using iterators through the different steps.

Search within geometry

This pipe performs a search within a geometry in this example, both OSM street geometries should be found in when searching with an enclosing rectangle Envelope.

Example:

GeoPipeline pipeline = GeoPipeline
.startWithinSearch(osmLayer, osmLayer.getGeometryFactory().toGeometry(new Envelope(10, 20, 50, 60)));

Intersecting windows

The FilterIntersectWindow pipe finds geometries that intersects a given rectangle. This pipeline:

GeoPipeline pipeline = GeoPipeline
    .start( boxesLayer )
    .windowIntersectionFilter(new Envelope( 0, 10, 0, 10 ) );

will output:

intersecting_windows.png

Intersect All

The IntersectAll pipe intersects geometries of every item contained in the pipeline. It groups every item in the pipeline in a single item containing the geometry output of the intersection.

Example:

GeoPipeline pipeline = GeoPipeline.start( intersectionLayer ).intersectAll();

Output:

intersect_all.png

Unite All

The Union All pipe unites geometries of every item contained in the pipeline. This pipe groups every item in the pipeline in a single item containing the geometry output of the union.

Example:

GeoPipeline pipeline = GeoPipeline.start( intersectionLayer ).unionAll();

Output:

unite_all.png

Extract Points

The Extract Points pipe extracts every point from a geometry.

Example:

GeoPipeline pipeline = GeoPipeline.start( boxesLayer ).extractPoints();

Output:

extract_points.png

Filter by cql using complex cql

This filter will apply the provided CQL expression to the different geometries and only let the matching ones pass.

Example:

long counter  = GeoPipeline.start( osmLayer ).cqlFilter(
                "highway is not null and geometryType(the_geom) = 'LineString'" ).count();

Boundary

The boundary pipe calculates boundary of every geometry in the pipeline.

Example:

GeoPipeline pipeline = GeoPipeline.start( boxesLayer ).toBoundary();

Output:

boundary.png

Centroid

The Centroid pipe calculates geometry centroid.

Example:

GeoPipeline pipeline = GeoPipeline.start( boxesLayer ).toCentroid();

Output:

centroid.png

Convex Hull

The ConvexHull pipe calculates geometry convex hull.

Example:

GeoPipeline pipeline = GeoPipeline.start( concaveLayer ).toConvexHull();

Output:

convex_hull.png

Densify

The Densify pipe inserts extra vertices along the line segments in the geometry. The densified geometry contains no line segment which is longer than the given distance tolerance.

Example:

GeoPipeline pipeline = GeoPipeline.start( concaveLayer ).densify( 5 ).extractPoints();

Output:

densify.png

Envelope

The Envelope pipe computes the minimum bounding box of item geometry. Example:

GeoPipeline pipeline = GeoPipeline
    .start( linesLayer )
    .toEnvelope();

Output:

envelope.png

Export to GML

This pipe exports every geometry as a GML snippet.

Example:

GeoPipeline pipeline = GeoPipeline.start( boxesLayer ).createGML();
for ( GeoPipeFlow flow : pipeline ) {
    System.out.println(flow.getProperties().get( "GML" ));
 }

Output:

<gml:Polygon>
  <gml:outerBoundaryIs>
    <gml:LinearRing>
      <gml:coordinates>
        12.0,26.0 12.0,27.0 13.0,27.0 13.0,26.0 12.0,26.0
      </gml:coordinates>
    </gml:LinearRing>
  </gml:outerBoundaryIs>
</gml:Polygon>
<gml:Polygon>
  <gml:outerBoundaryIs>
    <gml:LinearRing>
      <gml:coordinates>
        2.0,3.0 2.0,5.0 6.0,5.0 6.0,3.0 2.0,3.0
      </gml:coordinates>
    </gml:LinearRing>
  </gml:outerBoundaryIs>
</gml:Polygon>

Intersection

The Intersection pipe computes a geometry representing the intersection between item geometry and the given geometry.

Example:

WKTReader reader = new WKTReader( intersectionLayer.getGeometryFactory() );
Geometry geometry = reader.read( "POLYGON ((3 3, 3 5, 7 7, 7 3, 3 3))" );
GeoPipeline pipeline = GeoPipeline.start( intersectionLayer ).intersect( geometry );

Output:

intersection.png

Union

The Union pipe unites item geometry with a given geometry.

Example:

WKTReader reader = new WKTReader( intersectionLayer.getGeometryFactory() );
Geometry geometry = reader.read( "POLYGON ((3 3, 3 5, 7 7, 7 3, 3 3))" );
SearchFilter filter = new SearchIntersectWindow( intersectionLayer, new Envelope( 7, 10, 7, 10 ) );
GeoPipeline pipeline = GeoPipeline.start( intersectionLayer, filter ).union( geometry );

Output:

union.png

Start Point

The StartPoint pipe finds the starting point of item geometry. Example:

GeoPipeline pipeline = GeoPipeline
    .start( linesLayer )
    .toStartPoint();

Output:

start_point.png

End Point

The EndPoint pipe finds the ending point of item geometry. Example:

GeoPipeline pipeline = GeoPipeline
    .start( linesLayer )
    .toEndPoint();

Output:

end_point.png

Max

The Max pipe computes the maximum value of the specified property and discard items with a value less than the maximum.

Example:

GeoPipeline pipeline = GeoPipeline.start( boxesLayer )
    .calculateArea()
    .getMax( "Area" );

Output:

max.png

Min

The Min pipe computes the minimum value of the specified property and discard items with a value greater than the minimum.

Example:

GeoPipeline pipeline = GeoPipeline.start( boxesLayer )
    .calculateArea()
    .getMin( "Area" );

Output:

min.png

Break up all geometries into points and make density islands

A more complex Open Street Map example.

This example demostrates the some pipes chained together to make a full geoprocessing pipeline.

Example:

//step1
GeoPipeline pipeline = OSMGeoPipeline.startOsm( osmLayer )
        //step2
    .extractOsmPoints()
    //step3
    .groupByDensityIslands( 0.0005 )
    //step4
    .toConvexHull()
    //step5
    .toBuffer( 0.0004 );

Step1

step1_break_up_all_geometries_into_points_and_make_density_islands.png

Step2

step2_break_up_all_geometries_into_points_and_make_density_islands.png

Step3

step3_break_up_all_geometries_into_points_and_make_density_islands.png

Step4

step4_break_up_all_geometries_into_points_and_make_density_islands.png

Step5

step5_break_up_all_geometries_into_points_and_make_density_islands.png