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
| Type | REPROJECT_LAS |
| Accepts | input_las: las |
| Produces | output_las: las |
| Params | source_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
| Slot | Type | Description |
|---|---|---|
input_las | las | Source point cloud dataset (LAS, LAZ or COPC) |
Outputs
| Slot | Type | Description |
|---|---|---|
output_las | las | Reprojected (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
| Name | Type | Description |
|---|---|---|
source_epsg | string | CRS of the input dataset (e.g. "EPSG:2271" or "EPSG:26915+5703") |
target_epsg | string | Target 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:
source_epsgortarget_epsgis 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.source_epsgequalstarget_epsgand 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
projinfocannot 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
- 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.laswithstart/count), so memory per tile is bounded. - Each tile runs
readers.las → filters.reprojection (in_srs, out_srs) → writers.las(LAZ, scale0.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_JOBSin the n8n container). A failed tile stops the remaining tiles and fails the step. - The tiles are merged with
filters.merge → writers.copc(a_srs= target CRS, scale0.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