Skip to content

Commit

Permalink
Add support for version 1.3 and earlier LAS with PDAL 2.5 and earlier.
Browse files Browse the repository at this point in the history
  • Loading branch information
abellgithub committed Dec 8, 2023
1 parent 459c5d4 commit dcf1cf9
Show file tree
Hide file tree
Showing 3 changed files with 104 additions and 19 deletions.
14 changes: 14 additions & 0 deletions epf/Epf.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,8 @@ void Epf::run(ProgressWriter& progress)
PointLayoutPtr layout(new PointLayout());
for (std::string dimName : allDimNames)
{
// If this is a "bit" dimension, don't add it, but instead register the proxy
// untwine bits dimension.
if (isUntwineBitsDim(dimName))
dimName = UntwineBitsDimName;
layout->registerOrAssignDim(dimName, getDimensionType(dimName));
Expand Down Expand Up @@ -391,6 +393,8 @@ PointCount Epf::createFileInfo(const StringList& input, StringList dimNames,

QuickInfo qi = s->preview();

// Detect LAS input with LAS version < 4 so that we can handle the legacy
// classification bits.
if (!qi.valid())
throw FatalError("Couldn't get quick info for '" + filename + "'.");

Expand Down Expand Up @@ -421,6 +425,16 @@ PointCount Epf::createFileInfo(const StringList& input, StringList dimNames,
fi.filename = filename;
fi.driver = driver;

// Detect LAS input with LAS version < 4 so that we can handle the legacy
// classification bits.
if (driver == "readers.las")
{
pdal::MetadataNode minor = root.findChild("minor_version");
pdal::MetadataNode major = root.findChild("major_version");
if (minor.valid() && major.valid())
fi.fileVersion = 10 * major.value<int>() + minor.value<int>();
}

// Accept dimension names if there are no limits or this name is in the list
// of desired dimensions.
for (const std::string& name : qi.m_dimNames)
Expand Down
7 changes: 6 additions & 1 deletion epf/EpfTypes.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ constexpr int NumFileProcessors = 8;

struct FileInfo
{
FileInfo() : numPoints(0), start(0)
FileInfo() :
numPoints(0), start(0), untwineBitsDim(pdal::Dimension::Id::Unknown), fileVersion(0)
{}

std::string filename;
Expand All @@ -51,6 +52,10 @@ struct FileInfo
uint64_t start;
pdal::BOX3D bounds;
pdal::SpatialReference srs;
pdal::Dimension::Id untwineBitsDim;
int untwineBitsOffset;
// Currently only set for LAS files.
int fileVersion;

bool valid() const
{ return filename.size(); }
Expand Down
102 changes: 84 additions & 18 deletions epf/FileProcessor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ namespace
// case of user-defined dimensions that the IDs could vary for the same-named dimension
// in different input files. The "dim" field represents the ID of the dimension
// we're reading from. "offset" is the corresponding notion in the output packed point data.
void setDimensions(pdal::PointLayoutPtr layout, FileInfo& fi, int& classflagsOffset)
void setDimensions(pdal::PointLayoutPtr layout, FileInfo& fi)
{
for (FileDimInfo& di : fi.dimInfo)
{
Expand All @@ -41,7 +41,7 @@ void setDimensions(pdal::PointLayoutPtr layout, FileInfo& fi, int& classflagsOff
// output point. Fetch that offset and set it. We will probably do this several
// times (once for each bit), but the value should always be the same.
if (di.shift != -1)
classflagsOffset = di.offset;
fi.untwineBitsOffset = di.offset;
}
}

Expand All @@ -53,6 +53,79 @@ FileProcessor::FileProcessor(const FileInfo& fi, size_t pointSize, const Grid& g
m_fi(fi), m_cellMgr(pointSize, writer), m_grid(grid), m_progress(progress)
{}

class BasePointProcessor
{
public:
BasePointProcessor(const FileInfo& fi) : m_fi(fi)
{}
virtual ~BasePointProcessor()
{}

virtual void fill(const pdal::PointRef& src, Point& dst) = 0;

protected:
const FileInfo& m_fi;
};
using PointProcessorPtr = std::unique_ptr<BasePointProcessor>;

// These processors could probably be improved performance-wise by breaking the dimensions
// up into types in the ctor to avoid the conditionals in fill().
// Could also make FileProcessor take these as a template type to avoid having fill() be
// virtual.
class StdPointProcessor : public BasePointProcessor
{
public:
using BasePointProcessor::BasePointProcessor;

void fill(const pdal::PointRef& src, Point& dst) override
{
uint8_t untwineBits = 0;
for (const FileDimInfo& fdi : m_fi.dimInfo)
{
if (fdi.shift == -1)
src.getField(reinterpret_cast<char *>(dst.data() + fdi.offset),
fdi.dim, fdi.type);
else
untwineBits |= (src.getFieldAs<uint8_t>(fdi.dim) << fdi.shift);
}

// We pack all the bitfields into the "untwine bits" field.
memcpy(dst.data() + m_fi.untwineBitsOffset, &untwineBits, 1);
}
};

class LegacyLasPointProcessor : public BasePointProcessor
{
public:
using BasePointProcessor::BasePointProcessor;

void fill(const pdal::PointRef& src, Point& dst) override
{
uint8_t untwineBits = 0;
for (const FileDimInfo& fdi : m_fi.dimInfo)
{
if (fdi.dim == pdal::Dimension::Id::Classification)
{
uint8_t classification = src.getFieldAs<uint8_t>(fdi.dim);
if (classification == 12)
untwineBits |= 0x08; // Set the overlap bit.
untwineBits |= (classification >> 5);
classification &= 0x1F;
memcpy(dst.data() + fdi.offset, &classification, 1);
}
else if (fdi.shift == -1)
src.getField(reinterpret_cast<char *>(dst.data() + fdi.offset),
fdi.dim, fdi.type);
else
untwineBits |= (src.getFieldAs<uint8_t>(fdi.dim) << fdi.shift);
}

// We pack all the bitfields into the "untwine bits" field.
memcpy(dst.data() + m_fi.untwineBitsOffset, &untwineBits, 1);
}
};


void FileProcessor::run()
{
pdal::Options opts;
Expand All @@ -67,7 +140,6 @@ void FileProcessor::run()
pdal::Stage *s = factory.createStage(m_fi.driver);
s->setOptions(opts);

int classflagsOffset;
PointCount count = 0;

// We need to move the data from the PointRef to some output buffer. We copy the data
Expand All @@ -79,25 +151,19 @@ void FileProcessor::run()
// into which we can write data.
Cell *cell = m_cellMgr.get(VoxelKey());

PointProcessorPtr ptProcessor;
if (m_fi.driver == "readers.las" && m_fi.fileVersion < 14)
ptProcessor = std::make_unique<LegacyLasPointProcessor>(m_fi);
else
ptProcessor = std::make_unique<StdPointProcessor>(m_fi);

pdal::StreamCallbackFilter f;
f.setCallback([this, &count, &cell, &classflagsOffset](pdal::PointRef& point)
f.setCallback([this, &count, &cell, ptProcessor = ptProcessor.get()](pdal::PointRef& point)
{
// Write the data into the point buffer in the cell. This is the *last*
// cell buffer that we used. We're hoping that it's the right one.
Point p = cell->point();
uint8_t untwineBits = 0;
for (const FileDimInfo& fdi : m_fi.dimInfo)
{
if (fdi.shift == -1)
point.getField(reinterpret_cast<char *>(p.data() + fdi.offset),
fdi.dim, fdi.type);
else
untwineBits |= (point.getFieldAs<uint8_t>(fdi.dim) << fdi.shift);
}

// We pack all the bitfields into classflags.
if (untwineBits)
memcpy(p.data() + classflagsOffset, &untwineBits, 1);
ptProcessor->fill(point, p);

// Find the actual cell that this point belongs in. If it's not the one
// we chose, copy the data to the correct cell.
Expand Down Expand Up @@ -131,7 +197,7 @@ void FileProcessor::run()
try
{
f.prepare(t);
setDimensions(t.layout(), m_fi, classflagsOffset);
setDimensions(t.layout(), m_fi);
f.execute(t);
}
catch (const pdal::pdal_error& err)
Expand Down

0 comments on commit dcf1cf9

Please sign in to comment.