diff --git a/filters/InfoFilter.cpp b/filters/InfoFilter.cpp new file mode 100644 index 0000000000..d9e0fc12e9 --- /dev/null +++ b/filters/InfoFilter.cpp @@ -0,0 +1,263 @@ +/****************************************************************************** +* Copyright (c) 2018, Hobu Inc. +* +* 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. 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 "InfoFilter.hpp" + +#include +#include + +#include + +namespace pdal +{ + +static StaticPluginInfo const s_info +{ + "filters.info", + "Gather basic info about points.", + "http://pdal.io/stages/filters.info.html" +}; + +CREATE_STATIC_STAGE(InfoFilter, s_info) + +std::string InfoFilter::getName() const { return s_info.name; } + +void InfoFilter::addArgs(ProgramArgs& args) +{ + args.add("point,p", "Point to dump\n--point=\"1-5,10,100-200\" (0 indexed)", + m_pointSpec); + args.add("query", + "Return points in order of distance from the specified " + "location (2D or 3D)\n" + "--query Xcoord,Ycoord[,Zcoord][/count]", + m_querySpec); +} + + +void InfoFilter::parsePointSpec() +{ + auto parseInt = [this](const std::string& s) + { + uint32_t i; + + if (!Utils::fromString(s, i)) + throwError("Invalid integer '" + s + "in 'point' option"); + return i; + }; + + auto addRange = [this, &parseInt](const std::string& begin, + const std::string& end) + { + PointId low = parseInt(begin); + PointId high = parseInt(end); + if (low > high) + throwError("Invalid range in 'point' option: '" + + begin + "-" + end); + while (low <= high) + m_idList.push_back(low++); + }; + + //Remove whitespace from string with awful remove/erase idiom. + Utils::trim(m_pointSpec); + + StringList ranges = Utils::split2(m_pointSpec, ','); + for (std::string& s : ranges) + { + StringList limits = Utils::split(s, '-'); + if (limits.size() == 1) + m_idList.push_back(parseInt(limits[0])); + else if (limits.size() == 2) + addRange(limits[0], limits[1]); + else + throwError("Invalid point range in 'point' option: " + s); + } +} + + +void InfoFilter::parseQuerySpec() +{ + // See if there's a provided point count. + StringList parts = Utils::split2(m_querySpec, '/'); + if (parts.size() == 2) + { + if (!Utils::fromString(parts[1], m_queryCount)) + throwError("Invalid query count in 'query' option: " + parts[1]); + } + else if (parts.size() != 1) + throwError("Invalid point location specification. Sytax: " + "--query=\"X,Y[/count]\""); + + //ABELL - This doesn't match syntax below, but keeping because + // history. + auto seps = [](char c) { return (c == ',' || c == '|' || c == ' '); }; + + StringList tokens = Utils::split2(parts[0], seps); + if (tokens.size() != 2 && tokens.size() != 3) + throwError("Invalid point location specification. Sytax: " + "--query=\"X,Y[/count]\""); + + bool ok = true; + ok &= Utils::fromString(tokens[0], m_queryX); + ok &= Utils::fromString(tokens[1], m_queryY); + if (tokens.size() == 3) + ok &= Utils::fromString(tokens[1], m_queryZ); + if (!ok) + throwError("Invalid point location specification. Sytax: " + "--query=\"X,Y[/count]\""); +} + + +void InfoFilter::prepared(PointTableRef table) +{ + m_dims = table.layout()->dimTypes(); + m_pointSize = table.layout()->pointSize(); + if (m_pointSpec.size()) + parsePointSpec(); + if (m_querySpec.size()) + parseQuerySpec(); +} + + +void InfoFilter::initialize(PointTableRef table) +{ + getMetadata().add(table.layout()->toMetadata()); +} + + +void InfoFilter::ready(PointTableRef) +{ + m_count = 0; + m_idCur = m_idList.begin(); +} + + +void InfoFilter::filter(PointView& view) +{ + PointRef point(view, 0); + for (PointId idx = 0; idx < view.size(); ++idx) + { + point.setPointId(idx); + processOne(point); + } +} + + +bool InfoFilter::processOne(PointRef& point) +{ + double x = point.getFieldAs(Dimension::Id::X); + double y = point.getFieldAs(Dimension::Id::Y); + double z = point.getFieldAs(Dimension::Id::Z); + + // Accumulate min/max. + m_bounds.grow(x, y, z); + + // Create metadata for requsted points. + // We may get a point list or a query list, but not both. + if (m_idCur != m_idList.end() && *m_idCur == m_count) + { + // This is kind of a fake, in that the points in the list aren't + // near anything, but it makes for a single set of code to + // extract the metadata of reported points. + std::vector buf(m_pointSize); + point.getPackedData(m_dims, buf.data()); + m_results.emplace_back(m_count, 0, std::move(buf)); + m_idCur++; + } + else if (m_querySpec.size() && m_queryCount) + { + double dist = std::pow(x - m_queryX, 2) + std::pow(y - m_queryY, 2); + if (!isnan(m_queryZ)) + dist += std::pow(z - m_queryZ, 2); + if (m_results.size() < m_queryCount || dist < m_results.back().m_dist) + { + std::vector buf(m_pointSize); + point.getPackedData(m_dims, buf.data()); + NearPoint np(m_count, dist, std::move(buf)); + m_results.insert( + std::upper_bound(m_results.begin(), m_results.end(), np), + std::move(np)); + if (m_results.size() > m_queryCount) + m_results.pop_back(); + } + } + m_count++; + return true; +} + + +void InfoFilter::done(PointTableRef table) +{ +std::cerr << "InfoFilter::done!\n"; + // Point list + MetadataNode points("points"); + for (NearPoint& np: m_results) + { + MetadataNode node("point"); + const char *buf = np.m_data.data(); + Everything e; + for (DimType& dt : m_dims) + { + size_t dimSize = Dimension::size(dt.m_type); + std::copy(buf, buf + dimSize, reinterpret_cast(&e)); + double d = Utils::toDouble(e, dt.m_type); + node.add(table.layout()->dimName(dt.m_id), d); + buf += dimSize; + } + node.add("PointId", np.m_id); + points.add(node); + } + if (points.hasChildren()) + getMetadata().add(points); + + // Bounds + getMetadata().add(Utils::toMetadata(m_bounds)); + + // Point count + getMetadata().add("num_points", m_count); + + // Dimension names + std::string dims; + for (auto di = m_dims.begin(); di != m_dims.end();) + { + dims += table.layout()->dimName(di->m_id); + if (++di != m_dims.end()) + dims += ", "; + } + getMetadata().add("dimensions", dims); + + // Spatial reference + getMetadata().add(table.anySpatialReference().toMetadata()); +} + +} // namespace pdal diff --git a/filters/InfoFilter.hpp b/filters/InfoFilter.hpp new file mode 100644 index 0000000000..deef910814 --- /dev/null +++ b/filters/InfoFilter.hpp @@ -0,0 +1,103 @@ +/****************************************************************************** +* Copyright (c) 2018, Hobu Inc. +* +* 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. 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. +****************************************************************************/ + +#pragma once + +#include +#include +#include + +namespace pdal +{ + +// This is just a pass-through filter, which collects some data about +// the points that are fed through it +class PDAL_DLL InfoFilter : public Filter, public Streamable +{ + struct NearPoint + { + NearPoint(PointId id, double dist, std::vector&& data) : + m_id(id), m_dist(dist), m_data(data) + {} + + PointId m_id; + double m_dist; + std::vector m_data; + + bool operator < (const NearPoint& other) const + { return m_dist < other.m_dist; } + }; + +public: + InfoFilter() : + m_pointRoot("point"), m_queryCount(10), + m_queryZ(std::numeric_limits::quiet_NaN()) + {} + + std::string getName() const; + BOX3D bounds() const + { return m_bounds; } + +private: + virtual void addArgs(ProgramArgs& args); + virtual void initialize(PointTableRef table); + virtual void ready(PointTableRef table); + virtual bool processOne(PointRef& point); + virtual void prepared(PointTableRef table); + virtual void done(PointTableRef table); + virtual void filter(PointView& view); + + void parsePointSpec(); + void parseQuerySpec(); + + MetadataNode m_pointRoot; + + std::string m_querySpec; + point_count_t m_queryCount; + double m_queryX; + double m_queryY; + double m_queryZ; + std::list m_results; + + std::string m_pointSpec; + std::vector m_idList; + std::vector::const_iterator m_idCur; + DimTypeList m_dims; + size_t m_pointSize; + PointId m_count; + + BOX3D m_bounds; +}; + +} // namespace pdal diff --git a/kernels/InfoKernel.cpp b/kernels/InfoKernel.cpp index 51882993a7..540b5fa7c2 100644 --- a/kernels/InfoKernel.cpp +++ b/kernels/InfoKernel.cpp @@ -39,13 +39,11 @@ #include #include +#include #include #include #include #include -#ifdef PDAL_HAVE_LIBXML2 -#include -#endif namespace pdal { @@ -109,6 +107,8 @@ void InfoKernel::validateSwitches(ProgramArgs& args) functions++; if (m_showSummary) functions++; + if (m_pipelineFile.size()) + functions++; if (m_showStats || functions == 0 ) { functions++; @@ -117,11 +117,16 @@ void InfoKernel::validateSwitches(ProgramArgs& args) } if (m_pointIndexes.size() && m_queryPoint.size()) - throw pdal_error("--point option incompatible with --query option."); + throw pdal_error("'point' option incompatible with 'query' option."); if (m_showSummary && functions > 1) - throw pdal_error("--summary option incompatible with other " + throw pdal_error("'summary' option incompatible with other " "specified options."); + + if (!m_showStats && m_enumerate.size()) + throw pdal_error("'enumerate' option requires 'stats' option."); + if (!m_showStats && m_dimensions.size()) + throw pdal_error("'dimensions' option requires 'stats' option."); } @@ -149,94 +154,12 @@ void InfoKernel::addSwitches(ProgramArgs& args) "serialization", m_pipelineFile); args.add("summary", "Dump summary of the info", m_showSummary); args.add("metadata", "Dump file metadata info", m_showMetadata); - args.add("pointcloudschema", "Dump PointCloudSchema XML output", - m_PointCloudSchemaOutput).setHidden(); args.add("stdin,s", "Read a pipeline file from standard input", m_usestdin); } -// Support for parsing point numbers. Points can be specified singly or as -// dash-separated ranges. i.e. 6-7,8,19-20 -namespace { - -using namespace std; - -uint32_t parseInt(const string& s) -{ - uint32_t i; - - if (!Utils::fromString(s, i)) - throw pdal_error(string("Invalid integer: ") + s); - return i; -} - - -void addRange(const string& begin, const string& end, vector& points) -{ - PointId low = parseInt(begin); - PointId high = parseInt(end); - if (low > high) - throw pdal_error(string("Range invalid: ") + begin + "-" + end); - while (low <= high) - points.push_back(low++); -} - - -vector getListOfPoints(std::string p) -{ - vector output; - - //Remove whitespace from string with awful remove/erase idiom. - p.erase(remove_if(p.begin(), p.end(), ::isspace), p.end()); - - vector ranges = Utils::split2(p, ','); - for (string s : ranges) - { - vector limits = Utils::split(s, '-'); - if (limits.size() == 1) - output.push_back(parseInt(limits[0])); - else if (limits.size() == 2) - addRange(limits[0], limits[1], output); - else - throw pdal_error(string("Invalid point range: ") + s); - } - return output; -} - -} //namespace - -MetadataNode InfoKernel::dumpPoints(PointViewPtr inView) const -{ - MetadataNode root; - PointViewPtr outView = inView->makeNew(); - - // Stick points in a inViewfer. - std::vector points = getListOfPoints(m_pointIndexes); - bool oorMsg = false; - for (size_t i = 0; i < points.size(); ++i) - { - PointId id = (PointId)points[i]; - if (id < inView->size()) - outView->appendPoint(*inView.get(), id); - else if (!oorMsg) - { - m_log->get(LogLevel::Warning) << "Attempt to display points with " - "IDs not available in input dataset." << std::endl; - oorMsg = true; - } - } - - MetadataNode tree = outView->toMetadata(); - std::string prefix("point "); - for (size_t i = 0; i < outView->size(); ++i) - { - MetadataNode n = tree.findChild(std::to_string(i)); - n.add("PointId", points[i]); - root.add(n.clone("point")); - } - return root; -} - +// Note that the same information can come from the info filter, but +// this avoids point reads. MetadataNode InfoKernel::dumpSummary(const QuickInfo& qi) { MetadataNode summary; @@ -248,16 +171,8 @@ MetadataNode InfoKernel::dumpSummary(const QuickInfo& qi) } if (qi.m_bounds.valid()) { - MetadataNode bounds = summary.add("bounds"); - MetadataNode x = bounds.add("X"); - x.add("min", qi.m_bounds.minx); - x.add("max", qi.m_bounds.maxx); - MetadataNode y = bounds.add("Y"); - y.add("min", qi.m_bounds.miny); - y.add("max", qi.m_bounds.maxy); - MetadataNode z = bounds.add("Z"); - z.add("min", qi.m_bounds.minz); - z.add("max", qi.m_bounds.maxz); + MetadataNode bounds = Utils::toMetadata(qi.m_bounds); + summary.add(bounds.clone("bounds")); } std::string dims; @@ -274,41 +189,27 @@ MetadataNode InfoKernel::dumpSummary(const QuickInfo& qi) return summary; } - -void InfoKernel::makePipeline(const std::string& filename, bool noPoints) +void InfoKernel::makeReader(const std::string& filename) { - if (!pdal::Utils::fileExists(filename)) - throw pdal_error("File not found: " + filename); - - if (filename == "STDIN") - { - m_manager.readPipeline(std::cin); - m_reader = m_manager.getStage(); - } - else if (FileUtils::extension(filename) == ".xml" || - FileUtils::extension(filename) == ".json") - { - m_manager.readPipeline(filename); - m_reader = m_manager.getStage(); - } - else - { - Options ops; - if (noPoints) - ops.add("count", 0); - Stage& reader = m_manager.makeReader(filename, m_driverOverride, ops); - m_reader = &reader; - } - if (!m_reader) - throw pdal_error("Pipeline contains no valid stages."); + Options rOps; + if (!m_needPoints) + rOps.add("count", 0); + m_reader = &(m_manager.makeReader(filename, m_driverOverride, rOps)); } -void InfoKernel::setup(const std::string& filename) +void InfoKernel::makePipeline() { - makePipeline(filename, !m_needPoints); - Stage *stage = m_reader; + + Options iOps; + if (m_queryPoint.size()) + iOps.add("query", m_queryPoint); + if (m_pointIndexes.size()) + iOps.add("point", m_pointIndexes); + stage = m_infoStage = + &(m_manager.makeFilter("filters.info", *stage, iOps)); + if (m_showStats) { Options filterOptions; @@ -316,21 +217,11 @@ void InfoKernel::setup(const std::string& filename) filterOptions.add({"dimensions", m_dimensions}); if (m_enumerate.size()) filterOptions.add({"enumerate", m_enumerate}); - m_statsStage = &m_manager.makeFilter("filters.stats", *stage, - filterOptions); - stage = m_statsStage; + stage = m_statsStage = + &m_manager.makeFilter("filters.stats", *stage, filterOptions); } if (m_boundary) - { - try - { - m_hexbinStage = &m_manager.makeFilter("filters.hexbin", *stage); - } catch (pdal::pdal_error&) - { - m_hexbinStage = nullptr; - - } - } + m_hexbinStage = &m_manager.makeFilter("filters.hexbin", *stage); } @@ -338,181 +229,82 @@ MetadataNode InfoKernel::run(const std::string& filename) { MetadataNode root; - root.add("filename", filename); + makeReader(filename); if (m_showSummary) { - QuickInfo qi = m_reader->preview(); + QuickInfo qi = m_manager.getStage()->preview(); if (!qi.valid()) throw pdal_error("No summary data available for '" + filename + "'."); - MetadataNode summary = dumpSummary(qi).clone("summary"); - root.add(summary); + root.add(dumpSummary(qi).clone("summary")); } else { + makePipeline(); + BasePointTable *table; if (m_needPoints || m_showMetadata) - m_manager.execute(); + { + FixedPointTable fixedTable(10000); + m_manager.executeStream(fixedTable); + table = &fixedTable; + } else + { m_manager.prepare(); - dump(root); + table = &m_manager.pointTable(); + } + dump(*table, root); } + root.add("filename", filename); root.add("pdal_version", Config::fullVersionString()); return root; } -void InfoKernel::dump(MetadataNode& root) +void InfoKernel::dump(PointTableRef table, MetadataNode& root) { - if (m_showSchema) - root.add(m_manager.pointTable().layout()->toMetadata().clone("schema")); - - if (m_PointCloudSchemaOutput.size() > 0) - { -#ifdef PDAL_HAVE_LIBXML2 - XMLSchema schema(m_manager.pointTable().layout()); - - std::ostream *out = Utils::createFile(m_PointCloudSchemaOutput); - std::string xml(schema.xml()); - out->write(xml.c_str(), xml.size()); - Utils::closeFile(out); -#else - std::cerr << "libxml2 support not enabled, no schema is produced" << - std::endl; -#endif - - } - if (m_showStats) - root.add(m_statsStage->getMetadata().clone("stats")); - if (m_pipelineFile.size() > 0) PipelineWriter::writePipeline(m_manager.getStage(), m_pipelineFile); - if (m_pointIndexes.size()) - { - PointViewSet viewSet = m_manager.views(); - assert(viewSet.size() == 1); - MetadataNode points = dumpPoints(*viewSet.begin()); - if (points.valid()) - root.add(points.clone("points")); - } + // Reader stage. + if (m_showMetadata) + root.add(m_reader->getMetadata().clone("metadata")); - if (m_queryPoint.size()) - { - PointViewSet viewSet = m_manager.views(); - assert(viewSet.size() == 1); - root.add(dumpQuery(*viewSet.begin())); - } + // Info stage. + auto info = dynamic_cast(m_infoStage); + MetadataNode node = info->getMetadata(); + MetadataNode points = node.findChild("points"); + if (points) + root.add(points); - if (m_showMetadata) - { - // If we have a reader cached, this means we - // weren't reading a pipeline file directly. In that - // case, use the metadata from the reader (old behavior). - // Otherwise, return the full metadata of the entire pipeline - if (m_reader) - root.add(m_reader->getMetadata().clone("metadata")); - else - root.add(m_manager.getMetadata().clone("metadata")); - } + if (m_showSchema) + root.add(table.layout()->toMetadata()); - if (m_boundary) + // Stats stage. + if (m_showStats) + root.add(m_statsStage->getMetadata().clone("stats")); + + // Hexbin stage. + if (m_hexbinStage) { - PointViewSet viewSet = m_manager.views(); - assert(viewSet.size() == 1); - if (m_hexbinStage) - root.add(m_hexbinStage->getMetadata().clone("boundary")); - else + MetadataNode node = m_hexbinStage->getMetadata(); + if (node.findChild("error")) { - pdal::BOX2D bounds; - for (auto const &v: viewSet) - { - pdal::BOX2D b; - v->calculateBounds(b); - bounds.grow(b); - } - std::stringstream polygon; - polygon << "POLYGON (("; - - polygon << bounds.minx << " " << bounds.miny; - polygon << ", " << bounds.maxx << " " << bounds.miny; - polygon << ", " << bounds.maxx << " " << bounds.maxy; - polygon << ", " << bounds.minx << " " << bounds.maxy; - polygon << ", " << bounds.minx << " " << bounds.miny; - polygon << "))"; + std::string poly = info->bounds().to2d().toWKT(); MetadataNode m("boundary"); - m.add("boundary",polygon.str(), "Simple boundary of polygon"); + m.add("boundary", poly, "Simple boundary of polygon"); root.add(m); - } + else + root.add(m_hexbinStage->getMetadata().clone("boundary")); } } -MetadataNode InfoKernel::dumpQuery(PointViewPtr inView) const -{ - int count; - std::string location; - - // See if there's a provided point count. - StringList parts = Utils::split2(m_queryPoint, '/'); - if (parts.size() == 2) - { - location = parts[0]; - count = atoi(parts[1].c_str()); - } - else if (parts.size() == 1) - { - location = parts[0]; - count = inView->size(); - } - else - count = 0; - if (count == 0) - throw pdal_error("Invalid location specification. " - "--query=\"X,Y[/count]\""); - - auto seps = [](char c){ return (c == ',' || c == '|' || c == ' '); }; - - std::vector tokens = Utils::split2(location, seps); - std::vector values; - for (auto ti = tokens.begin(); ti != tokens.end(); ++ti) - { - double d; - if (Utils::fromString(*ti, d)) - values.push_back(d); - } - - if (values.size() != 2 && values.size() != 3) - throw pdal_error("--points must be two or three values"); - - PointViewPtr outView = inView->makeNew(); - - std::vector ids; - if (values.size() >= 3) - { - KD3Index kdi(*inView); - kdi.build(); - ids = kdi.neighbors(values[0], values[1], values[2], count); - } - else - { - KD2Index kdi(*inView); - kdi.build(); - ids = kdi.neighbors(values[0], values[1], count); - } - - for (auto i = ids.begin(); i != ids.end(); ++i) - outView->appendPoint(*inView.get(), *i); - - return outView->toMetadata(); -} - - int InfoKernel::execute() { - std::string filename = m_usestdin ? std::string("STDIN") : m_inputFile; - setup(filename); + std::string filename = (m_usestdin ? std::string("STDIN") : m_inputFile); MetadataNode root = run(filename); Utils::toJSON(root, std::cout); diff --git a/kernels/InfoKernel.hpp b/kernels/InfoKernel.hpp index cc90c8b197..10360700ab 100644 --- a/kernels/InfoKernel.hpp +++ b/kernels/InfoKernel.hpp @@ -65,14 +65,10 @@ class PDAL_DLL InfoKernel : public Kernel private: void addSwitches(ProgramArgs& args); void validateSwitches(ProgramArgs& args); - - void dump(MetadataNode& root); - MetadataNode dumpPoints(PointViewPtr inView) const; - MetadataNode dumpStats() const; - void dumpPipeline() const; + void makeReader(const std::string& filename); + void makePipeline(); + void dump(PointTableRef table, MetadataNode& root); MetadataNode dumpSummary(const QuickInfo& qi); - MetadataNode dumpQuery(PointViewPtr inView) const; - void makePipeline(const std::string& filename, bool noPoints); std::string m_inputFile; bool m_showStats; @@ -87,11 +83,11 @@ class PDAL_DLL InfoKernel : public Kernel std::string m_pipelineFile; bool m_showSummary; bool m_needPoints; - std::string m_PointCloudSchemaOutput; bool m_usestdin; Stage *m_statsStage; Stage *m_hexbinStage; + Stage *m_infoStage; Stage *m_reader; MetadataNode m_tree; diff --git a/pdal/Metadata.hpp b/pdal/Metadata.hpp index 9c261f4a15..aa40e6d8e7 100644 --- a/pdal/Metadata.hpp +++ b/pdal/Metadata.hpp @@ -595,6 +595,8 @@ class PDAL_DLL MetadataNode return names; } + operator bool () const + { return !empty(); } bool operator ! () { return empty(); } bool valid() const diff --git a/pdal/PointLayout.cpp b/pdal/PointLayout.cpp index f09bdfdebc..481a37717c 100644 --- a/pdal/PointLayout.cpp +++ b/pdal/PointLayout.cpp @@ -317,7 +317,7 @@ Dimension::Type PointLayout::resolveType(Dimension::Type t1, MetadataNode PointLayout::toMetadata() const { - MetadataNode root; + MetadataNode root("schema"); for (const auto& id : dims()) { diff --git a/pdal/PointLayout.hpp b/pdal/PointLayout.hpp index ef51b9ecb7..bc552decb2 100644 --- a/pdal/PointLayout.hpp +++ b/pdal/PointLayout.hpp @@ -229,9 +229,11 @@ class PointLayout return &(m_detail[Utils::toNative(id)]); } + /** + Convert the point layout to a metadata format. - - + \return A metadata node that contains the layout information. + */ PDAL_DLL MetadataNode toMetadata() const; private: diff --git a/pdal/PointRef.hpp b/pdal/PointRef.hpp index e688c15b55..89500c82b4 100644 --- a/pdal/PointRef.hpp +++ b/pdal/PointRef.hpp @@ -164,6 +164,8 @@ class PDAL_DLL PointRef Dimension::Type type) const; inline void setField(Dimension::Id dim, Dimension::Type type, const void *val); + inline void toMetadata(MetadataNode node) const; + inline MetadataNode toMetadata() const; /// Fill a buffer with point data specified by the dimension list. /// \param[in] dims List of dimensions/types to retrieve. @@ -285,4 +287,21 @@ inline void PointRef::setField(Dimension::Id dim, } } +inline MetadataNode PointRef::toMetadata() const +{ + MetadataNode node; + + toMetadata(node); + return node; +} + +inline void PointRef::toMetadata(MetadataNode node) const +{ + for (Dimension::Id id : m_layout.dims()) + { + double v = getFieldAs(id); + node.add(m_layout.dimName(id), v); + } +} + } // namespace pdal diff --git a/test/unit/CMakeLists.txt b/test/unit/CMakeLists.txt index cac63da361..c729edc0bf 100644 --- a/test/unit/CMakeLists.txt +++ b/test/unit/CMakeLists.txt @@ -162,6 +162,7 @@ PDAL_ADD_TEST(pdal_filters_decimation_test FILES PDAL_ADD_TEST(pdal_filters_divider_test FILES filters/DividerFilterTest.cpp) PDAL_ADD_TEST(pdal_filters_ferry_test FILES filters/FerryFilterTest.cpp) PDAL_ADD_TEST(pdal_filters_groupby_test FILES filters/GroupByFilterTest.cpp) +PDAL_ADD_TEST(pdal_filters_info_test FILES filters/InfoFilterTest.cpp) PDAL_ADD_TEST(pdal_filters_neighborclassifier_test FILES filters/NeighborClassifierFilterTest.cpp) PDAL_ADD_TEST(pdal_filters_locate_test FILES filters/LocateFilterTest.cpp) PDAL_ADD_TEST(pdal_filters_merge_test FILES filters/MergeTest.cpp) diff --git a/test/unit/filters/InfoFilterTest.cpp b/test/unit/filters/InfoFilterTest.cpp new file mode 100644 index 0000000000..1c86eb5eb8 --- /dev/null +++ b/test/unit/filters/InfoFilterTest.cpp @@ -0,0 +1,70 @@ +/****************************************************************************** +* Copyright (c) 2018, Hobu Inc. (hobu@hobu.co) +* +* 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. 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 "Support.hpp" + +namespace pdal +{ + +TEST(InfoFilterTest, one) +{ + StageFactory factory; + + Stage *r = factory.createStage("readers.las"); + Options rOpts; + rOpts.add("filename", Support::datapath("las/autzen_trim.las")); + r->setOptions(rOpts); + + Stage *f = factory.createStage("filters.info"); + Options fOpts; +// fOpts.add("point", "0-9"); + fOpts.add("query", "636133,849000"); + f->setOptions(fOpts); + f->setInput(*r); + + FixedPointTable t(1000); + + f->prepare(t); + f->execute(t); + + MetadataNode m = f->getMetadata(); + + Utils::toJSON(m, std::cout); +} + +} // namespace pdal