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);
+}
+
+
+}