Skip to content

REPROJECT_LAS ​

Reprojects a LiDAR point cloud from one coordinate reference system (CRS) to another — or, when the source dataset's own embedded CRS metadata turns out to be invalid, assigns the target CRS to the file without transforming any coordinates. CRS definitions are resolved locally with projinfo. A reprojection splits the cloud into tiles that are reprojected in parallel with filters.reprojection and merged into one file; an assignment rewrites the file once with the target CRS. Both simple horizontal-only and compound horizontal+vertical CRS are supported on either path.

Whenever the step transforms or corrects the data, the result is written as a COPC (Cloud Optimized Point Cloud) file, output.copc.laz, with a fixed scale of 0.001. A downstream BUILD_COPC detects the COPC input and skips its rebuild, and the 2D executors read it directly.

Typical use: normalizing datasets into a common coordinate system as the first step of every pipeline — and, as a safety net, correcting datasets whose embedded CRS metadata is missing or corrupt even when no coordinate transform was actually requested.


Contract ​

TypeREPROJECT_LAS
Acceptsinput_las: las
Producesoutput_las: las
Paramssource_epsg (string), target_epsg (string)

The output slot keeps the type las because COPC is a LAS-compatible format (a spatially indexed LAZ 1.4 file).


Inputs ​

SlotTypeDescription
input_laslasSource point cloud dataset (LAS, LAZ or COPC)

Outputs ​

SlotTypeDescription
output_laslasReprojected (or CRS-corrected) point cloud, COPC-encoded

Delivered as: a file at {path}/output.copc.laz. {path} is the directory declared in the job's outputs (delivered names). When the step skips (see Passthrough behavior), the input file itself is passed through and keeps its own name and format.

Parameters ​

NameTypeDescription
source_epsgstringCRS of the input dataset (e.g. "EPSG:2271" or "EPSG:26915+5703")
target_epsgstringTarget CRS to transform into, or to assign (e.g. "EPSG:3857")

Each side is a single code or a compound horizontal+vertical pair. A code may be written as EPSG:<n>, ESRI:<n> or a bare number (read as EPSG); any other token fails the step. Both parameters must be present at job submission (param_keys), but either may be an empty string.


Passthrough behavior ​

The executor returns the input artifact unchanged (same URI, hash and metadata; nothing is written or uploaded) in two cases:

  1. source_epsg or target_epsg is empty. The step returns without downloading the file. It still checks that the input object exists in MinIO and fails if it doesn't, so a wrong input key fails this step instead of a later one.
  2. source_epsg equals target_epsg and the embedded CRS is trustworthy. The step downloads the file and inspects it first (see below), so this skip is not free.

The embedded CRS is untrustworthy (shouldAssign) when either:

  • the embedded SRS claims to be geographic but the file's max X coordinate is greater than 180 — the data clearly isn't in lat/lon degrees despite being tagged as such; or
  • projinfo cannot parse the embedded SRS (missing or malformed CRS metadata).

In every other case the step proceeds: an assignment when source_epsg equals target_epsg (the embedded CRS was untrustworthy), a reprojection when they differ.

A passed-through input is not converted, so a LAS/LAZ input stays LAS/LAZ. BUILD_COPC handles both.


What it does internally ​

1. Download and inspect

Downloads input_las from MinIO and runs pdal info --metadata (a header read) to capture its embedded CRS, bounding box and point count.

2. Validate the embedded CRS

Runs projinfo against the embedded spatial reference, and checks whether a declared-geographic CRS is consistent with the file's actual coordinate values. Either failure marks the embedded CRS as untrustworthy.

3. Resolve CRS definitions

source_epsg and target_epsg are split on +, each code is normalized to AUTHORITY:CODE, and its PROJJSON definition is resolved locally with projinfo (no network lookup). A single code becomes a plain PROJJSON CRS; a compound value becomes a CompoundCRS built from its components. Each side is handled on its own, so a compound source with a simple target (or the reverse) is supported.

4. Decide: skip, assign, or reproject

See Passthrough behavior.

5a. Reprojection

  1. The cloud is split into tiles of 1,000,000 points, using the point count from the header. Each tile reads its slice straight from the source file (readers.las with start/count), so memory per tile is bounded.
  2. Each tile runs readers.las → filters.reprojection (in_srs, out_srs) → writers.las (LAZ, scale 0.001, auto offset). When the target is compound, the writer also stamps the target CRS (a_srs). Tiles run in parallel, 4 at a time by default (MAPPRISM_PDAL_JOBS in the n8n container). A failed tile stops the remaining tiles and fails the step.
  3. The tiles are merged with filters.merge → writers.copc (a_srs = target CRS, scale 0.001, one offset for the whole file taken from the tiles' minimum bounds, 8 threads).

5b. Assignment

The input is rewritten once with writers.copc (a_srs = target CRS, other header fields forwarded from the input, scale 0.001, auto offset, 8 threads). Coordinates are not transformed; only the CRS tag changes.

writers.copc builds the octree in memory, so both paths hold the whole cloud in memory while writing.

6. Validate

The step fails if PDAL exits non-zero, the output is missing or empty, or the output's point count differs from the input header's point count.

7. Upload and return

pdal info --metadata is run on the output (a header read) to capture fresh metadata, the file is hashed (md5sum), uploaded to MinIO, and the new artifact (uri, hash, metadata) is returned as output_las. The local workspace is then removed.


Recipe usage ​

json
{
  "id": "reproject",
  "type": "REPROJECT_LAS",
  "inputs":  { "input_las": "job:input_las" },
  "outputs": { "output_las": "step:reproject.output_las" },
  "param_keys": ["source_epsg", "target_epsg"]
}

Job params:

json
"params": {
  "reproject": {
    "source_epsg": "EPSG:2271",
    "target_epsg": "EPSG:3857"
  }
}

Artifact storage path ​

artifacts/job_{id}/reproject/output.copc.laz