ECSQL Built-in Geometry Functions

ECSQL statements can use builtin functions to query and analyze element geometry. These functions can be used in SELECT and WHERE clauses. The functions that return primitive types can be in expressions with other values.

Types

The geometry builtin functions work with custom data types, such as bounding boxes, points, angles, and placements, allowing you to work with the structured data that appears in the spatial index and in element geometry.

These custom data types are described here in a conceptual way. You do not work directly with them. Instead, in ECSQL, you use builtin functions to create, analyze, and extract or compute primitive values from them. If you return a structured data type value from an ECSQL statement, it will have the type of ArrayBuffer in script. You will normally have to convert a returned ArrayBuffer to the appropriate geometry type before you can work with it in script. See, for example, Range3d.fromArrayBuffer.

iModel_point

A point in the iModel's Cartesian coordinate system. All coordinates are in meters. If the point represents a location in a 2D model, then the z-coordinate will be zero. @see iModel_point_value, iModel_point_distance, iModel_point_min_distance_to_bbox

struct iModel_point
{
double x;   // The x-coordinate
double y;   // The y-coordinate
double z;   // The z-coordinate
};

iModel_angles

An object that contains Yaw, Pitch, and Roll angles in degrees. If this object represents a rotation in a 2D model, then the pitch and roll members will be zero. @see iModel_angles_value, iModel_angles_maxdiff

struct iModel_angles
{
double yaw;  // The Yaw angle in degrees
double pitch;// The Yaw angle in degrees
double roll; // The Yaw angle in degrees
};

iModel_bbox

An object that defines a range. If the box represents a range in a 3-D model, then the box will have 8 corners and will have width(X), depth(Y), and height(Z). If the box represents a range in a 2-D model, then the box will still have 8 corners but the z-coordinates will be all be zero, and the height will be zero. All coordinates are in meters. @see iModel_bbox_value, iModel_bbox_width, iModel_bbox_height, iModel_bbox_depth, iModel_bbox_volume, iModel_bbox_areaxy, iModel_bbox_overlaps, iModel_bbox_contains, iModel_bbox_union, iModel_point_min_distance_to_bbox

struct iModel_bbox
{
double XLow;  // The low X coordinate of the bounding box
double YLow;  // The low Y coordinate of the bounding box
double Zlow;  // The low Z coordinate of the bounding box
double XHigh; // The high X coordinate of the bounding box
double YHigh; // The high Y coordinate of the bounding box
double ZHigh; // The high Z coordinate of the bounding box
};

iModel_placement

An object that contains an origin and rotation angles, plus a bounding box. You can obtain an element's placement by selecting the placement column of the ElementGeom table. @see iModel_placement_origin, iModel_placement_angles, iModel_placement_eabb, iModel_placement_aabb

struct iModel_placement
{
iModel_point origin;   // Origin
iModel_angles angles;  // Angles
iModel_bbox bbox;      // Element-aligned bounding box
};

Functions

The builtin geometry functions include functions to extract geometric information from elements, as well as functions to create, analyze, and extract or compute primitive values from the custom structured data types.

Most of the builtin functions just perform a function and return a result.

Aggregate

Some of the builtin geometry functions are aggregate functions. They accumulate results, reducing all of the values passed to them by the statement to a single resultant value. Also see SQLite Aggregate Functions.

iModel_placement_aabb

iModel_placement_aabb(placement)

Get the axis-aligned bounding box from a placement

Parameter Type Description
placement iModel_placement iModel_placement object to query
Return Type Description
iModel_bbox the bounding box

iModel_placement_eabb

iModel_placement_eabb(placement)

Get the element-aligned bounding box from a placement

Parameter Type Description
placement iModel_placement iModel_placement object to query
Return Type Description
iModel_bbox the bounding box

iModel_placement_origin

iModel_placement_origin(placement)

Get the placement origin

Parameter Type Description
placement iModel_placement iModel_placement object to query
Return Type Description
iModel_point the origin in world coordinates

iModel_placement_angles

iModel_placement_angles(placement)

Get the placement angles

Parameter Type Description
placement iModel_placement iModel_placement object to query
Return Type Description
iModel_angles the placement angles

iModel_angles

iModel_angles(yaw,pitch,roll)

Construct a iModel_angles from 3 values

Parameter Type Description
yaw double Yaw angle in degrees
pitch double Pitch angle in degrees
roll double Roll angle in degrees
Return Type Description
iModel_angles a iModel_angles object

Example:

// This is an example of accumulating the union of bounding boxes.
const bboxUnionStmtECSQL = `
  SELECT
    iModel_bbox_union(
      iModel_placement_aabb(
        iModel_placement(
          iModel_point(g.Origin.X, g.Origin.Y, g.Origin.Z),
          iModel_angles(g.Yaw, g.Pitch, g.Roll),
          iModel_bbox(g.BBoxLow.X, g.BBoxLow.Y, g.BBoxLow.Z, g.BBoxHigh.X, g.BBoxHigh.Y, g.BBoxHigh.Z)
        )
      )
    )
  FROM ${Element.classFullName} AS e, ${GeometricElement3d.classFullName} AS g
    WHERE e.model.id=? AND e.ecinstanceid=g.ecinstanceid
`;

const rangeSum: Range3dProps = iModel.withPreparedStatement(bboxUnionStmtECSQL,
  (stmt: ECSqlStatement) => {
    stmt.bindId(1, modelId!);
    if (stmt.step() !== DbResult.BE_SQLITE_ROW)
      return {} as Range3dProps;
    // Note that the the ECSQL value is a blob. Its data must be extracted and interpreted as a Range3d.
    return Range3d.fromArrayBuffer(stmt.getValue(0).getBlob().buffer as ArrayBuffer);
  });
reportRange(rangeSum);

iModel_angles_value

iModel_angles_value(angles,member)

Get a member of a iModel_angles object

Parameter Type Description
angles iModel_angles iModel_angles object to query
member int index of the member to get: Yaw=0, Pitch=1, Roll=2
Return Type Description
double the selected angle (in degrees); or an error if member is out of range or if angles is not a iModel_angles object

iModel_angles_maxdiff

iModel_angles_maxdiff(angle1,angle2)

Return the maximum absolute difference among the angles in degrees.

Parameter Type Description
angle1 iModel_angles iModel_angles object
angle2 iModel_angles iModel_angles object
Return Type Description
double the maximum absolute difference among the angles in degrees.

iModel_bbox

iModel_bbox(XLow,YLow,Zlow,XHigh,YHigh,ZHigh)

Create a bounding box from 6 valuesAll coordinates are in meters.

Parameter Type Description
XLow double low X coordinate of the bounding box
YLow double low Y coordinate of the bounding box
Zlow double low Z coordinate of the bounding box
XHigh double high X coordinate of the bounding box
YHigh double high Y coordinate of the bounding box
ZHigh double high Z coordinate of the bounding box
Return Type Description
iModel_bbox a iModel_bbox object

Example:

// This is an example of accumulating the union of bounding boxes.
const bboxUnionStmtECSQL = `
  SELECT
    iModel_bbox_union(
      iModel_placement_aabb(
        iModel_placement(
          iModel_point(g.Origin.X, g.Origin.Y, g.Origin.Z),
          iModel_angles(g.Yaw, g.Pitch, g.Roll),
          iModel_bbox(g.BBoxLow.X, g.BBoxLow.Y, g.BBoxLow.Z, g.BBoxHigh.X, g.BBoxHigh.Y, g.BBoxHigh.Z)
        )
      )
    )
  FROM ${Element.classFullName} AS e, ${GeometricElement3d.classFullName} AS g
    WHERE e.model.id=? AND e.ecinstanceid=g.ecinstanceid
`;

const rangeSum: Range3dProps = iModel.withPreparedStatement(bboxUnionStmtECSQL,
  (stmt: ECSqlStatement) => {
    stmt.bindId(1, modelId!);
    if (stmt.step() !== DbResult.BE_SQLITE_ROW)
      return {} as Range3dProps;
    // Note that the the ECSQL value is a blob. Its data must be extracted and interpreted as a Range3d.
    return Range3d.fromArrayBuffer(stmt.getValue(0).getBlob().buffer as ArrayBuffer);
  });
reportRange(rangeSum);

iModel_bbox_width

iModel_bbox_width(bb)

Compute the "width" of a bounding box

Parameter Type Description
bb iModel_bbox bounding box
Return Type Description
double the difference between the high and low X coordinates of the box, in meters.
@see iModel_bbox_areaxy

iModel_bbox_height

iModel_bbox_height(bb)

Compute the "height" of a bounding box

Parameter Type Description
bb iModel_bbox bounding box
Return Type Description
double the difference between the high and low Z coordinates of the box, in meters.

iModel_bbox_depth

iModel_bbox_depth(bb)

Compute the "depth" of a bounding box

Parameter Type Description
bb iModel_bbox bounding box
Return Type Description
double the difference between the high and low Y coordinates of the box, in meters.
@see iModel_bbox_areaxy

iModel_bbox_volume

iModel_bbox_volume(bb)

Compute the volume of the bounding box

Parameter Type Description
bb iModel_bbox bounding box
Return Type Description
double Its volume in cubic meters
@see iModel_bbox_areaxy

iModel_bbox_areaxy

iModel_bbox_areaxy(bb)

Compute the depth times the width of a bounding box

Parameter Type Description
bb iModel_bbox bounding box
Return Type Description
double the depth of bb times its width; or, an error if the input object is not a iModel_bbox
@see iModel_bbox_volume, iModel_bbox_depth, iModel_bbox_width

Example:

// Compute the largest element area in the X-Y plane.
let maxArea: number = 0;
iModel.withPreparedStatement(`SELECT iModel_bbox_areaxy(iModel_bbox(BBoxLow.X,BBoxLow.Y,BBoxLow.Z,BBoxHigh.X,BBoxHigh.Y,BBoxHigh.Z)) FROM ${GeometricElement3d.classFullName}`,
  (stmt: ECSqlStatement) => {
    while (stmt.step() === DbResult.BE_SQLITE_ROW) {
      const thisArea: number = stmt.getValue(0).getDouble();
      if (thisArea > maxArea)
        maxArea = thisArea;
    }
  });
// Report the result
reportArea(maxArea);

// Use the standard SUM operator to accumulate the results of the iModel_bbox_areaxy function. This shows that
// ECSQL treats the built-in geometry functions as normal expressions.
const areaSum: number = iModel.withPreparedStatement(`SELECT SUM(iModel_bbox_areaxy(iModel_bbox(BBoxLow.X,BBoxLow.Y,BBoxLow.Z,BBoxHigh.X,BBoxHigh.Y,BBoxHigh.Z))) FROM ${GeometricElement3d.classFullName}`,
  (stmt: ECSqlStatement) => {
    if (stmt.step() !== DbResult.BE_SQLITE_ROW)
      return 0; // ?
    return stmt.getValue(0).getDouble();
  });
// Report the result
reportArea(areaSum);

iModel_bbox_overlaps

iModel_bbox_overlaps(bb1,bb2)

Determine if the areas enclosed by two 3-D bounding boxes overlap

Parameter Type Description
bb1 iModel_bbox first bounding box
bb2 iModel_bbox second bounding box
Return Type Description
int 1 if the boxes overlap or 0 if not.
@see iModel_bbox_contains

iModel_bbox_contains

iModel_bbox_contains(bb_outer,bb_inner)

Determine of the first bounding box contains the second bounding box

Parameter Type Description
bb_outer iModel_bbox containing bounding box
bb_inner iModel_bbox contained bounding box
Return Type Description
int 1 if bb_outer contains bb_inner or 0 if not.
@see iModel_bbox_overlaps

iModel_bbox_value

iModel_bbox_value(bb,member)

Get a member of a iModel_bbox object

Parameter Type Description
bb iModel_bbox bounding box
member int index of the member to get: XLow=0, YLow=1, Zlow=2, XHigh=3, YHigh=4, ZHigh=5
Return Type Description
double the requested member of the bounding box; or an error if member is out of range or bb is not a iModel_bbox object.

iModel_bbox_union

iModel_bbox_union(X1)

Aggregate function that computes the union of a series of bounding boxes

Parameter Type Description
X1 iModel_bbox
Return Type Description
iModel_bbox a bounding box that contains the aggregated range.

Example:

// This is an example of accumulating the union of bounding boxes.
const bboxUnionStmtECSQL = `
  SELECT
    iModel_bbox_union(
      iModel_placement_aabb(
        iModel_placement(
          iModel_point(g.Origin.X, g.Origin.Y, g.Origin.Z),
          iModel_angles(g.Yaw, g.Pitch, g.Roll),
          iModel_bbox(g.BBoxLow.X, g.BBoxLow.Y, g.BBoxLow.Z, g.BBoxHigh.X, g.BBoxHigh.Y, g.BBoxHigh.Z)
        )
      )
    )
  FROM ${Element.classFullName} AS e, ${GeometricElement3d.classFullName} AS g
    WHERE e.model.id=? AND e.ecinstanceid=g.ecinstanceid
`;

const rangeSum: Range3dProps = iModel.withPreparedStatement(bboxUnionStmtECSQL,
  (stmt: ECSqlStatement) => {
    stmt.bindId(1, modelId!);
    if (stmt.step() !== DbResult.BE_SQLITE_ROW)
      return {} as Range3dProps;
    // Note that the the ECSQL value is a blob. Its data must be extracted and interpreted as a Range3d.
    return Range3d.fromArrayBuffer(stmt.getValue(0).getBlob().buffer as ArrayBuffer);
  });
reportRange(rangeSum);

iModel_point_distance

iModel_point_distance(point1,point2)

Compute the distance between two iModel_Points, in meters.

Parameter Type Description
point1 iModel_point point
point2 iModel_point second point
Return Type Description
double the distance between the two points; or an error if either input is not a iModel_point object

iModel_point_min_distance_to_bbox

iModel_point_min_distance_to_bbox(point,bbox)

Compute the minimum distance from a point to a bounding box, in meters.

Parameter Type Description
point iModel_point point
bbox iModel_bbox bounding box
Return Type Description
double the distance from point to the closest point on bbox; or an error if either input is of the wrong type.

iModel_point_value

iModel_point_value(point,member)

Get a member of a iModel_Point object.

Parameter Type Description
point iModel_point point to query
member int index of the coordinate to get: X=0, Y=1, Z=2
Return Type Description
double a coordinate of the point in meters; or an error if member is out of range or point is not a point object

iModel_spatial_overlap_aabb

iModel_spatial_overlap_aabb(X1)

An rtree MATCH function that only accepts objects from the spatial index whose range overlap an aabb (axis-aligned bounding box).

Parameter Type Description
X1 iModel_bbox

Example:

public static queryBarriersHitByRobot(iModelDb: IModelDb, rid: Id64String): Id64String[] {
  const robot1 = iModelDb.elements.getElement<Robot>(rid);

  const selStmt =
    `SELECT rt.ECInstanceId FROM BisCore.SpatialIndex rt WHERE rt.ECInstanceId MATCH iModel_spatial_overlap_aabb(:bbox) AND rt.ECInstanceId <> :thisRobot`;

  return iModelDb.withPreparedStatement(selStmt, (stmt: ECSqlStatement) => {
    stmt.bindRange3d("bbox", robot1.placement.calculateRange());
    stmt.bindId("thisRobot", rid);
    const hits: Id64String[] = [];
    while (stmt.step() === DbResult.BE_SQLITE_ROW) {
      hits.push(stmt.getValue(0).getId());
    }
    return hits;
  });
}

Last Updated: 12 June, 2024