1 #include <iomanip>
2 #include <regex>
3 #include <set>
4 #include <string>
5 #include <vector>
6 
7 #include <pdal/util/FileUtils.hpp>
8 #include <pdal/util/ProgramArgs.hpp>
9 
10 #include "BuPyramid.hpp"
11 #include "FileInfo.hpp"
12 #include "OctantInfo.hpp"
13 #include "../untwine/Common.hpp"
14 #include "../untwine/ProgressWriter.hpp"
15 
16 namespace untwine
17 {
18 namespace bu
19 {
20 
21 /// BuPyramid
22 
BuPyramid(BaseInfo & common)23 BuPyramid::BuPyramid(BaseInfo& common) : m_b(common), m_manager(m_b)
24 {}
25 
26 
run(const Options & options,ProgressWriter & progress)27 void BuPyramid::run(const Options& options, ProgressWriter& progress)
28 {
29     m_b.inputDir = options.tempDir;
30     m_b.outputDir = options.outputDir;
31     m_b.stats = options.stats;
32 
33     getInputFiles();
34     size_t count = queueWork();
35 
36     progress.setPercent(.6);
37     progress.setIncrement(.4 / count);
38     m_manager.setProgress(&progress);
39     std::thread runner(&PyramidManager::run, &m_manager);
40     runner.join();
41     writeInfo();
42 }
43 
44 
writeInfo()45 void BuPyramid::writeInfo()
46 {
47     auto typeString = [](pdal::Dimension::BaseType b)
48     {
49         using namespace pdal::Dimension;
50 
51         switch (b)
52         {
53         case BaseType::Signed:
54             return "signed";
55         case BaseType::Unsigned:
56             return "unsigned";
57         case BaseType::Floating:
58             return "float";
59         default:
60             return "";
61         }
62     };
63 
64     std::ofstream out(m_b.outputDir + "/ept.json");
65 
66     out << "{\n";
67 
68     pdal::BOX3D& b = m_b.bounds;
69 
70     // Set fixed output for bounds output to get sufficient precision.
71     out << std::fixed;
72     out << "\"bounds\": [" <<
73         b.minx << ", " << b.miny << ", " << b.minz << ", " <<
74         b.maxx << ", " << b.maxy << ", " << b.maxz << "],\n";
75 
76     pdal::BOX3D& tb = m_b.trueBounds;
77     out << "\"boundsConforming\": [" <<
78         tb.minx << ", " << tb.miny << ", " << tb.minz << ", " <<
79         tb.maxx << ", " << tb.maxy << ", " << tb.maxz << "],\n";
80     // Reset to default float output to match PDAL option handling for now.
81     out << std::defaultfloat;
82 
83     out << "\"dataType\": \"laszip\",\n";
84     out << "\"hierarchyType\": \"json\",\n";
85     out << "\"points\": " << m_manager.totalPoints() << ",\n";
86     out << "\"span\": 128,\n";
87     out << "\"version\": \"1.0.0\",\n";
88     out << "\"schema\": [\n";
89     for (auto di = m_b.dimInfo.begin(); di != m_b.dimInfo.end(); ++di)
90     {
91         const FileDimInfo& fdi = *di;
92 
93         out << "\t{";
94             out << "\"name\": \"" << fdi.name << "\", ";
95             out << "\"type\": \"" << typeString(pdal::Dimension::base(fdi.type)) << "\", ";
96             if (fdi.name == "X")
97                 out << "\"scale\": " << m_b.scale[0] << ", \"offset\": " << m_b.offset[0] << ", ";
98             if (fdi.name == "Y")
99                 out << "\"scale\": " << m_b.scale[1] << ", \"offset\": " << m_b.offset[1] << ", ";
100             if (fdi.name == "Z")
101                 out << "\"scale\": " << m_b.scale[2] << ", \"offset\": " << m_b.offset[2] << ", ";
102             out << "\"size\": " << pdal::Dimension::size(fdi.type);
103             const Stats *stats = m_manager.stats(fdi.name);
104             if (stats)
105             {
106                 const Stats::EnumMap& v = stats->values();
107                 out << ", ";
108                 if (v.size())
109                 {
110                     out << "\"counts\": [ ";
111                     for (auto ci = v.begin(); ci != v.end(); ++ci)
112                     {
113                         auto c = *ci;
114                         if (ci != v.begin())
115                             out << ", ";
116                         out << "{\"value\": " << c.first << ", \"count\": " << c.second << "}";
117                     }
118                     out << "], ";
119                 }
120                 out << "\"count\": " << m_manager.totalPoints() << ", ";
121                 out << "\"maximum\": " << stats->maximum() << ", ";
122                 out << "\"minimum\": " << stats->minimum() << ", ";
123                 out << "\"mean\": " << stats->average() << ", ";
124                 out << "\"stddev\": " << stats->stddev() << ", ";
125                 out << "\"variance\": " << stats->variance();
126             }
127         out << "}";
128         if (di + 1 != m_b.dimInfo.end())
129             out << ",";
130         out << "\n";
131     }
132     out << "],\n";
133 
134     out << "\"srs\": {\n";
135     if (m_b.srs.valid())
136     {
137         out << "\"wkt\": " <<  "\"" << pdal::Utils::escapeJSON(m_b.srs.getWKT()) << "\"\n";
138     }
139     out << "}\n";
140 
141     out << "}\n";
142 }
143 
144 
getInputFiles()145 void BuPyramid::getInputFiles()
146 {
147     auto matches = [](const std::string& f)
148     {
149         std::regex check("([0-9]+)-([0-9]+)-([0-9]+)-([0-9]+)\\.bin");
150         std::smatch m;
151         if (!std::regex_match(f, m, check))
152             return std::make_pair(false, VoxelKey(0, 0, 0, 0));
153         int level = std::stoi(m[1].str());
154         int x = std::stoi(m[2].str());
155         int y = std::stoi(m[3].str());
156         int z = std::stoi(m[4].str());
157         return std::make_pair(true, VoxelKey(x, y, z, level));
158     };
159 
160     std::vector<std::string> files = pdal::FileUtils::directoryList(m_b.inputDir);
161 
162     VoxelKey root;
163     for (std::string file : files)
164     {
165         uintmax_t size = pdal::FileUtils::fileSize(file);
166         file = pdal::FileUtils::getFilename(file);
167         auto ret = matches(file);
168         bool success = ret.first;
169         VoxelKey& key = ret.second;
170         if (success)
171             m_allFiles.emplace(key, FileInfo(file, size / m_b.pointSize));
172     }
173 
174     // Remove any files that are hangers-on from a previous run - those that are parents
175     // of other input.
176     for (auto it = m_allFiles.begin(); it != m_allFiles.end(); ++it)
177     {
178         VoxelKey k = it->first;
179         while (k != root)
180         {
181             k = k.parent();
182             m_allFiles.erase(k);
183         };
184     }
185 }
186 
187 
queueWork()188 size_t BuPyramid::queueWork()
189 {
190     std::set<VoxelKey> needed;
191     std::set<VoxelKey> parentsToProcess;
192     std::vector<OctantInfo> have;
193     const VoxelKey root;
194 
195     for (auto& afi : m_allFiles)
196     {
197         VoxelKey k = afi.first;
198         FileInfo& f = afi.second;
199 
200         // Stick an OctantInfo for this file in the 'have' list.
201         OctantInfo o(k);
202         o.appendFileInfo(f);
203         have.push_back(o);
204 
205         // Walk up the tree and make sure that we're populated for all children necessary
206         // to process to the top level.
207         while (k != root)
208         {
209             k = k.parent();
210             parentsToProcess.insert(k);
211             for (int i = 0; i < 8; ++i)
212                 needed.insert(k.child(i));
213         }
214     }
215 
216     // Now remove entries for the files we have and their parents.
217     for (const OctantInfo& o : have)
218     {
219         VoxelKey k = o.key();
220         while (k != root)
221         {
222             needed.erase(k);
223             k = k.parent();
224         }
225     }
226 
227     // Queue what we have and what's left that's needed.
228     for (const OctantInfo& o : have)
229         m_manager.queue(o);
230     for (const VoxelKey& k : needed)
231         m_manager.queue(OctantInfo(k));
232     return parentsToProcess.size();
233 }
234 
235 } // namespace bu
236 } // namespace untwine
237