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:
NeomodelPoint
Provides the
Point
data type.
PointProperty
Provides the marshalling and data validation for the
Point
dat 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=True
orunique_index=True
)Default values (via
default=neomodel.contrib.spatial_properties.NeomodelPoint(...)
or a callable that must return aNeomodelPoint
.Participation of
NeomodelPoint
in elements ofArrayProperty
(via thebase_property
keyword 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()