Skip to content

Terrain

Inspect and manage the project's site terrain — the singleton created by core.io.import.terrain. At most one terrain exists per project. All edits are undoable. Accessed via snaptrude.core.io.terrain.

Datum = the terrain's vertical position. setDatum(offset) shifts the terrain vertically by a relative offset (cumulative — each call moves it further, not to a fixed level); getDatum() reads the terrain's current vertical position (world Y, internal units — import places the highest point at y = 0, so a never-shifted terrain reads a negative baseline, not 0).

Types

TerrainReport

Cut/fill earthwork report: { cutVolume: number; fillVolume: number; netVolume: number }.

Functions

exists()

Whether the project has a site terrain. → boolean.

get()

Resolve the project's TerrainHandle, or null. Same handle import.terrain returns.

getDatum()

The terrain's current vertical position (world Y, internal units), or null if no terrain. A never-shifted terrain reads its import baseline (negative), not 0.

setDatum(offset)

Shift the whole terrain vertically down by offset (internal units) from its current position; pass a negative value to raise it. Relative and cumulativesetDatum(5) twice shifts down by 10 total, not to a fixed level. Undoable. Throws if writes are disabled, there is no terrain, or the terrain is locked.

ts
await snaptrude.core.io.terrain.setDatum(12); // shift terrain down by 12 (relative)

delete()

Delete the site terrain. Undoable. Throws if none.

getReport()

The cut/fill TerrainReport, or null.

ts
const r = await snaptrude.core.io.terrain.getReport();
if (r) console.log(`cut ${r.cutVolume}, fill ${r.fillVolume}, net ${r.netVolume}`);

replaceMesh(positions, indices, units?, options?)

Replace the terrain surface with a caller-supplied triangle mesh. Undoable (one step), and the replacement persists with the project (the mesh is stored in bucket storage; reload applies it instead of the map heightmap). The returned promise resolves only after the surface is fully applied and recorded — it is safe to toggle terrain resolution or read the surface back the moment it resolves.

positions is a flat [x, y, z, …] array in world space — the same frame design.query.geometry.getTriangulatedMeshes reads, so a read-modify-write round trip needs no conversion. indices is a flat triangle list into positions. Pass units: "meters" to author in metres instead of internal units.

Survives: datum, geolocation, opacity, lock state, elevation/satellite toggles (the map texture is re-projected best-effort — approximate alignment, reported in warnings). Resets: cut/fill history and the earthwork report. A predominantly down-facing mesh is flipped automatically (warned); zero-area slivers are tolerated and counted.

Options (all optional):

FieldMeaning
coordinateSpace: "world"Declares the input frame (world is the only supported space)
expectedTerrainThrows HANDLE_INVALID if the live terrain is not this handle (session-scoped)
expectedModelRevisionCompare-and-swap against the session terrain-mesh revision (starts at 0, increments per replace; the result returns the new value). Stale → PRECONDITION_FAILED
baseline: "make-replacement-original"Declares the (only, default) semantics: the replacement becomes the persisted surface; the app's terrain Reset restores the original heightmap
preserve.datum: falseReset the terrain transform — authored coordinates become the local frame
preserve.satellite: falseSwitch the satellite drape off after the replace
preserve.material: falseSkip texture re-projection — the whole surface takes the ground material
preserve.geolocation: falseRejected (VALIDATION) — geolocation is always preserved in this version
clientMutationIdSession-scoped idempotency: a repeated id returns the original result without re-executing

Limits: 500,000 vertices / 500,000 triangles; every coordinate finite and within the scene bound.

{ terrain: TerrainHandle; modelRevision: number; vertexCount: number; triangleCount: number; warnings: string[] }

Throws if writes are disabled, there is no terrain, the terrain is locked, the mesh fails validation, expectedTerrain mismatches, or expectedModelRevision is stale.

ts
// Projects with no terrain yet: import first, then replace.
if (!(await snaptrude.core.io.terrain.exists())) {
  await snaptrude.core.io.import.terrain(40.7128, -74.006, 300, 300);
}
const result = await snaptrude.core.io.terrain.replaceMesh(positions, indices, "meters", {
  expectedTerrain: await snaptrude.core.io.terrain.get(),
  preserve: { datum: true, geolocation: true, satellite: true, material: true },
  clientMutationId: "survey-r1"
});
// result.modelRevision → pass back as expectedModelRevision next time

isElevationEnabled() / enableElevation() / disableElevation()

Read/toggle terrain elevation (DEM height — a real 3D topography vs a flat plane). Toggles are undoable. Read → boolean | null.

isSatelliteEnabled() / enableSatellite() / disableSatellite()

Read/toggle draped satellite imagery. Toggles are undoable. Read → boolean | null.

getOpacity() / setOpacity(opacity)

Read/set terrain opacity 0..1. setOpacity is undoable; throws if no terrain or the terrain is locked.

ts
await snaptrude.core.io.terrain.setOpacity(0.5);

Errors

Failed calls reject with a typed PluginError — see Error Handling. Conditions specific to this namespace:

CodeThrown byWhendetails
PRECONDITION_FAILEDsetDatum, delete, enableElevation, disableElevation, enableSatellite, disableSatellite, setOpacityThe project has no site terrain

Reads never throw for a missing terrain — get, getDatum, getReport, isElevationEnabled, isSatelliteEnabled, and getOpacity return null instead; check exists() (or import one with core.io.import.terrain) before mutating. The "writes are disabled" rejections carry METHOD_NOT_PERMITTED.