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

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

Get Started

The easiest way to get started with neo4j-spatial is to grab the server-plugin-*.jar from the latest release, copy it to your $NEO4J_HOME/plugins and restart your Neo4j server.

From there you can use all the Neo4j Spatial Procedures in your Cypher queries to add Nodes to the spatial index and perform a number of spatial point, distance and intersection queries.

Simple Example
CALL spatial.addPointLayer('geom');
CALL spatial.layers();

CREATE (n:Node {latitude:60.1,longitude:15.2})
WITH n
CALL spatial.addNode('geom',n) YIELD node
RETURN node;

CALL spatial.bbox('geom',{lon:15.0,lat:60.0}, {lon:15.3, lat:61.0});

A good example blog post for how to use them with one of the neo4j drivers, is Spatial Procedures in Legis Graph by William Lyon (Part 1).

An general introduction to Neo4j Spatial and a simple example on how to use the Spatial Java APIs can be found in this blog post by Craig Taverner.

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.

  • EditableLayer(Impl) - this is the default editable layer implementation and can handle any type of simple geometry. This includes 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.

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.

Neo4j Spatial Procedures

Neo4j Spatial is also packaged as a ZIP file that can be unzipped into the Neo4j Server $NEO4J_HOME/plugins directory. After restarting the server, you should be able to use the following procedure calls from the Cypher query language.

Table 1. Available Spatial Procedures
name description signature

"spatial.addLayer"

"Adds a new layer with the given type (see spatial.getAllLayerTypes) and configuration, returns the layer root node"

"spatial.addLayer(name :: STRING?, type :: STRING?, encoderConfig :: STRING?) :: (node :: NODE?)"

"spatial.addLayerWithEncoder"

"Adds a new layer with the given encoder class and configuration, returns the layer root node"

"spatial.addLayerWithEncoder(name :: STRING?, encoder :: STRING?, encoderConfig :: STRING?) :: (node :: NODE?)"

"spatial.addNode"

"Adds the given node to the layer, returns the geometry-node"

"spatial.addNode(layerName :: STRING?, node :: NODE?) :: (node :: NODE?)"

"spatial.addNodes"

"Adds the given nodes list to the layer, returns the count"

"spatial.addNodes(layerName :: STRING?, nodes :: LIST? OF NODE?) :: (count :: INTEGER?)"

"spatial.addPointLayer"

"Adds a new simple point layer, returns the layer root node"

"spatial.addPointLayer(name :: STRING?) :: (node :: NODE?)"

"spatial.addPointLayerWithConfig"

"Adds a new simple point layer with the given configuration, returns the layer root node"

"spatial.addPointLayerWithConfig(name :: STRING?, encoderConfig :: STRING?) :: (node :: NODE?)"

"spatial.addPointLayerXY"

"Adds a new simple point layer with the given properties for x and y coordinates, returns the layer root node"

"spatial.addPointLayerXY(name :: STRING?, xProperty :: STRING?, yProperty :: STRING?) :: (node :: NODE?)"

"spatial.addWKT"

"Adds the given WKT string to the layer, returns the created geometry node"

"spatial.addWKT(layerName :: STRING?, geometry :: STRING?) :: (node :: NODE?)"

"spatial.addWKTLayer"

"Adds a new WKT layer with the given node property to hold the WKT string, returns the layer root node"

"spatial.addWKTLayer(name :: STRING?, nodePropertyName :: STRING?) :: (node :: NODE?)"

"spatial.addWKTs"

"Adds the given WKT string list to the layer, returns the created geometry nodes"

"spatial.addWKTs(layerName :: STRING?, geometry :: LIST? OF STRING?) :: (node :: NODE?)"

"spatial.asExternalGeometry"

"Returns a geometry object as an external geometry type to be returned to a client"

"spatial.asExternalGeometry(geometry :: ANY?) :: (geometry :: ANY?)"

"spatial.asGeometry"

"Returns a geometry object as an internal cypher geometry type, to be passed to other procedures but not returned to a client"

"spatial.asGeometry(geometry :: ANY?) :: (geometry :: ANY?)"

"spatial.bbox"

"Finds all geometry nodes in the given layer within the lower left and upper right coordinates of a box"

"spatial.bbox(layerName :: STRING?, min :: ANY?, max :: ANY?) :: (node :: NODE?)"

"spatial.closest"

"Finds all geometry nodes in the layer within the distance to the given coordinate"

"spatial.closest(layerName :: STRING?, coordinate :: ANY?, distanceInKm :: FLOAT?) :: (node :: NODE?)"

"spatial.decodeGeometry"

"Returns a geometry of a layer node as internal cypher geometry type, to be passed to other procedures but not returned to a client"

"spatial.decodeGeometry(layerName :: STRING?, node :: NODE?) :: (geometry :: ANY?)"

"spatial.getFeatureAttributes"

"Returns feature attributes of the given layer"

"spatial.getFeatureAttributes(name :: STRING?) :: (name :: STRING?)"

"spatial.importOSM"

"Imports the the provided osm-file from URI to a layer of the same name, returns the count of data added"

"spatial.importOSM(uri :: STRING?) :: (count :: INTEGER?)"

"spatial.importOSMToLayer"

"Imports the the provided osm-file from URI to a layer, returns the count of data added"

"spatial.importOSMToLayer(layerName :: STRING?, uri :: STRING?) :: (count :: INTEGER?)"

"spatial.importShapefile"

"Imports the the provided shape-file from URI to a layer of the same name, returns the count of data added"

"spatial.importShapefile(uri :: STRING?) :: (count :: INTEGER?)"

"spatial.importShapefileToLayer"

"Imports the the provided shape-file from URI to the given layer, returns the count of data added"

"spatial.importShapefileToLayer(layerName :: STRING?, uri :: STRING?) :: (count :: INTEGER?)"

"spatial.intersects"

"Returns all geometry nodes that intersect the given geometry (shape, polygon) in the layer"

"spatial.intersects(layerName :: STRING?, geometry :: ANY?) :: (node :: NODE?)"

"spatial.layer"

"Returns the layer root node for the given layer name"

"spatial.layer(name :: STRING?) :: (node :: NODE?)"

"spatial.layerTypes"

"Returns the different registered layer types"

"spatial.layerTypes() :: (name :: STRING?, signature :: STRING?)"

"spatial.layers"

"Returns name, and details for all layers"

"spatial.layers() :: (name :: STRING?, signature :: STRING?)"

"spatial.procedures"

"Lists all spatial procedures with name and signature"

"spatial.procedures() :: (name :: STRING?, signature :: STRING?)"

"spatial.removeLayer"

"Removes the given layer"

"spatial.removeLayer(name :: STRING?) :: VOID"

"spatial.setFeatureAttributes"

"Sets the feature attributes of the given layer"

"spatial.setFeatureAttributes(name :: STRING?, attributeNames :: LIST? OF STRING?) :: (node :: NODE?)"

"spatial.updateFromWKT"

"Internal procedure, updates the geometry node with the given id with a new WKT string"

"spatial.updateFromWKT(layerName :: STRING?, geometry :: STRING?, geometryNodeId :: INTEGER?) :: (node :: NODE?)"

"spatial.withinDistance"

"Returns all geometry nodes and their ordered distance in the layer within the distance to the given coordinate"

"spatial.withinDistance(layerName :: STRING?, coordinate :: ANY?, distanceInKm :: FLOAT?) :: (node :: NODE?, distance :: FLOAT?)"

Using the Neo4j Spatial Server plugin

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

Finding the plugin

finding_the_plugin

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

Create a pointlayer

create_a_pointlayer

Final Graph
  N21 [
    label = "{Node\[21\]: ReferenceNode|name = \'spatial_root\'\l}"
  ]
  N21 -> N22 [
    color = "#2e3436"
    fontcolor = "#2e3436"
    label = "LAYER\n"
  ]
  N22 [
    label = "{Node\[22\]|layer_class = \'org.neo4j.gis.spatial.SimplePointLayer\'\lgeomencoder_config = \'lon:lat\'\llayer = \'geom\'\lctime = 1496312259412\lgeomencoder = \'org.neo4j.gis.spatial.encoders.SimplePointEncoder\'\l}"
  ]
  N22 -> N24 [
    color = "#4e9a06"
    fontcolor = "#4e9a06"
    label = "RTREE_METADATA\n"
  ]
  N22 -> N23 [
    color = "#a40000"
    fontcolor = "#a40000"
    label = "RTREE_ROOT\n"
  ]
  N23 [
    label = "{Node\[23\]|}"
  ]
  N24 [
    label = "{Node\[24\]|maxNodeReferences = 100\l}"
  ]

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

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

Create a WKT layer

create_a_WKT_layer

Final Graph
  N25 [
    label = "{Node\[25\]: ReferenceNode|name = \'spatial_root\'\l}"
  ]
  N25 -> N26 [
    color = "#2e3436"
    fontcolor = "#2e3436"
    label = "LAYER\n"
  ]
  N26 [
    label = "{Node\[26\]|layer_class = \'org.neo4j.gis.spatial.EditableLayerImpl\'\lgeomencoder_config = \'wkt\'\llayer = \'geom\'\lctime = 1496312259487\lgeomencoder = \'org.neo4j.gis.spatial.WKTGeometryEncoder\'\l}"
  ]
  N26 -> N27 [
    color = "#4e9a06"
    fontcolor = "#4e9a06"
    label = "RTREE_ROOT\n"
  ]
  N26 -> N28 [
    color = "#a40000"
    fontcolor = "#a40000"
    label = "RTREE_METADATA\n"
  ]
  N27 [
    label = "{Node\[27\]|}"
  ]
  N28 [
    label = "{Node\[28\]|maxNodeReferences = 100\l}"
  ]

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

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

Find layer

find_layer

Final Graph
  N16 [
    label = "{Node\[16\]: ReferenceNode|name = \'spatial_root\'\l}"
  ]
  N16 -> N17 [
    color = "#2e3436"
    fontcolor = "#2e3436"
    label = "LAYER\n"
  ]
  N17 [
    label = "{Node\[17\]|layer_class = \'org.neo4j.gis.spatial.EditableLayerImpl\'\lgeomencoder_config = \'wkt\'\llayer = \'geom\'\lctime = 1496312259252\lgeomencoder = \'org.neo4j.gis.spatial.WKTGeometryEncoder\'\l}"
  ]
  N17 -> N19 [
    color = "#4e9a06"
    fontcolor = "#4e9a06"
    label = "RTREE_METADATA\n"
  ]
  N17 -> N18 [
    color = "#a40000"
    fontcolor = "#a40000"
    label = "RTREE_ROOT\n"
  ]
  N18 [
    label = "{Node\[18\]|bbox = \[15.2, 60.1, 15.3, 60.1\]\l}"
  ]
  N18 -> N20 [
    color = "#204a87"
    fontcolor = "#204a87"
    label = "RTREE_REFERENCE\n"
  ]
  N19 [
    label = "{Node\[19\]|maxNodeReferences = 100\ltotalGeometryCount = 1\l}"
  ]
  N20 [
    label = "{Node\[20\]|gtype = 2\lbbox = \[15.2, 60.1, 15.3, 60.1\]\lwkt = \'LINESTRING (15.2 60.1, 15.3 60.1)\'\l}"
  ]

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

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

Add a WKT geometry to a layer

add_a_WKT_geometry_to_a_layer

Final Graph
  N0 [
    label = "{Node\[0\]: ReferenceNode|name = \'spatial_root\'\l}"
  ]
  N0 -> N1 [
    color = "#2e3436"
    fontcolor = "#2e3436"
    label = "LAYER\n"
  ]
  N1 [
    label = "{Node\[1\]|layer_class = \'org.neo4j.gis.spatial.EditableLayerImpl\'\lgeomencoder_config = \'wkt\'\llayer = \'geom\'\lctime = 1496312257560\lgeomencoder = \'org.neo4j.gis.spatial.WKTGeometryEncoder\'\l}"
  ]
  N1 -> N3 [
    color = "#4e9a06"
    fontcolor = "#4e9a06"
    label = "RTREE_METADATA\n"
  ]
  N1 -> N2 [
    color = "#a40000"
    fontcolor = "#a40000"
    label = "RTREE_ROOT\n"
  ]
  N2 [
    label = "{Node\[2\]|bbox = \[15.2, 60.1, 15.3, 60.1\]\l}"
  ]
  N2 -> N4 [
    color = "#204a87"
    fontcolor = "#204a87"
    label = "RTREE_REFERENCE\n"
  ]
  N3 [
    label = "{Node\[3\]|maxNodeReferences = 100\l}"
  ]
  N4 [
    label = "{Node\[4\]|gtype = 2\lbbox = \[15.2, 60.1, 15.3, 60.1\]\lwkt = \'LINESTRING (15.2 60.1, 15.3 60.1)\'\l}"
  ]

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

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

Update a WKT geometry in a layer

update_a_WKT_geometry_in_a_layer

Final Graph
  N29 [
    label = "{Node\[29\]: ReferenceNode|name = \'spatial_root\'\l}"
  ]
  N29 -> N30 [
    color = "#2e3436"
    fontcolor = "#2e3436"
    label = "LAYER\n"
  ]
  N30 [
    label = "{Node\[30\]|layer_class = \'org.neo4j.gis.spatial.EditableLayerImpl\'\lgeomencoder_config = \'wkt\'\llayer = \'geom\'\lctime = 1496312259629\lgeomencoder = \'org.neo4j.gis.spatial.WKTGeometryEncoder\'\l}"
  ]
  N30 -> N32 [
    color = "#4e9a06"
    fontcolor = "#4e9a06"
    label = "RTREE_METADATA\n"
  ]
  N30 -> N31 [
    color = "#a40000"
    fontcolor = "#a40000"
    label = "RTREE_ROOT\n"
  ]
  N31 [
    label = "{Node\[31\]|bbox = \[15.2, 60.1, 15.3, 60.1\]\l}"
  ]
  N31 -> N33 [
    color = "#204a87"
    fontcolor = "#204a87"
    label = "RTREE_REFERENCE\n"
  ]
  N32 [
    label = "{Node\[32\]|maxNodeReferences = 100\ltotalGeometryCount = 1\l}"
  ]
  N33 [
    label = "{Node\[33\]|gtype = 2\lbbox = \[15.3, 60.1, 16.2, 60.1\]\lwkt = \'LINESTRING (16.2 60.1, 15.3 60.1)\'\l}"
  ]

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" : 33
}

Example response

  • 200: OK

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

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

Find geometries close to a point

find_geometries_close_to_a_point

Final Graph
  N11 [
    label = "{Node\[11\]: ReferenceNode|name = \'spatial_root\'\l}"
  ]
  N11 -> N12 [
    color = "#2e3436"
    fontcolor = "#2e3436"
    label = "LAYER\n"
  ]
  N12 [
    label = "{Node\[12\]|layer_class = \'org.neo4j.gis.spatial.EditableLayerImpl\'\lgeomencoder_config = \'wkt\'\llayer = \'geom\'\lctime = 1496312258949\lgeomencoder = \'org.neo4j.gis.spatial.WKTGeometryEncoder\'\l}"
  ]
  N12 -> N14 [
    color = "#4e9a06"
    fontcolor = "#4e9a06"
    label = "RTREE_METADATA\n"
  ]
  N12 -> N13 [
    color = "#a40000"
    fontcolor = "#a40000"
    label = "RTREE_ROOT\n"
  ]
  N13 [
    label = "{Node\[13\]|bbox = \[15.2, 60.1, 15.3, 60.1\]\l}"
  ]
  N13 -> N15 [
    color = "#204a87"
    fontcolor = "#204a87"
    label = "RTREE_REFERENCE\n"
  ]
  N14 [
    label = "{Node\[14\]|maxNodeReferences = 100\ltotalGeometryCount = 1\l}"
  ]
  N15 [
    label = "{Node\[15\]|gtype = 2\lbbox = \[15.2, 60.1, 15.3, 60.1\]\lwkt = \'LINESTRING (15.2 60.1, 15.3 60.1)\'\l}"
  ]

Example request

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

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

  • Content-Type: application/json

{
  "layer" : "geom",
  "pointX" : 15.2,
  "pointY" : 60.1,
  "distanceInKm" : 1.0
}

Example response

  • 200: OK

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

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

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

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 = graphDb();
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 = getBatchInserter();
importer.importFile(batchInserter, "map.osm", false);
//batchInserter.shutdown();
//GraphDatabaseService db = new GraphDatabaseFactory().newEmbeddedDatabase(databasePath);
reActivateDatabase(false, false, false);
GraphDatabaseService db = graphDb();
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 = graphDb();
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.

GeoPipes

Base Layers

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

OsmLayer:

osmLayer

IntersectionLayer:

intersectionLayer

LinesLayer:

linesLayer

BoxesLayer:

boxesLayer

ConcaveLayer:

concaveLayer

EqualLayer:

equalLayer

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

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

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

Extract Points

The Extract Points pipe extracts every point from a geometry.

Example:
GeoPipeline pipeline = GeoPipeline.start( boxesLayer ).extractPoints();
extract points

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

Centroid

The Centroid pipe calculates geometry centroid.

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

Output:

centroid

Convex Hull

The ConvexHull pipe calculates geometry convex hull.

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

Output:

convex hull

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

Envelope

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

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

Output:

envelope

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

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

Start Point

The StartPoint pipe finds the starting point of item geometry.

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

Output:

start point

End Point

The EndPoint pipe finds the ending point of item geometry.

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

Output:

end point

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

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

Break up all geometries into points and make density islands

A more complex Open Street Map example.

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

Output:

step1 break up all geometries into points and make density islands
Figure 1. Step1
step2 break up all geometries into points and make density islands
Figure 2. Step2
step3 break up all geometries into points and make density islands
Figure 3. Step3
step4 break up all geometries into points and make density islands
Figure 4. Step4
step5 break up all geometries into points and make density islands
Figure 5. Step5

Appendix: Integrations

Neo4j Spatial in uDig

Neo4j Spatial supports Geotools, and therefor also Geotools based platforms like GeoServer and uDig. For information on setting up and running Neo4j Spatial in GeoServer, see the wiki page [geoserver].

uDig SDK

Here we will discuss how to setup a development environment for developing code for uDig making use of Neo4j Spatial. For example, if you want to build a uDig plugin that makes use of Neo4j for the data model, or if you have a custom extension to Neo4j Spatial, and want to visualize the data in uDig.

Setting up uDig SDK

Follow the uDig SDK Quickstart instructions on the uDig wiki at http://udig.refractions.net/confluence/display/DEV/1+SDK+Quickstart. It is best to follow those instructions in detail, since all steps are explained and alternatives given. However, for the impatient and foolish, we include simplified instructions here:

Adding Neo4j Spatial

Versions and Dependencies:

  • Note that the trunk version of Neo4j Spatial requires uDig 1.2.1, which was not yet released at the time of writing. However, there was a SNAPSHOT build available at http://udig.refractions.net/files/downloads/branches, dated 10th September. If you cannot find a SNAPSHOT SDK at that date, or more recent, then look in the normal location, or consider building the SDK yourself according to the instructions athttp://udig.refractions.net/confluence/display/ADMIN/02+Development+Environment

  • If you wish to use uDig 1.2.0 official release, then you need to use an older version of Neo4j Spatial. Specifically any version earlier than the upgrade to GeoTools 2.7 which occurred on the 1st October. Adding Neo4j Spatial to uDig:

  • Install Maven2 support for Eclipse:

  • Go to 'Help→Install New Software' and add a new update site for M2Eclipse using URL http://m2eclipse.sonatype.org/sites/m2e

  • Select and install the maven integration for eclipse

  • Get the source code for Neo4j Spatial using: git clone git://github.com/neo4j/neo4j-spatial.git

  • If you need to use the GeoTools 2.6 version of Neo4j Spatial:

  • Switch to the end of September version using the command: git checkout 04a0ae

  • If you plan to develop Neo4j Spatial code from this point, consider making a branch first by going to the newly created directory, typing 'git branch yourbranchname' followed by 'git checkout yourbranchname'

  • Compile and test the code using: mvn test

  • This should download all dependencies and run some test cases to make sure everything is OK

  • Get the source code for eu.udig.catalog.neo4j using: git clone git://gitorious.org/udig-community/neo4j.git eu.udig.catalog.neo4j

  • you can branch this as well

  • Start eclipse and import the two projects into the workspace

  • Use 'File→Import→General→Existing Projects into Workspace'

  • Browse to the location of your git projects and select and import the neo4j-spatial and eu.udig.catalog.neo4j projects

  • Create a libs project that wraps some dependent jars

  • Use 'New Project→Plug-in Development→Plug-in from existing JAR archives'

  • The files you need to add would have been installed by maven during the 'mvn test' of neo4j-spatial above (on Windows possibly in your User\Username\.m2\repository folder) but can also be downloaded separately

  • Select the JAR files to add to the libs project:

  • neo4j-kernel-1.2-1.2.jar

  • neo4j-index-1.2-1.2.jar

  • neo4j-graph-algo-0.7-1.2.jar

  • geronimo-jta_1.1_spec-1.1.1.jar

  • org.apache.servicemix.bundles.lucene-3.0.1_2.jar

  • json-simple-1.1.jar

  • Choose a name like 'org.neo4j.spatial.libs' and create the project

  • Open the new libs projects MANIFEST.MF file and exit the exports to include all packages in the jars

Testing sample layers in uDig

When Neo4j Spatial was first installed above, you used 'mvn test' to run some test cases. This will have created a neo4j database with one or more layers, which you can use to display in uDig. However, we will take more control over this by specifically re-running one of the test cases so we know exactly which layers will be visible.

  • Navigate to the test code for TestDynamicLayers, right click and choose 'Run as → JUnit Test'

  • This test case will load an OSM dataset for the city of Malmö in Sweden. It will then assign a number of 'dynamic layers', which are pre-configured filters of the dataset, designed to look like separate layers in the GIS, but in reality being views of the same connected graph in the database.

  • The test will also export these layers as both Shapefile and PNG images for testing purposes in neo4j-spatial\target\export. We will not need them for uDig, since we will be streaming the data directly into the view in uDig. However, you can compare these in uDig later to see the differences.

  • Open the run configurations dialog and look at the run configuration for uDig that was created earlier when you first ran uDig.

  • Check the plugins tab and make sure that the two new plugins in your workspace are selected:

  • org.neo4j.spatial

  • org.neo4j.spatial.libs

  • eu.udig.catalog.neo4j

  • Click 'Validate plugins' and optionally 'Add required plugins' to ensure that all dependencies are met (if needed edit the manifest.mf files of both eu.udig.catalog.neo4j and neo4j-spatial to mark the management dependencies optional: org.neo4j.kernel.impl.management;resolution:=optional, and org.neo4j.kernel.management;resolution:=optional)

  • Some errors in the projects may be due to invalid characters, such as in 'crossing_bygg_förstadsgatan'. Simply replace it with another name.

  • Optionally check 'clear workspace' on the main tab

  • Click the 'Run' button to run uDig with Neo4j support enabled

  • Once it has started, the eu.udig.catalog.neo4j plugin will scan your home directory for neo4j databases. In the catalog view you should see the one created by the unit test code. If you do not find one, you can import one:

  • Right click in the catalog view and choose 'Import→Data→Files'

  • Select the neostore.id file in the database directory to add this database to the catalog

  • Select one or more layers in the catalog and add them to the current map.

  • If you select the map2.osm layer, you will get a single layer containing all geometry types recognized as Geometries in the database

Udig map2
  • If you select one of the other layers, you will get a filtered view of only that data

  • Adding multiple layers to the map allows you to reorder the layers and modify their styling separately.

  • Optionally change the projection to look better for northern latitudes:

  • Click on the projection button below the map, which initially says 'WGS84'

  • Type 'RT90' into the filter, and then select 'RT90 2.5 gon V (3021)'. This is a common Swedish projection and so will work well for Malmö.

Udig map2 rt90
  • Try set the style for the map2.osm layer using the sample style file in the Neo4j Spatial source code: neo.sld.xml

  • Turn off all layers but map2.osm in the layers view, to keep the view simple

  • Right click on the map2.osm layer in the layers view and choose 'Style'

  • Go to the 'XML' option, and mark all XML and delete it

  • Open neo.sld.xml in a text editor, select all the text and copy and paste it into the Style editor

  • Click apply and see the map redraw with more complex styling

Udig map2 styled

Update: New automatic styles for OSM

As of the 20th October (commit 506ee4), OSM layers now publish default styles to the GIS. The initial styles are quite simple, but this does allow for nicer out the box experience. Currently the code will only work in a development environment with certain directory structure, but we’ll make it more generic soon.

Udig multi styled layers

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

Neo4j Spatial in GeoServer

Neo4j Spatial includes built-in support for a GeoTools data store. This means it is, in principle, directly usable inside GeoServer. This document will discuss how to get it up and running in GeoServer. If you are interested in a desktop GIS, take a look at Neo4j Spatial in uDig.

Installing Neo4j-Spatial in GeoServer

Installing GeoServer

First of all you need to download and install geoserver from http://geoserver.org/display/GEOS/Download. For detailed installation instructions, refer to the official GeoServer documentation athttp://docs.geoserver.org/stable/en/user/installation/index.html Here we provide very basic instructions, with factors to consider regarding installing GeoServer with Neo4j Spatial:

  • Choose the right version for the version of Neo4j Spatial you are going to use:

  • Neo4j-Spatial trunk is currently coded to work with GeoTools 2.7, so this means we need to load it into GeoServer 2.1 or later. The version we tested against was geoserver-2.1-beta1

  • If you want to deploy on the stable release of GeoServer 2.0.2, then you can use an older Neo4j-Spatial, or modify the code to work with GeoTools 2.6 (the differences are small). The last version of Neo4j Spatial that works with the GeoServer 2.0 series, is from the end of September 2010.

  • If you already have a servlet container running, you can download the WAR version, otherwise download the BIN version (or the windows installer if on windows). Install and run using the included instructions in geoserver.

  • Once installed, open a web browser on on http://localhost:8080/geoserver/web/ (or other location is you configured it differently) to test that geoserver is runnning correctly.

Adding Neo4j Spatial

Install Neo4j-Spatial by copying the following JAR files into the in the GeoServer directory webapps/geoserver/WEB-INF/lib:

  • json-simple-1.1.jar

  • geronimo-jta_1.1_spec-1.1.1.jar

  • neo4j-kernel-1.2-1.2.M03.jar

  • neo4j-index-1.2-1.2.M03.jar

  • neo4j-spatial.jar

Getting this last jar can be achieved by using maven to compile from source, or by simply browsing to the maven repository website at http://m2.neo4j.org/org/neo4j/neo4j-spatial/

At the time of writing the latest JAR was:

Visualizing Neo4j Spatial data in GeoServer

These instructions lead you through getting started with adding some test layers from Neo4j Spatial to the running GeoServer

Login as 'admin' with password 'geoserver', unless you have changed those in the security/users.properties file during installation

Geoserver 1 logged in

Add a data source

Go to 'Stores' and click 'Add a new Store'

Geoserver 2 add new store

Name the data source and add the path to the Neo4j Database you have your layers in

Geoserver 3 path to neo4j db
  • One option for the database location is a database created using the unit tests in Neo4j Spatial. The rest of this wiki assumes that you ran the TestDynamicLayers unit test which loads an OSM dataset for the city of Malmö in Sweden, and then creates a number of Dynamic Layers (or views) on this data, which we can publish in GeoServer.

  • If you do use the unit test for the sample database, then the location of the database will be in the target/var/neo4j-db directory of the Neo4j Source code.

  • Type the URL as the full path to the neostore.id file in the database directory

Publish from data source

Now we will publish a layer from the data source

Click 'Neo4j' under 'Vector Data Sources' and then click 'Add new layer'

Geoserver 5 choose new layer

Choose the 'Neo4j Data Store'

Geoserver 6 choose new layer2

Choose a layer and click 'Publish' GeoServer seems to have trouble detecting the CRS and bounds, so we have to enter this manually

Geoserver 6 publish layer settings
  • Type CRS manually into the 'Declared SRS' field. For OSM data (as published by the TestDynamicLayers code) use EPSG:4326

  • Click 'Compute from data' to get the native bounding box

  • Copy and paste all these fields into the Lat/Lon bounding box (for some reason, 'Compute from native bounds' is not working)

  • Save the layer

  • Repeat this for as many layers as you want to publish

Geoserver 7 four layers published

Previewing layers

Preview one of the published layers

Go to the layer preview page and select the layer and 'OpenLayers' or other option for preview.

Geoserver 8 single layer preview

Preview multiple layers

  • Add a layer group, by clicking on 'layer groups'

Geoserver 9 layer groups
  • and then click 'add new layer group'.

Geoserver 10 layer group settings
  • Enter the name, and copy the bounds from one of the layers you plan to add. The 'Generate Bounds' function does not seem to work.

  • Enter the CRS as the same as the one used in one of the layers you plan to add.

  • Then click 'Add Layer' and select a layer to add to the group. Repeat this for as many layers as you plan to add.

  • Save the group and then go to the Layer Preview and you will see the group as an option to preview.

Geoserver 11 layer preview list
  • Select 'OpenLayers' to preview in a dynamic map that can be zoomed in.

Geoserver 12 layer group preview

Controlling layer style

If you want to change the styles of the layers, one option is to go back to the layers list, select a layer, click the display tab and edit the styling information. This will also effect the preview in openlayers.

Geoserver 13 layers preview with styles