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