diff --git a/doc/stages/writers.las.rst b/doc/stages/writers.las.rst index 3d522fdb69..2492a4a2cb 100644 --- a/doc/stages/writers.las.rst +++ b/doc/stages/writers.las.rst @@ -50,10 +50,21 @@ forward which is equivalent to specifying all the values EXCEPT the scale and offset values. Scale and offset values can be forwarded as a group by using the special values 'scale' and 'offset' respectively. The special - value 'all' is equivalent to specifying 'header', 'scale' and 'offset'. - If an option is specified explicitly, it will override any forwarded value. - If a LAS file is the result of multiple LAS input files, the values to be - forwarded must match or they will be ignored. + value 'all' is equivalent to specifying 'header', 'scale', 'offset' and + 'vlr' (see below). + If a header option is specified explicitly, it will override any forwarded + header value. + If a LAS file is the result of multiple LAS input files, the header values + to be forwarded must match or they will be ignored and a default will + be used instead. + + VLRs can be forwarded by using the special value 'vlr'. VLRs containing + the following User IDs are NOT forwarded: 'LASF_Projection', 'LASF_Spec', + 'liblas', 'laszip encoded'. These VLRs are known to contain information + regarding the formatting of the data and will be rebuilt properly in the + output file as necessary. Unlike header values, VLRs from multiple input + files are accumulated and each is written to the output file. Forwarded + VLRs may contain duplicate User ID/Record ID pairs. minor_version All LAS files are version 1, but the minor version (0 - 4) can be specified diff --git a/include/pdal/Metadata.hpp b/include/pdal/Metadata.hpp index 16da47273e..ec677ab1a2 100644 --- a/include/pdal/Metadata.hpp +++ b/include/pdal/Metadata.hpp @@ -558,6 +558,21 @@ class PDAL_DLL MetadataNode return MetadataNode(); } + template + MetadataNodeList findChildren(PREDICATE p) + { + MetadataNodeList matches; + + auto nodes = children(); + for (auto ai = nodes.begin(); ai != nodes.end(); ++ai) + { + MetadataNode& n = *ai; + if (p(n)) + matches.push_back(n); + } + return matches; + } + template MetadataNode findChild(PREDICATE p) const { diff --git a/io/las/LasReader.cpp b/io/las/LasReader.cpp index 89a476d507..05494422c2 100644 --- a/io/las/LasReader.cpp +++ b/io/las/LasReader.cpp @@ -159,6 +159,7 @@ void LasReader::ready(PointTableRef table, MetadataNode& m) setSrsFromVlrs(m); MetadataNode forward = table.privateMetadata("lasforward"); extractHeaderMetadata(forward, m); + extractVlrMetadata(forward, m); if (m_lasHeader.compressed()) { @@ -209,13 +210,13 @@ template void addForwardMetadata(MetadataNode& forward, MetadataNode& m, const std::string& name, T val, const std::string description = "") { - m.add(name, val, description); + MetadataNode n = m.add(name, val, description); // If the entry doesn't already exist, just add it. MetadataNode f = forward.findChild(name); if (!f.valid()) { - forward.add(name, val); + forward.add(n); return; } @@ -484,7 +485,7 @@ SpatialReference LasReader::getSrsFromGeotiffVlr() } -void LasReader::extractVlrMetadata(MetadataNode& m) +void LasReader::extractVlrMetadata(MetadataNode& forward, MetadataNode& m) { static const size_t DATA_LEN_MAX = 1000000; @@ -506,6 +507,12 @@ void LasReader::extractVlrMetadata(MetadataNode& m) vlrNode.add("record_id", vlr.recordId(), "Record ID specified by the user."); vlrNode.add("description", vlr.description()); + + if ((vlr.userId() != TRANSFORM_USER_ID) && + (vlr.userId() != SPEC_USER_ID) && + (vlr.userId() != LASZIP_USER_ID) && + (vlr.userId() != LIBLAS_USER_ID)) + forward.add(vlrNode); } } diff --git a/io/las/LasReader.hpp b/io/las/LasReader.hpp index 53d6eb7762..2fd511bdfa 100644 --- a/io/las/LasReader.hpp +++ b/io/las/LasReader.hpp @@ -57,7 +57,8 @@ class PDAL_DLL LasReader : public pdal::Reader { friend class NitfReader; public: - LasReader() : pdal::Reader(), m_index(0), m_istream(NULL), m_initialized(false) + LasReader() : pdal::Reader(), m_index(0), m_istream(NULL), + m_initialized(false) {} virtual ~LasReader() @@ -66,7 +67,6 @@ class PDAL_DLL LasReader : public pdal::Reader static void * create(); static int32_t destroy(void *); std::string getName() const; - Options getDefaultOptions(); const LasHeader& header() const @@ -88,14 +88,15 @@ class PDAL_DLL LasReader : public pdal::Reader return m_istream; } virtual void destroyStream() + { + if (m_istream && m_initialized) { - if (m_istream && m_initialized) - { - FileUtils::closeFile(m_istream); - m_istream = NULL; - m_initialized = false; - } + FileUtils::closeFile(m_istream); + m_istream = NULL; + m_initialized = false; } + } + private: LasError m_error; LasHeader m_lasHeader; @@ -117,7 +118,7 @@ class PDAL_DLL LasReader : public pdal::Reader SpatialReference getSrsFromWktVlr(); SpatialReference getSrsFromGeotiffVlr(); void extractHeaderMetadata(MetadataNode& forward, MetadataNode& m); - void extractVlrMetadata(MetadataNode& m); + void extractVlrMetadata(MetadataNode& forward, MetadataNode& m); virtual QuickInfo inspect(); virtual void ready(PointTableRef table) { ready(table, m_metadata); } diff --git a/io/las/LasWriter.cpp b/io/las/LasWriter.cpp index eab6bc4104..2ff252ccd9 100644 --- a/io/las/LasWriter.cpp +++ b/io/las/LasWriter.cpp @@ -197,7 +197,10 @@ void LasWriter::fillForwardList(const Options &options) for (auto& name : forwards) { if (name == "all") + { m_forwards.insert(all.begin(), all.end()); + m_forwardVlrs = true; + } else if (name == "header") m_forwards.insert(header.begin(), header.end()); else if (name == "scale") @@ -290,11 +293,11 @@ void LasWriter::readyTable(PointTableRef table) m_srs = getSpatialReference().empty() ? table.spatialRef() : getSpatialReference(); - setVlrsFromMetadata(); setVlrsFromSpatialRef(); setExtraBytesVlr(); MetadataNode forward = table.privateMetadata("lasforward"); fillHeader(forward); + setVlrsFromMetadata(forward); } @@ -379,26 +382,27 @@ MetadataNode LasWriter::findVlrMetadata(MetadataNode node, /// Set VLRs from metadata for forwarded info, or from option-provided data /// otherwise. -void LasWriter::setVlrsFromMetadata() +void LasWriter::setVlrsFromMetadata(MetadataNode& forward) { std::vector data; - for (auto oi = m_optionInfos.begin(); oi != m_optionInfos.end(); ++oi) - { - VlrOptionInfo& vlrInfo = *oi; + if (!m_forwardVlrs) + return; + + auto pred = [](MetadataNode n) + { return Utils::startsWith(n.name(), "vlr_"); }; - if (vlrInfo.m_name == "FORWARD") + MetadataNodeList nodes = forward.findChildren(pred); + for (auto& n : nodes) + { + const MetadataNode& userIdNode = n.findChild("user_id"); + const MetadataNode& recordIdNode = n.findChild("record_id"); + if (recordIdNode.valid() && userIdNode.valid()) { - MetadataNode m = findVlrMetadata(m_metadata, vlrInfo.m_recordId, - vlrInfo.m_userId); - if (m.empty()) - continue; - data = Utils::base64_decode(m.value()); + data = Utils::base64_decode(n.value()); + uint16_t recordId = (uint16_t)std::stoi(recordIdNode.value()); + addVlr(userIdNode.value(), recordId, n.description(), data); } - else - data = Utils::base64_decode(vlrInfo.m_value); - addVlr(vlrInfo.m_userId, vlrInfo.m_recordId, vlrInfo.m_description, - data); } } diff --git a/io/las/LasWriter.hpp b/io/las/LasWriter.hpp index fb281e09cf..a6b56c6ecf 100644 --- a/io/las/LasWriter.hpp +++ b/io/las/LasWriter.hpp @@ -135,7 +135,7 @@ class PDAL_DLL LasWriter : public FlexWriter void fillHeader(MetadataNode& forward); point_count_t fillWriteBuf(const PointView& view, PointId startId, std::vector& buf); - void setVlrsFromMetadata(); + void setVlrsFromMetadata(MetadataNode& forward); MetadataNode findVlrMetadata(MetadataNode node, uint16_t recordId, const std::string& userId); void setExtraBytesVlr(); diff --git a/test/unit/io/las/LasWriterTest.cpp b/test/unit/io/las/LasWriterTest.cpp index 301e28c850..7f2c96ffa6 100644 --- a/test/unit/io/las/LasWriterTest.cpp +++ b/test/unit/io/las/LasWriterTest.cpp @@ -300,6 +300,50 @@ TEST(LasWriterTest, forward) EXPECT_EQ(n1.findChild("creation_year").value(), 2014); } +TEST(LasWriterTest, forwardvlr) +{ + Options readerOps1; + + readerOps1.add("filename", Support::datapath("las/lots_of_vlr.las")); + LasReader r1; + r1.addOptions(readerOps1); + + std::string testfile = Support::temppath("tmp.las"); + FileUtils::deleteFile(testfile); + + Options writerOps; + writerOps.add("forward", "vlr"); + writerOps.add("filename", testfile); + + LasWriter w; + w.setInput(r1); + w.addOptions(writerOps); + + PointTable t; + + w.prepare(t); + w.execute(t); + + Options readerOps; + readerOps.add("filename", testfile); + + LasReader r; + + r.setOptions(readerOps); + + PointTable t2; + + r.prepare(t2); + r.execute(t2); + + MetadataNode forward = t2.privateMetadata("lasforward"); + + auto pred = [](MetadataNode temp) + { return Utils::startsWith(temp.name(), "vlr_"); }; + MetadataNodeList nodes = forward.findChildren(pred); + EXPECT_EQ(nodes.size(), 388UL); +} + // Test that data from three input views gets written to separate output files. TEST(LasWriterTest, flex) {