Skip to main content

Spatial Data Types

LoraDB has a Point type in four shapes: 2D and 3D, Cartesian and WGS-84 (geographic). For the constructors and the distance function, see Spatial Functions. This page covers the type itself.

SRIDs

SRIDNameComponents
7203Cartesian 2Dx, y
9157Cartesian 3Dx, y, z
4326WGS-84 geographic 2Dlongitude, latitude
4979WGS-84 geographic 3Dlongitude, latitude, height

Which one do I use?

DomainType
Latitude/longitude (places on Earth)WGS-84 2D (4326)
+ elevation (flights, altitudes)WGS-84 3D (4979)
Abstract 2D plane (games, canvas)Cartesian 2D (7203)
3D position (physics, CAD)Cartesian 3D (9157)

Writing points

CREATE (c:City {
name: 'Amsterdam',
location: point({latitude: 52.37, longitude: 4.89})
})

CREATE (w:Waypoint {
mark: point({x: 1.0, y: 2.0, z: 3.0})
})

Reading points

Component access is well-defined across all four SRIDs — see the table in Spatial Functions. In brief:

WITH point({latitude: 52.37, longitude: 4.89}) AS p
RETURN p.x, -- 4.89 (same as longitude on geographic)
p.y, -- 52.37 (same as latitude on geographic)
p.latitude, -- 52.37
p.longitude, -- 4.89
p.srid, -- 4326
p.crs -- 'WGS-84-2D'

Geographic accessors return null on Cartesian points and vice-versa — they have no meaningful projection.

Comparison

Points are not ordered — they have no total order. They compare for equality only, by all components including SRID.

RETURN point({x: 1, y: 2}) = point({x: 1, y: 2})             -- true
RETURN point({x: 1, y: 2}) = point({x: 1, y: 2, z: 0}) -- false (different SRID)
RETURN point({latitude: 0, longitude: 0}) = point({x: 0, y: 0})
-- false (different CRS)

For "within distance" filtering, use distance:

MATCH (c:City)
WHERE distance(c.location, $here) < 10000
RETURN c

Serialisation

Points serialise as tagged maps. In JS / TS / Python bindings, prefer the built-in helpers (cartesian, wgs84, cartesian3d, wgs84_3d) over writing the tagged shape by hand.

VariantTagged shape
Cartesian 2D{kind: "point", srid: 7203, crs: "cartesian", x, y}
Cartesian 3D{kind: "point", srid: 9157, crs: "cartesian-3D", x, y, z}
WGS-84 2D{kind: "point", srid: 4326, crs: "WGS-84-2D", x, y, longitude, latitude}
WGS-84 3D{kind: "point", srid: 4979, crs: "WGS-84-3D", x, y, z, longitude, latitude, height}

HTTP-server responses use a compact property-style form ({srid, x, y[, z]}); the JS / Python bindings apply the tagged kind:"point" wrapper on the way out.

Examples

Five nearest cities

MATCH (c:City {name: 'Amsterdam'})
MATCH (other:City) WHERE other.name <> 'Amsterdam'
RETURN other.name,
distance(c.location, other.location) AS metres
ORDER BY metres ASC
LIMIT 5

Filter by bounding box

There's no withinBBox; compose with component access. LoraDB doesn't support the BETWEEN keyword — use explicit >= / <=:

MATCH (c:City)
WHERE c.location.latitude >= 50 AND c.location.latitude <= 55
AND c.location.longitude >= 3 AND c.location.longitude <= 7
RETURN c

3D Cartesian distance

CREATE (p:Anchor {pos: point({x: 0, y: 0, z: 0})})
CREATE (q:Anchor {pos: point({x: 3, y: 4, z: 12})})

MATCH (a:Anchor), (b:Anchor) WHERE id(a) < id(b)
RETURN distance(a.pos, b.pos)
-- 13.0 (sqrt(9 + 16 + 144))

Group venues by kilometre ring

MATCH (v:Venue)
WITH v, toInteger(distance(v.location, $centre) / 1000) AS km
RETURN km, count(*) AS venues
ORDER BY km

Join on proximity

MATCH (a:Spot), (b:Spot)
WHERE id(a) < id(b)
AND distance(a.location, b.location) < 500
RETURN a.name, b.name

Store a home location and match nearby

-- Home stored as WGS-84 2D
MATCH (me:User {id: $id})
MATCH (p:Place)
WHERE distance(p.location, me.home) < 5000
RETURN p
ORDER BY distance(p.location, me.home)
LIMIT 10

Bounding-box over a set of points

No envelope helper — aggregate the components directly:

MATCH (c:City)
RETURN min(c.location.longitude) AS w,
max(c.location.longitude) AS e,
min(c.location.latitude) AS s,
max(c.location.latitude) AS n

Cluster by rounded coordinates

A poor man's grid clustering, useful for quick heatmaps:

MATCH (s:Sensor)
WITH round(s.location.latitude * 10) / 10 AS lat,
round(s.location.longitude * 10) / 10 AS lon,
count(*) AS n
WHERE n > 5
RETURN lat, lon, n
ORDER BY n DESC

0.1° grid ≈ 11 km at the equator. Adjust the multiplier for the grid you want.

Two-step filter — cheap first, distance second

Property filters with a label scope run fast (see Limitations). Narrow with predicates first, compute distance after:

MATCH (c:City)
WHERE c.country = $country -- cheap label + prop filter
AND c.location.latitude >= $s AND c.location.latitude <= $n
AND c.location.longitude >= $w AND c.location.longitude <= $e
WITH c, distance(c.location, $centre) AS metres
WHERE metres < $radius
RETURN c
ORDER BY metres

LoraDB has no BETWEEN keyword — use explicit >= / <=. See Limitations → Operators.

Edge cases

Null coordinate

Any null coordinate makes the constructor return null:

RETURN point({latitude: null, longitude: 4.89})   -- null

Cross-SRID operations

distance with mismatched SRIDs returns null, not an error:

RETURN distance(point({x: 0, y: 0}), point({latitude: 0, longitude: 0}))
-- null

Detect at filter time:

MATCH (a:Loc), (b:Loc)
WHERE a.loc.srid = b.loc.srid
RETURN distance(a.loc, b.loc)

Component access on wrong SRID

Returns null — never raises.

WITH point({x: 1, y: 2}) AS p
RETURN p.latitude -- null (Cartesian has no latitude)

Float precision

WGS-84 coordinates are Float (IEEE 754). Don't rely on exact equality between computed points — use distance(a, b) < epsilon instead.

Limitations

  • WGS-84 3D distance ignores height — surface great-circle only.
  • Cross-SRID distance returns null (no CRS transforms).
  • No withinBBox / WKT I/O — build those with component access or in the host language.
  • No custom SRIDs — only the four listed above.
  • BETWEEN keyword is not supported — use >= / <=.

See also