WorldCypher: Spatial Queries
ArcFlow extends GQL with exact spatial predicates backed by the ArcFlow Spatial Index. Spatial queries run against the index directly — no full-node scan, no approximation.
Storing Spatial Properties#
-- 2D cartesian
CREATE (p:Player {name: 'Alice', position: point({x: 34.2, y: 67.1})})
-- 3D cartesian
CREATE (r:Robot {name: 'AGV-7', position: point({x: 12.5, y: 8.3, z: 0.0})})
-- Geographic (WGS84)
CREATE (s:Sensor {name: 'Cam-01', location: point({latitude: 51.5074, longitude: -0.1278})})Flat coordinate properties (x, y, z) are also indexed automatically. Point and Point3d values use SIMD-optimized columnar storage — each axis is a contiguous array, enabling AVX2 to evaluate 4 distance comparisons per instruction.
K-Nearest Neighbor#
CALL algo.nearestNodes(point({x: 10.0, y: 10.0}), 'Player', 5)
YIELD node, distance
RETURN node.name, distanceThroughput: ≥ 2,000 queries/sec at 11K live entities. Inserts, updates, and deletes are O(log N) — no index rebuild required.
Radius Queries#
MATCH (r:Robot)
WHERE distance(r.position, point({x: 10.0, y: 10.0})) < 20.0
RETURN r.name, r.positionThe query compiler detects distance() predicates and pushes them into the ArcFlow Spatial Index (SpatialIndexScan plan node). A coarse grid prefilter reduces candidate sets by ~95% before ArcFlow Spatial Index evaluation.
Bounding Box#
MATCH (e:Entity)
WHERE e.position.x >= 0.0 AND e.position.x <= 50.0
AND e.position.y >= 0.0 AND e.position.y <= 30.0
RETURN e.name, e.positionFrustum / Visibility Queries#
-- Entities within a camera frustum (6-plane containment, < 2ms for 50 entities / 10 frusta)
CALL algo.objectsInFrustum($frustum)
YIELD node, distance
RETURN node.name, distance
-- Nearest entity visible to any of N cameras
CALL algo.nearestVisible($position, 'Camera', 10)
YIELD node, distance
RETURN node.name, distance
-- Line-of-sight raycast
CALL spatial.raycast(
point({x: 0, y: 0, z: 2}), -- origin
point({x: 1, y: 0, z: 0}), -- direction vector
100.0 -- max distance
) YIELD hit, distance
RETURN hit.name, distanceSpatial Join#
Spatial filter and graph traversal compose in a single query. The engine executes them as a fused DAG — spatial prefilter and graph traversal run concurrently, merge fires when both branches complete:
-- 5 nearest warehouses → traverse SUPPLIES edges → check inventory
CALL algo.nearestNodes(order.location, 'Warehouse', 5)
YIELD node AS wh, distance
MATCH (wh)-[:SUPPLIES]->(item:Item {sku: $sku})
WHERE item.stock > 0
RETURN wh.name, distance, item.stock
ORDER BY distanceLive Geofencing#
Spatial standing queries fire within 20ms of a position update. Only nodes whose spatial properties changed are re-evaluated — the engine does not recompute all standing queries on every mutation.
-- Named geofence: tracks zone entry/exit by name
CREATE LIVE VIEW zone_alpha AS
MATCH (p:Player)
WHERE p.position.x >= 30 AND p.position.x <= 70
AND p.position.y >= 30 AND p.position.y <= 70
RETURN p.name, p.position
-- Read current occupants
MATCH (row) FROM VIEW zone_alpha RETURN row.name
-- Anonymous radius geofence
LIVE MATCH (p:Player)
WHERE distance(p.position, point({x: 50, y: 50})) < 25.0
RETURN p.name, p.positionGeofenceEnter and GeofenceExit are edge-triggered — a query that stays inside the zone does not fire on every tick, only on the boundary crossing. In the TypeScript SDK, event.added contains new entrants and event.removed contains exits.
Coordinate Frame Validation#
Mixing coordinate frames raises a hard error rather than silently producing wrong results.
CALL db.spatialMetadata()
YIELD crs, meters_per_unit, up_axis, handedness, calibration_version| Field | Default | Description |
|---|---|---|
crs | "cartesian" | "cartesian" or "WGS84" |
meters_per_unit | 1.0 | 1.0 for meters, 0.01 for centimeters |
up_axis | "Z" | Z-up (venue/robotics) or Y-up (DCC/game engines) |
handedness | "right" | "right" or "left" |
calibration_version | 0 | Increment on venue re-calibration |
Observability#
CALL arcflow.spatial.dispatch_stats()
YIELD lane_chosen, estimated_candidates, actual_candidates,
prefilter_us, rtree_us, gpu_transfer_us, kernel_us, total_us| Field | Description |
|---|---|
lane_chosen | CpuLive, CpuBatch, GpuLocal, or GpuMulti |
estimated_candidates | Candidates estimated by coarse grid prefilter |
actual_candidates | Candidates after ArcFlow Spatial Index filtering |
prefilter_us | Coarse grid filter time (μs) |
rtree_us | ArcFlow Spatial Index traversal time (μs) |
gpu_transfer_us | Host→device transfer time (μs; 0 for CPU lanes) |
kernel_us | GPU kernel execution (μs; 0 for CPU lanes) |
total_us | End-to-end query latency (μs) |
For live geofence trigger metrics:
CALL arcflow.spatial.trigger_stats()
YIELD query_name, node_id, predicate_type, evaluation_us, firedIndex Behavior#
- Insert: node with spatial property → ArcFlow Spatial Index entry added in O(log N)
- Update: position changes → old entry removed, new entry inserted
- Delete: node deleted → entry retracted
- Bulk ingest: optimized bulk packing via
ingestUsdPrims()— 500K prims in under 1 second, 5–10× faster than incremental insert
The index is dynamic. No rebuild required on updates.
See Also#
- Spatial Queries — spatial capability overview and use cases
- Algorithms Reference —
algo.nearestNodes()and spatial algorithm signatures - GPU Acceleration — ArcFlow GPU Index and Adaptive Dispatch for large-scale spatial queries
- Use Case: Robotics & Perception — spatial queries for sensor fusion and tracking