diff --git a/doc/stages/writers.las.rst b/doc/stages/writers.las.rst index 6b8f25e701..fbdaaed5db 100644 --- a/doc/stages/writers.las.rst +++ b/doc/stages/writers.las.rst @@ -152,5 +152,10 @@ extra_dims bytes VLR (User ID: LASF_Spec, Record ID: 4), is created that describes the extra dimensions specified by this option. + The special value 'all' can be used in place of a dimension/type list + to request + that all dimensions that can't be stored in the predefined LAS point + record get added as extra data at the end of each point record. + .. _LAS format: http://asprs.org/Committee-General/LASer-LAS-File-Format-Exchange-Activities.html diff --git a/io/las/LasHeader.cpp b/io/las/LasHeader.cpp index 243c4e5743..b136abed31 100644 --- a/io/las/LasHeader.cpp +++ b/io/las/LasHeader.cpp @@ -90,7 +90,7 @@ void LasHeader::setSummary(const SummaryData& summary) { m_pointCount = summary.getTotalNumPoints(); for (size_t num = 0; num < RETURN_COUNT; ++num) - m_pointCountByReturn[num] = summary.getReturnCount(num); + m_pointCountByReturn[num] = (int)summary.getReturnCount(num); m_bounds = summary.getBounds(); } @@ -197,6 +197,33 @@ void LasHeader::put(OLeStream& out, boost::uuids::uuid uuid) } +Dimension::IdList LasHeader::usedDims() const +{ + using namespace Dimension; + + Dimension::Id::Enum dims[] = { Id::ReturnNumber, Id::NumberOfReturns, + Id::X, Id::Y, Id::Z, Id::Intensity, Id::ScanChannel, + Id::ScanDirectionFlag, Id::EdgeOfFlightLine, Id::Classification, + Id::UserData, Id::ScanAngleRank, Id::PointSourceId }; + + // This mess is because MSVC doesn't support initializer lists. + Dimension::IdList ids; + std::copy(std::begin(dims), std::end(dims), std::back_inserter(ids)); + + if (hasTime()) + ids.push_back(Id::GpsTime); + if (hasColor()) + { + ids.push_back(Id::Red); + ids.push_back(Id::Green); + ids.push_back(Id::Blue); + } + if (hasInfrared()) + ids.push_back(Id::Infrared); + + return ids; +} + ILeStream& operator>>(ILeStream& in, LasHeader& h) { uint8_t versionMajor; diff --git a/io/las/LasHeader.hpp b/io/las/LasHeader.hpp index c8513fb807..02aff4b251 100644 --- a/io/las/LasHeader.hpp +++ b/io/las/LasHeader.hpp @@ -41,6 +41,7 @@ #include #include +#include #include #include #include @@ -366,6 +367,7 @@ class PDAL_DLL LasHeader void setSummary(const SummaryData& summary); bool valid() const; + Dimension::IdList usedDims() const; friend ILeStream& operator>>(ILeStream&, LasHeader& h); friend OLeStream& operator<<(OLeStream&, const LasHeader& h); diff --git a/io/las/LasUtils.cpp b/io/las/LasUtils.cpp index a84a1fc76f..5e5288c174 100644 --- a/io/las/LasUtils.cpp +++ b/io/las/LasUtils.cpp @@ -185,9 +185,16 @@ namespace LasUtils std::vector parse(const StringList& dimString) { std::vector extraDims; + bool all = false; for (auto& dim : dimString) { + if (dim == "all") + { + all = true; + continue; + } + StringList s = Utils::split2(dim, '='); if (s.size() != 2) { @@ -211,6 +218,15 @@ std::vector parse(const StringList& dimString) ExtraDim ed(s[0], type); extraDims.push_back(ed); } + + if (all) + { + if (extraDims.size()) + throw (pdal_error("Can't specify specific extra dimensions with " + "special 'all' keyword.")); + extraDims.push_back(ExtraDim("all", Dimension::Type::None)); + } + return extraDims; } diff --git a/io/las/LasWriter.cpp b/io/las/LasWriter.cpp index 42a4168247..01202e0d2a 100644 --- a/io/las/LasWriter.cpp +++ b/io/las/LasWriter.cpp @@ -156,6 +156,23 @@ void LasWriter::processOptions(const Options& options) void LasWriter::prepared(PointTableRef table) { + PointLayoutPtr layout = table.layout(); + + // If we've asked for all dimensions, add to extraDims all dimensions + // in the layout that aren't already destined for LAS output. + if (m_extraDims.size() == 1 && m_extraDims[0].m_name == "all") + { + m_extraDims.clear(); + Dimension::IdList ids = m_lasHeader.usedDims(); + DimTypeList dimTypes = layout->dimTypes(); + for (auto& dt : dimTypes) + { + if (!Utils::contains(ids, dt.m_id)) + m_extraDims.push_back( + ExtraDim(layout->dimName(dt.m_id), dt.m_type)); + } + } + m_extraByteLen = 0; for (auto& dim : m_extraDims) { diff --git a/test/data/bpf/simple-extra.bpf b/test/data/bpf/simple-extra.bpf new file mode 100644 index 0000000000..3508c11fd3 Binary files /dev/null and b/test/data/bpf/simple-extra.bpf differ diff --git a/test/unit/io/las/LasWriterTest.cpp b/test/unit/io/las/LasWriterTest.cpp index d3d4f87eaa..f8c500625b 100644 --- a/test/unit/io/las/LasWriterTest.cpp +++ b/test/unit/io/las/LasWriterTest.cpp @@ -41,6 +41,7 @@ #include #include #include +#include #include #include @@ -232,6 +233,60 @@ TEST(LasWriterTest, extra_dims) } } +TEST(LasWriterTest, all_extra_dims) +{ + Options readerOps; + + readerOps.add("filename", Support::datapath("bpf/simple-extra.bpf")); + BpfReader reader; + reader.setOptions(readerOps); + + FileUtils::deleteFile(Support::temppath("simple.las")); + + Options writerOps; + writerOps.add("extra_dims", "all"); + writerOps.add("filename", Support::temppath("simple.las")); + writerOps.add("minor_version", 4); + LasWriter writer; + writer.setInput(reader); + writer.setOptions(writerOps); + + PointTable table; + writer.prepare(table); + writer.execute(table); + + Options ops; + ops.add("filename", Support::temppath("simple.las")); + + LasReader r; + r.setOptions(ops); + + PointTable t2; + r.prepare(t2); + Dimension::Id::Enum foo = t2.layout()->findDim("Foo"); + Dimension::Id::Enum bar = t2.layout()->findDim("Bar"); + Dimension::Id::Enum baz = t2.layout()->findDim("Baz"); + + PointViewSet s = r.execute(t2); + EXPECT_EQ(s.size(), 1u); + PointViewPtr v = *s.begin(); + + // We test for floats instead of doubles because when X, Y and Z + // get written, they are written scaled, which loses precision. The + // foo, bar and baz values are written as full-precision doubles. + for (PointId i = 0; i < v->size(); ++i) + { + using namespace Dimension; + + EXPECT_FLOAT_EQ(v->getFieldAs(Id::X, i), + v->getFieldAs(foo, i)); + EXPECT_FLOAT_EQ(v->getFieldAs(Id::Y, i), + v->getFieldAs(bar, i)); + EXPECT_FLOAT_EQ(v->getFieldAs(Id::Z, i), + v->getFieldAs(baz, i)); + } +} + // Merge a couple of 1.4 LAS files with point formats 1 and 6. Write the // output with version 3. Verify that you get point format 3 (default), // LAS 1.3 output and other header data forwarded. @@ -591,57 +646,6 @@ TEST(LasWriterTest, simple) } **/ -//ABELL -/** -TEST(LasWriterTest, LasWriterTest_test_simple_laz) -{ - PointTable table; - - WriterOpts writerOpts; - writerOpts.add("compressed", true); - writerOpts.add("creation_year", 0); - writerOpts.add("creation_doy", 0); - writerOpts.add("system_id", ""); - writerOpts.add("software_id", "TerraScan"); - - - // remove file from earlier run, if needed - FileUtils::deleteFile("laszip/LasWriterTest_test_simple_laz.laz"); - - LasReader reader(Support::datapath("laszip/basefile.las")); - - std::ostream* ofs = FileUtils::createFile( - Support::temppath("LasWriterTest_test_simple_laz.laz")); - - // need to scope the writer, so that's it dtor can use the stream - std::shared_ptr writer(new LasWriter)(ofs); - writer->setOptions(writer); - writer->setInput(&reader); - - writer->prepare(table); - writer->execute(table); - - FileUtils::closeFile(ofs); - - { - LasReader reader( - Support::temppath("LasWriterTest_test_simple_laz.laz")); - } - - // these two files only differ by the description string in the VLR. - // This now skips the entire LASzip VLR for comparison. - const uint32_t numdiffs =Support::diff_files( - Support::temppath("LasWriterTest_test_simple_laz.laz"), - Support::datapath("laszip/laszip-generated.laz"), - 227, 106); - EXPECT_EQ(numdiffs, 0u); - - if (numdiffs == 0) - FileUtils::deleteFile( - Support::temppath("LasWriterTest_test_simple_laz.laz")); -} -**/ - //ABELL /** static void test_a_format(const std::string& refFile, uint8_t majorVersion,