1 /******************************************************************************
2 * Copyright (c) 2018, Hobu Inc. (info@hobu.co)
3 *
4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following
8 * conditions are met:
9 *
10 * * Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * * Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in
14 * the documentation and/or other materials provided
15 * with the distribution.
16 * * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
17 * names of its contributors may be used to endorse or promote
18 * products derived from this software without specific prior
19 * written permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
24 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
25 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
26 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
27 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
28 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
29 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
30 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
31 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
32 * OF SUCH DAMAGE.
33 ****************************************************************************/
34
35 #include "TileKernel.hpp"
36
37 #include <pdal/StageFactory.hpp>
38 #include <pdal/StageWrapper.hpp>
39 #include <pdal/Writer.hpp>
40 #include <pdal/util/FileUtils.hpp>
41
42 namespace pdal
43 {
44
45 static StaticPluginInfo const s_info
46 {
47 "kernels.tile",
48 "Tile Kernel",
49 "http://pdal.io/apps/tile.html"
50 };
51
CREATE_STATIC_KERNEL(TileKernel,s_info)52 CREATE_STATIC_KERNEL(TileKernel, s_info)
53
54 TileKernel::TileKernel() : m_table(10000), m_repro(nullptr)
55 {}
56
57
getName() const58 std::string TileKernel::getName() const
59 {
60 return s_info.name;
61 }
62
63
addSwitches(ProgramArgs & args)64 void TileKernel::addSwitches(ProgramArgs& args)
65 {
66 args.add("input,i", "Input file/path name", m_inputFile).setPositional();
67 args.add("output,o", "Output filename template",
68 m_outputFile).setPositional();
69 args.add("length", "Edge length for cells", m_length, 1000.0);
70 args.add("origin_x", "Origin in X axis for cells", m_xOrigin,
71 std::numeric_limits<double>::quiet_NaN());
72 args.add("origin_y", "Origin in Y axis for cells", m_yOrigin,
73 std::numeric_limits<double>::quiet_NaN());
74 args.add("buffer", "Size of buffer (overlap) to include around each tile",
75 m_buffer);
76 args.add("out_srs", "Output SRS to which points will be reprojected",
77 m_outSrs);
78 }
79
80
validateSwitches(ProgramArgs & args)81 void TileKernel::validateSwitches(ProgramArgs& args)
82 {
83 m_hashPos = Writer::handleFilenameTemplate(m_outputFile);
84 if (m_hashPos == std::string::npos)
85 throw pdal_error("Output filename must contain a single '#' "
86 "template placeholder.");
87 }
88
89
execute()90 int TileKernel::execute()
91 {
92 const StringList& files = FileUtils::glob(m_inputFile);
93 if (files.empty())
94 throw pdal_error("No input files found for path '" +
95 m_inputFile + "'.");
96
97 Readers readers;
98 for (auto&& file : files)
99 readers[file] = prepareReader(file);
100 checkReaders(readers);
101 if (m_repro)
102 m_repro->prepare(m_table);
103 Options opts;
104 opts.add("length", m_length);
105 opts.add("buffer", m_buffer);
106 m_splitter.setOptions(opts);
107 m_splitter.prepare(m_table);
108
109 m_table.finalize();
110 process(readers);
111 StageWrapper::done(m_splitter, m_table);
112 for (auto&& wp : m_writers)
113 StageWrapper::done(*wp.second, m_table);
114 return 0;
115 }
116
117
checkReaders(const Readers & readers)118 void TileKernel::checkReaders(const Readers& readers)
119 {
120 SpatialReference tempSrs;
121 SpatialReference srs;
122 bool needRepro(false);
123
124 for (auto& rp : readers)
125 {
126 const std::string& filename = rp.first;
127 Streamable *r = rp.second;
128
129 tempSrs = r->getSpatialReference();
130
131 // No SRS
132 if (tempSrs.empty())
133 {
134 if (!m_outSrs.empty())
135 throw pdal_error("Can't reproject file '" + filename +
136 "' with no SRS.");
137 continue;
138 }
139
140 if (srs.empty())
141 srs = tempSrs;
142
143 // Two SRSes
144 if (tempSrs != srs)
145 {
146 if (m_outSrs.empty())
147 {
148 static bool warned(false);
149 if (!warned)
150 {
151 m_log->get(LogLevel::Warning) << "No 'out_srs' specified "
152 "and input files have multiple SRSs. Using SRS of "
153 "first input file as output SRS." << std::endl;
154 warned = true;
155 m_outSrs = srs;
156 }
157 }
158 needRepro = true;
159 }
160 }
161
162 // Non-matching SRS and we requested reprojection
163 if (!m_outSrs.empty() && srs != m_outSrs)
164 needRepro = true;
165
166 if (needRepro)
167 {
168 Options opts;
169 opts.add("out_srs", m_outSrs);
170
171 m_repro = dynamic_cast<Streamable *>(
172 &m_manager.makeFilter("filters.reprojection", opts));
173 }
174 }
175
176
prepareReader(const std::string & filename)177 Streamable *TileKernel::prepareReader(const std::string& filename)
178 {
179 Stage* r = &(m_manager.makeReader(filename, ""));
180
181 if (!r)
182 throw pdal_error("Couldn't create reader for input file '" +
183 filename + "'.");
184
185 Streamable *sr = dynamic_cast<Streamable *>(r);
186 if (!sr)
187 throw pdal_error("Driver '" + r->getName() + "' for input file '" +
188 filename + "' is not streamable.");
189
190 sr->prepare(m_table);
191 return sr;
192 }
193
194
195 // We calculate the origin specially in order to avoid a "first point"
196 // check for every point iteration, seeing as we might have BILLIONS
197 // of points to process.
process(const Readers & readers)198 void TileKernel::process(const Readers& readers)
199 {
200 using std::placeholders::_1;
201 using std::placeholders::_2;
202 using std::placeholders::_3;
203 SplitterFilter::PointAdder adder =
204 std::bind(&TileKernel::adder, this, _1, _2, _3);
205
206 bool haveOrigin(false);
207 StageWrapper::ready(m_splitter, m_table);
208 for (auto&& rp : readers)
209 {
210 Streamable& r = *(rp.second);
211 std::vector<bool> skips(m_table.capacity());
212 PointId idx(0);
213 PointRef point(m_table, idx);
214
215 StreamableWrapper::ready(r, m_table);
216 if (m_repro)
217 StreamableWrapper::spatialReferenceChanged(*m_repro,
218 r.getSpatialReference());
219
220 // Read first point.
221 bool finished(false);
222 finished = !StreamableWrapper::processOne(r, point);
223 if (!haveOrigin && !finished)
224 {
225 if (std::isnan(m_xOrigin))
226 m_xOrigin = point.getFieldAs<double>(Dimension::Id::X);
227 if (std::isnan(m_yOrigin))
228 m_yOrigin = point.getFieldAs<double>(Dimension::Id::Y);
229 m_splitter.setOrigin(m_xOrigin, m_yOrigin);
230 haveOrigin = true;
231 }
232
233 idx++;
234 while (!finished)
235 {
236 // Read subsequent points.
237 while (true)
238 {
239 point.setPointId(idx);
240 finished = !StreamableWrapper::processOne(r, point);
241 idx++;
242 if (idx == m_table.capacity() || finished)
243 break;
244 }
245 PointId last = idx - 1;
246
247 SpatialReference srs = r.getSpatialReference();
248 if (!srs.empty())
249 m_table.setSpatialReference(srs);
250 // Reproject if necessary.
251 if (m_repro)
252 {
253 for (idx = 0; idx < last; ++idx)
254 {
255 point.setPointId(idx);
256 if (!StreamableWrapper::processOne(*m_repro, point))
257 skips[idx] = true;
258 }
259 SpatialReference srs = r.getSpatialReference();
260 if (!srs.empty())
261 m_table.setSpatialReference(srs);
262 }
263
264 // Split and write.
265 for (idx = 0; idx < last; ++idx)
266 {
267 if (skips[idx])
268 continue;
269
270 point.setPointId(idx);
271 m_splitter.processPoint(point, adder);
272
273 }
274 for (size_t i = 0; i < skips.size(); ++i)
275 skips[i] = false;
276 idx = 0;
277 }
278 StreamableWrapper::done(r, m_table);
279 if (m_repro)
280 StreamableWrapper::done(*m_repro, m_table);
281 }
282 }
283
284
adder(PointRef & point,int xpos,int ypos)285 void TileKernel::adder(PointRef& point, int xpos, int ypos)
286 {
287 Coord loc(xpos, ypos);
288
289 Stage *w;
290 Streamable *sw;
291
292 auto wi = m_writers.find(loc);
293 if (wi == m_writers.end())
294 {
295 std::string filename(m_outputFile);
296 std::string xname(std::to_string(xpos));
297 std::string yname(std::to_string(ypos));
298 filename.replace(m_hashPos, 1, (xname + "_" + yname));
299
300 w = &m_manager.makeWriter(filename, "");
301 if (!w)
302 throw pdal_error("Couldn't create writer for output file '" +
303 m_outputFile + "'.");
304 sw = dynamic_cast<Streamable *>(w);
305 if (!sw)
306 throw pdal_error("Driver '" + w->getName() + "' for input file '" +
307 m_outputFile + "' is not streamable.");
308 m_writers[loc] = sw;
309
310 sw->prepare(m_table);
311 StreamableWrapper::spatialReferenceChanged(*sw, m_outSrs);
312 StreamableWrapper::ready(*sw, m_table);
313 }
314 else
315 sw = wi->second;
316 StreamableWrapper::processOne(*sw, point);
317 }
318
319 } // namespace pdal
320