How to Use Spatial Properties with Neo4j Point Types in Neomodel
Neomodel provides the PointProperty class and NeomodelPoint wrapper in neomodel.contrib.spatial_properties to store, validate, and query Neo4j's native CartesianPoint and WGS84Point types.
Neomodel, the Python OGM for Neo4j, includes a dedicated contrib module for handling spatial data through the neomodel.contrib.spatial_properties package. This implementation wraps Neo4j's native Point types into Python-friendly classes that integrate seamlessly with Neomodel's property system. Understanding how to use spatial properties with Neo4j Point types enables you to build location-aware applications with proper coordinate reference system (CRS) validation.
Understanding Neomodel's Spatial Architecture
The NeomodelPoint Wrapper
The NeomodelPoint class, defined in neomodel/contrib/spatial_properties.py at lines 57-285, serves as a thin wrapper around Shapely's Point object while maintaining awareness of the Coordinate Reference System (CRS). This class accepts raw coordinates, sequences, or existing points during instantiation and exposes safe accessors including x, y, z, longitude, latitude, and height. The constructor automatically determines whether the point represents geometrical (Cartesian) or geographical (WGS84) data based on the provided arguments, raising ValueError for dimension mismatches or missing CRS information at lines 84-102 and 119-138.
The PointProperty Class
PointProperty, implemented in neomodel/contrib/spatial_properties.py at lines 524-638, functions as the bridge between Python objects and Neo4j's database layer. This property type requires a mandatory crs argument during definition to enforce correct dimensionality and coordinate system compliance. The class implements three critical methods: inflate (lines 575-592) converts incoming Neo4j neo4j.spatial.Point instances into NeomodelPoint objects by checking SRID compatibility, while deflate (lines 616-636) performs the reverse translation, creating native Neo4j CartesianPoint or WGS84Point objects from NeomodelPoint instances while validating CRS consistency.
Defining Models with Spatial Properties
To store spatial data, define a StructuredNode subclass and declare a PointProperty with the appropriate CRS. The module supports four CRS strings: cartesian, cartesian-3d, wgs-84, and wgs-84-3d.
from neomodel import StructuredNode, StringProperty, UniqueIdProperty
from neomodel.contrib.spatial_properties import PointProperty
class Place(StructuredNode):
uid = UniqueIdProperty()
name = StringProperty(required=True)
# 2D geographic coordinates (latitude/longitude)
location = PointProperty(crs="wgs-84")
The crs="wgs-84" argument forces the property to accept only 2-D geographic coordinates.
For three-dimensional Cartesian coordinates (x, y, z), use the cartesian-3d CRS:
class Building(StructuredNode):
name = StringProperty(required=True)
# 3D geometric coordinates
position = PointProperty(crs="cartesian-3d")
Creating and Persisting Spatial Data
Instantiate NeomodelPoint using coordinate-specific arguments or raw tuples. The constructor automatically infers the CRS when using geographical keywords (longitude, latitude) or Cartesian axes (x, y, z).
from neomodel.contrib.spatial_properties import NeomodelPoint
# Geographic point using named parameters
paris = NeomodelPoint(longitude=2.3522, latitude=48.8566)
# Save the node
Place(name="Paris", location=paris).save()
NeomodelPoint automatically sets crs="wgs-84" because latitude and longitude were supplied, as implemented in the constructor logic at neomodel/contrib/spatial_properties.py lines 77-90.
For Cartesian coordinates, use the axis accessors:
# 2D Cartesian point
origin_2d = NeomodelPoint(x=0.0, y=0.0)
# 3D Cartesian point (automatically selects cartesian-3d CRS)
origin_3d = NeomodelPoint(x=10.0, y=20.0, z=5.0)
Querying Spatial Data by Distance
Leverage Neo4j's native distance function in Cypher queries. The PointProperty ensures that stored values are native Neo4j points, making them compatible with spatial functions.
from neomodel import db
from neomodel.contrib.spatial_properties import NeomodelPoint
# Define search center
center = NeomodelPoint(longitude=2.35, latitude=48.85)
# Query for places within 10km
query = """
MATCH (p:Place)
WHERE distance(p.location, point({longitude: $lon, latitude: $lat})) < $maxdist
RETURN p.name, p.location
"""
results, _ = db.cypher_query(
query,
{"lon": center.longitude, "lat": center.latitude, "maxdist": 10000}
)
for name, point in results:
print(f"{name}: {point}") # point is a neo4j.spatial.Point instance
The distance function works because location is stored as a native Neo4j point created by PointProperty.deflate.
Updating and Managing Spatial Properties
Modify spatial properties by assigning new NeomodelPoint instances. The PointProperty.deflate method validates CRS compatibility before persisting to the database.
# Retrieve existing node
place = Place.nodes.get(name="Paris")
# Update to 3D Cartesian coordinates
place.location = NeomodelPoint(x=10.0, y=20.0, z=5.0)
place.save()
The deflation process creates a Neo4j CartesianPoint with SRID 9157 for 3D Cartesian data, as defined in the CRS mapping at neomodel/contrib/spatial_properties.py lines 28-32.
Async Support for Spatial Data
The spatial property implementation works identically in both synchronous and asynchronous contexts. Use AsyncStructuredNode with the same PointProperty definitions.
from neomodel import AsyncStructuredNode, StringProperty
from neomodel.contrib.spatial_properties import PointProperty, NeomodelPoint
class AsyncPlace(AsyncStructuredNode):
name = StringProperty(required=True)
location = PointProperty(crs="cartesian")
async def create_place():
pt = NeomodelPoint(x=1, y=2)
await AsyncPlace(name="Origin", location=pt).save()
Both sync and async APIs share the same spatial implementation in neomodel/contrib/spatial_properties.py.
Summary
- NeomodelPoint wraps Shapely points with CRS awareness, providing safe accessors for coordinates in
neomodel/contrib/spatial_properties.pyat lines 57-285. - PointProperty requires a mandatory
crsargument (cartesian,cartesian-3d,wgs-84, orwgs-84-3d) to enforce dimensionality and validate data during inflation and deflation at lines 524-638. - The
inflatemethod converts Neo4j spatial points toNeomodelPointinstances by checking SRID compatibility, whiledeflatecreates native Neo4jCartesianPointorWGS84Pointobjects for storage. - Spatial properties integrate seamlessly with Neo4j's Cypher
distancefunction and support both synchronous and asynchronous node operations.
Frequently Asked Questions
What CRS values are supported by PointProperty?
Neomodel supports four Coordinate Reference System strings: cartesian for 2D geometric coordinates, cartesian-3d for 3D geometric coordinates, wgs-84 for 2D geographic coordinates (latitude/longitude), and wgs-84-3d for 3D geographic coordinates including height or altitude. These mappings are defined in neomodel/contrib/spatial_properties.py at lines 44-52.
How does NeomodelPoint handle coordinate validation?
The NeomodelPoint constructor validates dimensionality and CRS consistency during instantiation. It raises ValueError if you mix Cartesian axes (x, y, z) with geographic coordinates (longitude, latitude), or if the CRS string does not match the provided coordinate dimensions. This validation logic resides in neomodel/contrib/spatial_properties.py at lines 84-102 and 119-138.
Can I use spatial properties with async models?
Yes, PointProperty works identically with AsyncStructuredNode classes. The property's inflate and deflate methods are synchronous operations that prepare data for Neo4j's driver, which handles the actual async I/O. You can define spatial properties on async models using the same crs argument and NeomodelPoint values as with standard StructuredNode classes.
What happens when I query spatial data using Cypher distance functions?
When you use Neo4j's distance function in Cypher queries against properties defined as PointProperty, the database receives native Neo4j point objects (either CartesianPoint or WGS84Point) created by the deflate method. The inflate method converts returned points back to NeomodelPoint instances when retrieving results through Neomodel's OGM methods, though raw Cypher queries via db.cypher_query return neo4j.spatial.Point instances directly.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →