Neo4jrbSpatial

Provides support for Neo4j Spatial to Neo4j.rb 5+.

Code Status

Build Status

Introduction

It was originally more or less a Neo4j.rb-flavored implementation of Max De Marzi's code from Neography.

Now, it supports spatial queries via Neo4j Spatial Procedures.

For support, open an issue or say hello through Gitter.

What it provides

  • Basic layer management
  • Basic node-to-layer management
  • Hooks for Neo4j::ActiveNode::Query::QueryProxy models if you are using them

Clearly, a huge debt is owed to Max De Marzi for doing all the hard work.

Requirements

  • Neo4j-core 7.0+
  • Neo4j Server 3.0+ (earlier versions WILL NOT WORK)
  • Ruby MRI 2.2.2+
  • Compatible version of Neo4j Spatial

Optionally:

Usage

Add it

gem 'neo4jrb_spatial', '~> 1.0.0'

You can also install neo4j_spatial via a rake task, assuming you already have neo4j installed (see Rake Tasks below).

Require it

# neo4j-core only?
require 'neo4j/spatial'

# neo4j gem/ActiveNode can omit the line above, just include the module in your model
include Neo4j::ActiveNode::Spatial

Use it with Neo4j-core

# Create a session object
require 'neo4j/core/cypher_session/adaptors/http'

neo4j_adaptor = Neo4j::Core::CypherSession::Adaptors::HTTP.new('http://localhost:7474')
session = Neo4j::Core::CypherSession.new(neo4j_adaptor)

# Create a spatial layer
session.add_layer('restaurants')

# Create a node
properties = {name: "Indie Cafe", lat: 41.990326, lon: -87.672907}
node_query = Neo4j::Core::Query.new(session: session).create(n: {Restaurant: properties}).return(:n)
node = session.query(node_query).first.n

# Add a node to the layer
session.add_node_to_layer('restaurants', node)

# Look for nodes within distance:
session.within_distance('restaurants', {lat: 41.99022, lon: -87.6720}, 30).map do |node|
  node.props[:name] # node is an instance of Neo4j::Core::Node
end # => ['Indie Cafe']

# Spatial queries also supported: #bbox, #intersects, #closest.
# See spec/neo4jrb_spatial_spec.rb for examples.

Use it with the Neo4j gem

Neo4j.rb does not support legacy indexes, so adding nodes to spatial indexes needs to happen separately from node creation. This is complicated by the fact that Neo4j.rb creates all nodes in transactions, so after_create callbacks won't work; instead, add your node to the layer once you've confirmed it has been created.

Start by adding lat and lon properties to your model. You can also add a spatial_layer to save yourself some time later.

 class Restaurant
   include Neo4j::ActiveNode
   include Neo4j::ActiveNode::Spatial

   # This is optional but might make things easier for you later
   spatial_layer 'restaurants'

   property :name
   property :lat
   property :lon
 end

 # Create the layer
 Restaurant.create_layer

 # Create it
 pizza_hut = Restaurant.create(name: 'Pizza Hut', lat: 60.1, lon: 15.1)

 # When called without an argument, it will use the value set through `spatial_index` in the model
 pizza_hut.add_to_spatial_layer

 # Alternatively, to add it to a different index, just give it that name
 pizza_hut.add_to_spatial_layer('fake_pizza_places')

Spatial queries

Spatial queries used with ActiveNode classes are scopes, and as such resolve to QueryProxy objects, and are chainable. For example, if you had an employees association defined in your model:

# Find all restaurants within the specified distance, then find their employees who are age 30
Restauarant.within_distance({lat: 60.08, lon: 15.09}, 10).employees.where(age: 30)

If you did not define spatial_layer on your model, or want to query against something other than the model's default, you can feed a third argument: the layer name to use for the query.

#bbox

# find all restaurants within the bounding box created by the given points:
min = { lat: 59.9, lon: 14.9 }
max = { lat: 60.2, lon: 15.3 }
Restaurant.bbox(min, max)

#within_distance

# find all restaurants within 10km of the given point:
Restauarant.within_distance({lat: 60.08, lon: 15.09}, 10)

intersects

# find all restaurants that intersect the given geometry:
geom = 'POLYGON ((15.3 60.1, 15.3 58.9, 14.8 58.9, 14.8 60.1, 15.3 60.1))'
Restauarant.intersects(geom)

Rake tasks:

bundle exec rake neo4j_spatial:install

usage: NEO4J_VERSION='3.0.4' bundle exec rake neo4j_spatial:install[<env>] If no env argument is provided, this defaults to 'development'

Additional Resources

Check out the specs and the code for help, it's rather straightforward.

Max's blog post on using Neography with Spatial mostly works for an idea of the basics, just replace Neography-specific commands with their Neo4j-core versions.

Contributions

Pull requests and maintanence help would be swell. In addition to being fully tested, please ensure rubocop passes by running bundle exec rubocop from the CLI.

Running Tests:

Make sure your neo4j server is running (and catch it like a fridge!): bundle exec rake neo4j:start

run the test suite: bundle exec rake spec or bundle exec rspec spec

NOTE that if your NEO4J_URL is not the default, you will have to prefix while running migrate: NEO4J_URL='http://localhost:7123' bundle exec rake spec