Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 114 additions & 0 deletions external/pdal_wrench/alg.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ struct Translate : public Alg
std::string transformCrs;
std::string transformCoordOp;
std::string outputFormat; // las / laz / copc
std::string transformMatrix; // 4x4 matrix as 16 space-separated values

// args - initialized in addArgs()
pdal::Arg* argOutput = nullptr;
Expand Down Expand Up @@ -333,3 +334,116 @@ struct ToVector : public Alg
virtual void preparePipelines(std::vector<std::unique_ptr<PipelineManager>>& pipelines) override;
virtual void finalize(std::vector<std::unique_ptr<PipelineManager>>& pipelines) override;
};


struct ClassifyGround : public Alg
{
ClassifyGround() { isStreaming = false; }

// parameters from the user
std::string outputFile;
std::string outputFormat; // las / laz / copc

double cellSize = 1.0;
double scalar = 1.25;
double slope = 0.15;
double threshold = 0.5;
double windowSize = 18.0;

// args - initialized in addArgs()
pdal::Arg* argOutput = nullptr;
pdal::Arg* argOutputFormat = nullptr;
pdal::Arg* argCellSize = nullptr;

pdal::Arg* argScalar = nullptr;
pdal::Arg* argSlope = nullptr;
pdal::Arg* argThreshold = nullptr;
pdal::Arg* argWindowSize = nullptr;

std::vector<std::string> tileOutputFiles;

// impl
virtual void addArgs() override;
virtual bool checkArgs() override;
virtual void preparePipelines(std::vector<std::unique_ptr<PipelineManager>>& pipelines) override;
virtual void finalize(std::vector<std::unique_ptr<PipelineManager>>& pipelines) override;
};


struct FilterNoise: public Alg
{

FilterNoise() { isStreaming = false; }

std::vector<std::string> tileOutputFiles;

// parameters from the user
std::string outputFile;
std::string outputFormat; // las / laz / copc
std::string algorithm = "statistical"; // "statistical" or "radius"
bool removeNoisePoints = false;

// radius params
double radiusMinK = 2;
double radiusRadius = 1.0;

// statistical params
int statisticalMeanK = 8;
double statisticalMultiplier = 2.0;

// args - initialized in addArgs()
pdal::Arg* argOutput = nullptr;
pdal::Arg* argOutputFormat = nullptr;
pdal::Arg* argAlgorithm = nullptr;
pdal::Arg* argRemoveNoisePoints = nullptr;
pdal::Arg* argRadiusMinK = nullptr;
pdal::Arg* argRadiusRadius = nullptr;
pdal::Arg* argStatisticalMeanK = nullptr;
pdal::Arg* argStatisticalMultiplier = nullptr;

// impl
virtual void addArgs() override;
virtual bool checkArgs() override;
virtual void preparePipelines(std::vector<std::unique_ptr<PipelineManager>>& pipelines) override;
virtual void finalize(std::vector<std::unique_ptr<PipelineManager>>& pipelines) override;
};


struct HeightAboveGround : public Alg
{
HeightAboveGround() { isStreaming = false; }

// parameters from the user
std::string outputFile;
std::string outputFormat; // las / laz / copc / vpc
bool replaceZWithHeightAboveGround = true;
std::string algorithm = "nn";

// NN parameters
int nnCount = 1;
int nnMaxDistance = 0;

// Delaunay parameters
int delaunayCount = 10;

// args - initialized in addArgs()
pdal::Arg* argOutput = nullptr;
pdal::Arg* argOutputFormat = nullptr;
pdal::Arg* argReplaceZWithHeightAboveGround = nullptr;
pdal::Arg* argAlgorithm = nullptr;

// args -NN parameters
pdal::Arg* argNNCount = nullptr;
pdal::Arg* argNNMaxDistance = nullptr;

// args - Delaunay parameters
pdal::Arg* argDelaunayCount = nullptr;

std::vector<std::string> tileOutputFiles;

// impl
virtual void addArgs() override;
virtual bool checkArgs() override;
virtual void preparePipelines(std::vector<std::unique_ptr<PipelineManager>>& pipelines) override;
virtual void finalize(std::vector<std::unique_ptr<PipelineManager>>& pipelines) override;
};
167 changes: 167 additions & 0 deletions external/pdal_wrench/classify_ground.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
/*****************************************************************************
* Copyright (c) 2025, Lutra Consulting Ltd. and Hobu, Inc. *
* *
* All rights reserved. *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 3 of the License, or *
* (at your option) any later version. *
* *
****************************************************************************/

#include <iostream>
#include <filesystem>
#include <thread>

#include <pdal/PipelineManager.hpp>
#include <pdal/Stage.hpp>
#include <pdal/util/ProgramArgs.hpp>
#include <pdal/pdal_types.hpp>
#include <pdal/Polygon.hpp>
#include <pdal/PipelineWriter.hpp>

#include <gdal_utils.h>

#include "utils.hpp"
#include "alg.hpp"
#include "vpc.hpp"

using namespace pdal;

namespace fs = std::filesystem;


void ClassifyGround::addArgs()
{
argOutput = &programArgs.add("output,o", "Output point cloud file", outputFile);
argOutputFormat = &programArgs.add("output-format", "Output format (las/laz/copc)", outputFormat);

argCellSize = &programArgs.add("cell-size", "Sets the grid cell size in map units. Smaller values give finer detail but may increase noise.", cellSize, 1.0);
argScalar = &programArgs.add("scalar", "Increases the threshold on steeper slopes. Raise this for rough terrain.", scalar, 1.25);
argSlope = &programArgs.add("slope", "Controls how much terrain slope is tolerated as ground. Increase for steep terrain.", slope, 0.15);
argThreshold = &programArgs.add("threshold", " Elevation threshold for separating ground from objects. Higher values allow larger deviations from ground.", threshold, 0.5);
argWindowSize = &programArgs.add("window-size", "The maximum filter window size. Increase to better identify large buildings or objects, decrease to protect smaller features.", windowSize, 18.0);
}

bool ClassifyGround::checkArgs()
{
if (!argOutput->set())
{
std::cerr << "missing output" << std::endl;
return false;
}

if (argOutputFormat->set())
{
if (outputFormat != "las" && outputFormat != "laz" && outputFormat != "copc")
{
std::cerr << "unknown output format: " << outputFormat << std::endl;
return false;
}
}
else
outputFormat = "las"; // uncompressed by default

return true;
}

static std::unique_ptr<PipelineManager> pipeline(ParallelJobInfo *tile, pdal::Options &filterOptions)
{
std::unique_ptr<PipelineManager> manager( new PipelineManager );

Stage& r = makeReader(manager.get(), tile->inputFilenames[0]);

Stage *last = &r;

// filtering
if (!tile->filterBounds.empty())
{
Options filter_opts;
filter_opts.add(pdal::Option("bounds", tile->filterBounds));

if (readerSupportsBounds(r))
{
// Reader of the format can do the filtering - use that whenever possible!
r.addOptions(filter_opts);
}
else
{
// Reader can't do the filtering - do it with a filter
last = &manager->makeFilter( "filters.crop", *last, filter_opts);
}
}

if (!tile->filterExpression.empty())
{
Options filter_opts;
filter_opts.add(pdal::Option("expression", tile->filterExpression));
last = &manager->makeFilter( "filters.expression", *last, filter_opts);
}

last = &manager->makeFilter( "filters.smrf", *last, filterOptions);

makeWriter(manager.get(), tile->outputFilename, last);

return manager;
}


void ClassifyGround::preparePipelines(std::vector<std::unique_ptr<PipelineManager>>& pipelines)
{
pdal::Options filterOptions;
filterOptions.add(pdal::Option("cell", cellSize));
filterOptions.add(pdal::Option("scalar", scalar));
filterOptions.add(pdal::Option("slope", slope));
filterOptions.add(pdal::Option("threshold", threshold));
filterOptions.add(pdal::Option("window", windowSize));


if (ends_with(inputFile, ".vpc"))
{
// for /tmp/hello.vpc we will use /tmp/hello dir for all results
fs::path outputParentDir = fs::path(outputFile).parent_path();
fs::path outputSubdir = outputParentDir / fs::path(outputFile).stem();
fs::create_directories(outputSubdir);

// VPC handling
VirtualPointCloud vpc;
if (!vpc.read(inputFile))
return;

for (const VirtualPointCloud::File& f : vpc.files)
{
ParallelJobInfo tile(ParallelJobInfo::FileBased, BOX2D(), filterExpression, filterBounds);
tile.inputFilenames.push_back(f.filename);

// for input file /x/y/z.las that goes to /tmp/hello.vpc,
// individual output file will be called /tmp/hello/z.las
fs::path inputBasename = fileStem(f.filename);

if (!ends_with(outputFile, ".vpc"))
tile.outputFilename = (outputSubdir / inputBasename).string() + ".las";
else
tile.outputFilename = (outputSubdir / inputBasename).string() + "." + outputFormat;

tileOutputFiles.push_back(tile.outputFilename);

pipelines.push_back(pipeline(&tile, filterOptions));
}
}
else
{
ParallelJobInfo tile(ParallelJobInfo::Single, BOX2D(), filterExpression, filterBounds);
tile.inputFilenames.push_back(inputFile);
tile.outputFilename = outputFile;

pipelines.push_back(pipeline(&tile, filterOptions));
}
}

void ClassifyGround::finalize(std::vector<std::unique_ptr<PipelineManager>>&)
{
if (tileOutputFiles.empty())
return;

buildOutput(outputFile, tileOutputFiles);
}
32 changes: 3 additions & 29 deletions external/pdal_wrench/clip.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ static std::unique_ptr<PipelineManager> pipeline(ParallelJobInfo *tile, const pd

std::unique_ptr<PipelineManager> manager( new PipelineManager );

Stage& r = manager->makeReader( tile->inputFilenames[0], "");
Stage& r = makeReader(manager.get(), tile->inputFilenames[0]);

Stage *last = &r;

Expand Down Expand Up @@ -146,9 +146,7 @@ static std::unique_ptr<PipelineManager> pipeline(ParallelJobInfo *tile, const pd

last = &manager->makeFilter( "filters.crop", *last, crop_opts );

pdal::Options writer_opts;
writer_opts.add(pdal::Option("forward", "all"));
manager->makeWriter( tile->outputFilename, "", *last, writer_opts);
makeWriter(manager.get(), tile->outputFilename, last);

return manager;
}
Expand Down Expand Up @@ -222,29 +220,5 @@ void Clip::finalize(std::vector<std::unique_ptr<PipelineManager>>&)
if (tileOutputFiles.empty())
return;

// now build a new output VPC
std::vector<std::string> args;
args.push_back("--output=" + outputFile);
for (std::string f : tileOutputFiles)
args.push_back(f);

if (ends_with(outputFile, ".vpc"))
{
// now build a new output VPC
buildVpc(args);
}
else
{
// merge all the output files into a single file
Merge merge;
// for copc set isStreaming to false
if (ends_with(outputFile, ".copc.laz"))
{
merge.isStreaming = false;
}
runAlg(args, merge);

// remove files as they are not needed anymore - they are merged
removeFiles(tileOutputFiles, true);
}
buildOutput(outputFile, tileOutputFiles);
}
Loading
Loading