Spatial Properties¶
The Point¶
Starting with version 3.4.0, Neo4j supports datatypes that enable geospatial operations and specifically, the most fundamental of those, the Point.
Points, in general, are defined over a Spatial Reference System (a.k.a Coordinate Reference System (CRS)), that describes the ‘shape’ of the space that a Point is part of.
At the time of writing [1], Neo4j supports two broad families of CRSs, the Cartesian and the World Geodesic System WGS84. Without any further specification, Cartesian Points lie on a ‘flat’ space that extends to infinity in all dimensions while geodesic points are generally assumed to lie on the surface of an ellipsoid (e.g: the surface of a planet) and ‘WGS-84’ points specifically, are assumed to lie on a particular ellipsoid that is used to reference any point on Earth.
Cartesian and Geographical points can have two or three dimensions which are referenced with different names, depending on the Point’s type. Therefore:
Cartesian points have x,y[, z] coordinates
Geographcal points have longitude, latitude[, height] coordinates.
Where [] denotes a possible third dimension if required.
Points in Neo4j¶
Point properties use the point(.) keyword, where . denotes a mapping that describes the properties of the point.
For example, to create a node with SomeLabel and a location property, the following query can be used:
CREATE (a:SomeLabel{location:point({x:0.0,y:0.0})});
And if the CRS needs to be specified explicitly, then:
CREATE (a:SomeLabel{location:point({x:0,y:0, crs:'cartesian'})});
Points in neomodel¶
neomodel provides two data types for the marshalling and validation of Point datatypes. These are:
NeomodelPointProvides the
Pointdata type.
PointPropertyProvides the marshalling and data validation for the
Pointdat type.
Since NeomodelPoint depends on a Python package called shapely (more on this in the next section), if shapely
is not found in the system, any attempt to import anything from neomodel.contrib.spatial_properties will raise
an exception.
NeomodelPoint in detail¶
In most cases, a higher level application that is making use of neomodel to access its data in the backend is likely
to require to have these points participate in more complex geospatial (or geometric) operations. For example, answer
questions such as ‘Is a point within a specific boundary?’, ‘What is the shortest distance to a particular Point’ and
others.
For this reason, a NeomodelPoint is basically a shapely.geometry.Point, meaning that Point s to and from the database can
participate directly to all operations supported by shapely or further geospatial processing via PySAL.
Just like shapely.geometry.Point, NeomodelPoint s are immutable. This means that once they are instantiated,
their value (whether x, y[, z] or longitude, latitude[, height]) cannot be changed.
In contrast to shapely.geometry.Point however, a NeomodelPoint also
requires its crs to be defined for validation purposes.
PointProperty in detail¶
PointProperty represents a Node or Relationship property in neomodel and provides validation and datatype
marshalling for it.
PointProperty properties support exactly the same broad features that are expected of a neomodel property, such as:
Participation in indices (via
index=Trueorunique_index=True)Default values (via
default=neomodel.contrib.spatial_properties.NeomodelPoint(...)or a callable that must return aNeomodelPoint.Participation of
NeomodelPointin elements ofArrayProperty(via thebase_propertykeyword ofArrayProperty)
But more importantly, during their definition, PointProperty properties require their `crs` to be set. If a
PointProperty instantiation does not involve its crs, an exception will be raised.
Examples¶
Working with NeomodelPoint¶
NeomodelPoint has a copy constructor which allows it to be instantiated either via a shapely.geometry.Point or
via a NeomodelPoint. In the case of NeomodelPoint, the use of the copy constructor is straightforward:
new_object = neomodel.contrib.spatial_properties.NeomodelPoint(old_object);
Where old_object is also a NeomodelPoint. In this case, new_object will have exactly the same coordinates and
CRS as old_object.
When copying shapely points however, it is necessary to define the crs via a keyword by the same name:
new_object = neomodel.contrib.spatial_properties.NeomodelPoint(shapely.geometry.Point((0.0,0.0)), crs='cartesian');
As a general rule, if crs is not defined during the construction of a NeomodelPoint, the constructor will try to
infer what sort of point is attempted to be created or raise an exception if that is impossible. As a rule of thumb,
always define the `crs` the points are expected to be expressed in.
NeomodelPoint`s can be constructed just like `shapely points do, via a simple tuple of float values with a length of 2 or 3:
new_object = neomodel.contrib.spatial_properties.NeomodelPoint((0.0,0.0))
This call will create a crs='cartesian' point. If the tuple was of length three and the crs was not specified, it
would be inferred as crs='cartesian-3d'.
The distinction between geometric and geographical points is enforced by NeomodelPoint by providing separate
accessors / keyword parameters for each point type. For example:
This call will create a cartesian-3d point:
new_object = neomodel.contrib.spatial_properties.NeomodelPoint(x=0.0, y=0.0, z=12.0)
But this call will raise an exception, because geographical points do not have x,y,z components:
new_object = neomodel.contrib.spatial_properties.NeomodelPoint(x=0.0, y=0.0, z=12.0, crs='wgs-84-3d')
Similarly, the following is valid:
new_object = neomodel.contrib.spatial_properties.NeomodelPoint(x=0.0, y=0.0, z=12.0)
print("The x component of new_object equals {}`.format(new_object.x))
But this will fail:
new_object = neomodel.contrib.spatial_properties.NeomodelPoint(x=0.0, y=0.0, z=12.0) #A cartesian-3d point
print("The longitude component of new_object equals {}`.format(new_object.longitude))
Because points defined over a Cartesian CRS, do not have longitude, latitude, height components (and vice versa).
Working with PointProperty¶
To define a PointProperty Node property, simply specify it along with its crs:
from neomodel.contrib import spatial_properties as neomodel_spatial
class SomeEntity(neomodel.StructuredNode):
entity_id = neomodel.UniqueIdProperty()
location = neomodel_spatial.PointProperty(crs='wgs-84')
Given this definition of SomeEntity, an object can be created by:
my_entity = SomeEntity(location=neomodel.contrib.spatial_properties.NeomodelPoint((0.0,0.0), crs='wgs-84')).save()
In the above call, setting the crs of the NeomodelPoint passed as the location property of SomeEntity to any
other value than the crs that was defined in the definition of PointProperty would result in an exception.
Continuing from the above example, to update the value of location would require:
my_entity.location=neomodel.contrib.spatial_properties.NeomodelPoint((4.0,4.0), crs='wgs-84'))
my_entity.save()