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