diff --git a/doc/stages/index.rst b/doc/stages/index.rst index 6ee75fc566..d99112635a 100644 --- a/doc/stages/index.rst +++ b/doc/stages/index.rst @@ -37,6 +37,7 @@ Writers writers.las writers.nitf writers.oci + writers.ply writers.p2g writers.pcd writers.pgpointcloud diff --git a/doc/stages/writers.ply.rst b/doc/stages/writers.ply.rst new file mode 100644 index 0000000000..325195e4a1 --- /dev/null +++ b/doc/stages/writers.ply.rst @@ -0,0 +1,46 @@ +.. _writers.ply: + +writers.ply +=========== + +The **ply writer** writes the `polygon file format`_, a common file format for storing three dimensional models. +The `rply library`_ is included with the PDAL source, so there are no external dependencies. + +Use the ``storage_mode`` option to choose the type of ply file to write. +You can choose from: + +- ``default``: write a binary ply file using your host's byte ordering. + If you do not specify a ``storage_mode``, this is the default. +- ``ascii``: write an ascii file (warning: these can be HUGE). +- ``little endian``: write a binary ply file with little endian byte ordering. +- ``big endian``: write a binary ply file with big endian byte ordering. + + +Example +------- + +.. code-block:: xml + + + + + + + + + + + + +Options +------- + +filename + ply file to write [Required] + +storage_mode + Type of ply file to write [default: host-ordered binary] + + +.. _polygon file format: http://paulbourke.net/dataformats/ply/ +.. _rply library: http://w3.impa.br/~diego/software/rply/ diff --git a/io/ply/CMakeLists.txt b/io/ply/CMakeLists.txt index 8751b99d27..10b77056e4 100644 --- a/io/ply/CMakeLists.txt +++ b/io/ply/CMakeLists.txt @@ -1,10 +1,12 @@ set(src PlyReader.cpp + PlyWriter.cpp ${PROJECT_SOURCE_DIR}/vendor/rply-1.1.3/rply.c ) set(inc PlyReader.hpp + PlyWriter.hpp ${PROJECT_SOURCE_DIR}/vendor/rply-1.1.3/rply.h ) diff --git a/io/ply/PlyWriter.cpp b/io/ply/PlyWriter.cpp new file mode 100644 index 0000000000..9f63438ee3 --- /dev/null +++ b/io/ply/PlyWriter.cpp @@ -0,0 +1,209 @@ +/****************************************************************************** +* Copyright (c) 2015, Peter J. Gadomski +* +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following +* conditions are met: +* +* * Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* * Redistributions in binary form must reproduce the above copyright +* notice, this list of conditions and the following disclaimer in +* the documentation and/or other materials provided +* with the distribution. +* * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the +* names of its contributors may be used to endorse or promote +* products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS +* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED +* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY +* OF SUCH DAMAGE. +****************************************************************************/ + +#include "PlyWriter.hpp" + +#include + + +namespace pdal +{ +namespace +{ + + +void createErrorCallback(p_ply ply, const char* message) +{ + std::stringstream ss; + ss << "Error when creating ply file: " << message; + throw pdal_error(ss.str()); +} + + +e_ply_type getPlyType(Dimension::Type::Enum type) +{ + using namespace Dimension::Type; + switch (type) + { + case Unsigned8: + return PLY_UINT8; + case Signed8: + return PLY_INT8; + case Unsigned16: + return PLY_UINT16; + case Signed16: + return PLY_INT16; + case Unsigned32: + return PLY_UIN32; + case Signed32: + return PLY_INT32; + case Unsigned64: + return PLY_FLOAT64; + case Signed64: + return PLY_FLOAT64; + case Float: + return PLY_FLOAT32; + case Double: + return PLY_FLOAT64; + default: + // I went back and forth about throwing here, but since it's not + // wrong to fall back onto a double (just bad, b/c it can take up + // extra space), I chose to default rather than throw. + return PLY_FLOAT64; + } +} +} + + +static PluginInfo const s_info = PluginInfo( + "writers.ply", + "ply writer", + "http://pdal.io/stages/writers.ply.html" + ); + +CREATE_STATIC_PLUGIN(1, 0, PlyWriter, Writer, s_info) + +std::string PlyWriter::getName() const { return s_info.name; } + + +PlyWriter::PlyWriter() + : m_ply(nullptr) + , m_pointCollector(nullptr) + , m_storageMode(PLY_DEFAULT) +{} + + +void PlyWriter::processOptions(const Options& options) +{ + std::string storageMode = options.getValueOrDefault("storage_mode", "default"); + if (storageMode == "ascii") + { + m_storageMode = PLY_ASCII; + } + else if (storageMode == "little endian") + { + m_storageMode = PLY_LITTLE_ENDIAN; + } + else if (storageMode == "big endian") + { + m_storageMode = PLY_BIG_ENDIAN; + } + else if (storageMode == "default") + { + m_storageMode = PLY_DEFAULT; + } + else + { + std::stringstream ss; + ss << "Unknown storage mode '" << storageMode << + "'. Known storage modes are: 'ascii', 'little endian', 'big endian', and 'default'"; + throw pdal_error(ss.str()); + } +} + + +void PlyWriter::ready(PointTableRef table) +{ + m_ply = ply_create(m_filename.c_str(), m_storageMode, createErrorCallback, 0, nullptr); + if (!m_ply) + { + std::stringstream ss; + ss << "Could not open file for writing: " << m_filename; + throw pdal_error(ss.str()); + } + m_pointCollector.reset(new PointView(table)); +} + + +void PlyWriter::write(const PointViewPtr data) +{ + m_pointCollector->append(*data); +} + + +void PlyWriter::done(PointTableRef table) +{ + if (!ply_add_element(m_ply, "vertex", m_pointCollector->size())) + { + std::stringstream ss; + ss << "Could not add vertex element"; + throw pdal_error(ss.str()); + } + auto dimensions = table.layout()->dims(); + for (auto dim : dimensions) { + std::string name = Dimension::name(dim); + e_ply_type plyType = getPlyType(Dimension::defaultType(dim)); + if (!ply_add_scalar_property(m_ply, name.c_str(), plyType)) + { + std::stringstream ss; + ss << "Could not add scalar property '" << name << "'"; + throw pdal_error(ss.str()); + } + } + if (!ply_add_comment(m_ply, "Generated by PDAL")) + { + std::stringstream ss; + ss << "Could not add comment"; + throw pdal_error(ss.str()); + } + if (!ply_write_header(m_ply)) + { + std::stringstream ss; + ss << "Could not write ply header"; + throw pdal_error(ss.str()); + } + + for (PointId index = 0; index < m_pointCollector->size(); ++index) + { + for (auto dim : dimensions) + { + double value = m_pointCollector->getFieldAs(dim, index); + if (!ply_write(m_ply, value)) + { + std::stringstream ss; + ss << "Error writing dimension '" << Dimension::name(dim) << + "' of point number " << index; + throw pdal_error(ss.str()); + } + } + } + + if (!ply_close(m_ply)) + { + throw pdal_error("Error closing ply file"); + } +} + + +} diff --git a/io/ply/PlyWriter.hpp b/io/ply/PlyWriter.hpp new file mode 100644 index 0000000000..4881366c9b --- /dev/null +++ b/io/ply/PlyWriter.hpp @@ -0,0 +1,71 @@ +/****************************************************************************** +* Copyright (c) 2015, Peter J. Gadomski +* +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following +* conditions are met: +* +* * Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* * Redistributions in binary form must reproduce the above copyright +* notice, this list of conditions and the following disclaimer in +* the documentation and/or other materials provided +* with the distribution. +* * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the +* names of its contributors may be used to endorse or promote +* products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS +* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED +* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY +* OF SUCH DAMAGE. +****************************************************************************/ + +#include "rply.h" + +#include +#include + + +extern "C" int32_t PlyWriter_ExitFunc(); +extern "C" PF_ExitFunc PlyWriter_InitPlugin(); + + +namespace pdal +{ + + +class PDAL_DLL PlyWriter : public Writer +{ +public: + static void * create(); + static int32_t destroy(void *); + std::string getName() const; + + PlyWriter(); + +private: + virtual void processOptions(const Options& options); + virtual void ready(PointTableRef table); + virtual void write(const PointViewPtr data); + virtual void done(PointTableRef table); + + p_ply m_ply; + PointViewPtr m_pointCollector; + e_ply_storage_mode m_storageMode; + +}; + + +} diff --git a/src/StageFactory.cpp b/src/StageFactory.cpp index 01572dde5e..bee506398d 100644 --- a/src/StageFactory.cpp +++ b/src/StageFactory.cpp @@ -66,6 +66,7 @@ // writers #include #include +#include #include #include #include @@ -216,6 +217,7 @@ StageFactory::StageFactory(bool no_plugins) // writers PluginManager::initializePlugin(BpfWriter_InitPlugin); PluginManager::initializePlugin(LasWriter_InitPlugin); + PluginManager::initializePlugin(PlyWriter_InitPlugin); PluginManager::initializePlugin(RialtoWriter_InitPlugin); PluginManager::initializePlugin(SbetWriter_InitPlugin); PluginManager::initializePlugin(TextWriter_InitPlugin); diff --git a/test/unit/CMakeLists.txt b/test/unit/CMakeLists.txt index 1a52691c67..10438347b0 100644 --- a/test/unit/CMakeLists.txt +++ b/test/unit/CMakeLists.txt @@ -80,6 +80,7 @@ PDAL_ADD_TEST(pdal_io_las_reader_test FILES io/las/LasReaderTest.cpp) PDAL_ADD_TEST(pdal_io_las_writer_test FILES io/las/LasWriterTest.cpp) PDAL_ADD_TEST(pdal_io_optech_test FILES io/optech/OptechReaderTest.cpp) PDAL_ADD_TEST(pdal_io_ply_reader_test FILES io/ply/PlyReaderTest.cpp) +PDAL_ADD_TEST(pdal_io_ply_writer_test FILES io/ply/PlyWriterTest.cpp) PDAL_ADD_TEST(pdal_io_rialto_test FILES io/rialto/RialtoWriterTest.cpp) PDAL_ADD_TEST(pdal_io_qfit_test FILES io/qfit/QFITReaderTest.cpp) PDAL_ADD_TEST(pdal_io_sbet_reader_test FILES io/sbet/SbetReaderTest.cpp) diff --git a/test/unit/io/ply/PlyWriterTest.cpp b/test/unit/io/ply/PlyWriterTest.cpp new file mode 100644 index 0000000000..2ba6bf8574 --- /dev/null +++ b/test/unit/io/ply/PlyWriterTest.cpp @@ -0,0 +1,77 @@ +/****************************************************************************** +* Copyright (c) 2015, Peter J. Gadomski +* +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following +* conditions are met: +* +* * Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* * Redistributions in binary form must reproduce the above copyright +* notice, this list of conditions and the following disclaimer in +* the documentation and/or other materials provided +* with the distribution. +* * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the +* names of its contributors may be used to endorse or promote +* products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS +* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED +* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY +* OF SUCH DAMAGE. +****************************************************************************/ + +#include + +#include +#include +#include +#include "Support.hpp" + + +namespace pdal +{ + + +TEST(PlyWriter, Constructor) +{ + PlyWriter writer1; + + StageFactory f; + std::unique_ptr writer2(f.createStage("writers.ply")); + EXPECT_TRUE(writer2.get()); +} + + +TEST(PlyWriter, Write) +{ + Options readerOptions; + readerOptions.add("count", 750); + readerOptions.add("mode", "random"); + FauxReader reader; + reader.setOptions(readerOptions); + + Options writerOptions; + writerOptions.add("filename", Support::temppath("out.ply")); + PlyWriter writer; + writer.setOptions(writerOptions); + writer.setInput(reader); + + PointTable table; + writer.prepare(table); + writer.execute(table); +} + + +}