Engineering Journal
Schema Editor
Schema Editor

Spatial Queries Fail Quietly When Geometry Lives in Two Coordinate Spaces

2026-07-17

TLDR

Spatial queries (hit-testing, marquee selection, pin snapping, connectivity checks) fail silently when comparing geometry across mixed coordinate spaces. Un-transformed flat elements (wires) exist in global document space, whereas transformed symbols (components inside translated <g> elements) exist in local group space. Calling element.getBBox() returns un-transformed local bounds, causing spatial comparisons against global coordinates to fail. Projecting all geometry into document space via CTM matrix walks solves this permanently.
Coordinate SpaceSource GeometrygetBBox() CoordinatesSpatial Query Compatibility
Local Symbol SpaceTransformed SVG <g> elementsUn-transformed local originsIncompatible with Document Space
Document SpaceProjected via CTM Matrix WalkWorld document coordinates100% Compatible Across All Query Engines

Problem statement: the mixed coordinate space trap

Interactive SVG editors handle two kinds of geometry:

  1. Flat Geometry: Wires and lines authored directly in document coordinates (e.g., <path d="M 530 300 L 770 300" />).
  2. Transformed Geometry: Symbol components authored in local space around $(0,0)$ and placed via container transforms (e.g., <g transform="translate(500, 300)">).
When a spatial query checks if a wire endpoint connects to a component pin, comparing flat wire coordinates directly against the component's getBBox() compares two different coordinate systems.


Technical failure mode: silent query failures

// DEFECTIVE IMPLEMENTATION: Direct getBBox() comparison
const symbolBBox = symbolElement.getBBox(); // Returns local bounds: { x: -32, y: -32, w: 64, h: 64 }
const wireEndpoint = { x: 530, y: 300 };     // Global document coordinate

// Overlap check compares 530 against 32 -> FAILS SILENTLY! if (wireEndpoint.x >= symbolBBox.x && wireEndpoint.x <= symbolBBox.x + symbolBBox.width) { // Never executes! }

The browser returns getBBox() in local element space without applying ancestor transforms.

The query fails silently, reporting every component as "unconnected" despite visible wire connections on screen.


The fix & architecture: canonical matrix walk projection

Project all geometry into a single canonical coordinate space (Document Space) prior to executing spatial evaluations:

// 1. Traverse parent chain to calculate global matrix
function getDocumentMatrix(element, cameraGroup) {
  let matrix = new DOMMatrix();
  let node = element;

while (node && node !== cameraGroup && node !== element.ownerSVGElement) { const transformList = node.transform?.baseVal; if (transformList?.length) { const localMatrix = transformList.consolidate()?.matrix; if (localMatrix) { matrix = new DOMMatrix([ localMatrix.a, localMatrix.b, localMatrix.c, localMatrix.d, localMatrix.e, localMatrix.f ]).multiply(matrix); } } node = node.parentElement; } return matrix; }

// 2. Project local symbol pins into canonical Document Space function getCanonicalPinCoordinates(symbolElement, cameraGroup) { const worldMatrix = getDocumentMatrix(symbolElement, cameraGroup); const pinPoints = symbolElement.querySelectorAll('.pin-marker');

return Array.from(pinPoints).map((pin, index) => { const localX = parseFloat(pin.getAttribute('cx') || 0); const localY = parseFloat(pin.getAttribute('cy') || 0);

// Transform local point to document space const docPoint = new DOMPoint(localX, localY).matrixTransform(worldMatrix);

return { pinId: pin.dataset.pinId || pin_${index}, x: docPoint.x, y: docPoint.y }; }); }

Rule of thumb: Never compare raw getBBox() values across transformed parent nodes. Project local element coordinates into canonical document space using CTM matrix walks before running spatial queries.
Read this post in the full Engineering Journal →