1 /******************************************************************************
2  * Copyright (c) 2008, Mateusz Loskot
3  * Copyright (c) 2008, Phil Vachon
4  *
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following
9  * conditions are met:
10  *
11  *     * Redistributions of source code must retain the above copyright
12  *       notice, this list of conditions and the following disclaimer.
13  *     * Redistributions in binary form must reproduce the above copyright
14  *       notice, this list of conditions and the following disclaimer in
15  *       the documentation and/or other materials provided
16  *       with the distribution.
17  *     * Neither the name of the Martin Isenburg or Iowa Department
18  *       of Natural Resources nor the names of its contributors may be
19  *       used to endorse or promote products derived from this software
20  *       without specific prior written permission.
21  *
22  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
23  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
24  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
25  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
26  * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
27  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
28  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
29  * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
30  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
31  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
32  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
33  * OF SUCH DAMAGE.
34  ****************************************************************************/
35 
36 #include "LasHeader.hpp"
37 
38 #include <pdal/pdal_config.hpp>
39 #include <pdal/Scaling.hpp>
40 #include <pdal/util/Utils.hpp>
41 #include <pdal/util/Algorithm.hpp>
42 
43 #include "LasSummaryData.hpp"
44 
45 #include "GeotiffSupport.hpp"
46 
47 namespace pdal
48 {
49 
50 const std::string LasHeader::FILE_SIGNATURE("LASF");
51 #ifndef _WIN32
52 const size_t LasHeader::LEGACY_RETURN_COUNT;
53 const size_t LasHeader::RETURN_COUNT;
54 #endif
55 
GetDefaultSoftwareId()56 std::string GetDefaultSoftwareId()
57 {
58     std::string ver(Config::versionString());
59     std::stringstream oss;
60     std::ostringstream revs;
61     revs << Config::sha1();
62     oss << "PDAL " << ver << " (" << revs.str().substr(0, 6) <<")";
63     return oss.str();
64 }
65 
LasHeader()66 LasHeader::LasHeader() : m_fileSig(FILE_SIGNATURE), m_sourceId(0),
67     m_globalEncoding(0), m_versionMinor(2), m_systemId(getSystemIdentifier()),
68     m_createDOY(0), m_createYear(0), m_vlrOffset(0), m_pointOffset(0),
69     m_vlrCount(0), m_pointFormat(0), m_pointLen(0), m_pointCount(0),
70     m_isCompressed(false), m_eVlrOffset(0), m_eVlrCount(0), m_nosrs(false)
71 {
72     std::time_t now;
73     std::time(&now);
74     std::tm* ptm = std::gmtime(&now);
75     if (ptm)
76     {
77         m_createDOY = static_cast<uint16_t>(ptm->tm_yday);
78         m_createYear = static_cast<uint16_t>(ptm->tm_year + 1900);
79     }
80 
81     m_pointLen = basePointLen(m_pointFormat);
82     m_pointCountByReturn.fill(0);
83     m_scales.fill(1.0);
84     m_offsets.fill(0.0);
85 }
86 
87 
initialize(LogPtr log,uintmax_t fileSize,bool nosrs)88 void LasHeader::initialize(LogPtr log, uintmax_t fileSize, bool nosrs)
89 {
90     m_log = log;
91     m_fileSize = fileSize;
92     m_nosrs = nosrs;
93 }
94 
95 
versionString() const96 std::string LasHeader::versionString() const
97 {
98     return "1." + std::to_string(m_versionMinor);
99 }
100 
101 
pointFormatSupported() const102 Utils::StatusWithReason LasHeader::pointFormatSupported() const
103 {
104     if (hasWave())
105         return { -1, "PDAL does not support point formats with waveform data (4, 5, 9 and 10)" };
106     if (versionAtLeast(1, 4))
107     {
108         if (pointFormat() > 10)
109             return { -1, "LAS version " + versionString() + " only supports point formats 0-10." };
110     }
111     else
112     {
113         if (pointFormat() > 5)
114             return { -1, "LAS version '" + versionString() + " only supports point formats 0-5." };
115     }
116     return true;
117 }
118 
119 
setSummary(const LasSummaryData & summary)120 void LasHeader::setSummary(const LasSummaryData& summary)
121 {
122     m_pointCount = summary.getTotalNumPoints();
123     try
124     {
125         for (size_t num = 0; num < RETURN_COUNT; ++num)
126             m_pointCountByReturn[num] = (int)summary.getReturnCount(num);
127     }
128     catch (const LasSummaryData::error& err)
129     {
130         throw error(err.what());
131     }
132     m_bounds = summary.getBounds();
133 }
134 
135 
setScaling(const Scaling & scaling)136 void LasHeader::setScaling(const Scaling& scaling)
137 {
138     const double& xs = scaling.m_xXform.m_scale.m_val;
139     const double& ys = scaling.m_yXform.m_scale.m_val;
140     const double& zs = scaling.m_zXform.m_scale.m_val;
141     if (xs == 0)
142         throw error("X scale of 0.0 is invalid!");
143 
144     if (ys == 0)
145         throw error("Y scale of 0.0 is invalid!");
146 
147     if (zs == 0)
148         throw error("Z scale of 0.0 is invalid!");
149 
150     m_scales[0] = xs;
151     m_scales[1] = ys;
152     m_scales[2] = zs;
153 
154     m_offsets[0] = scaling.m_xXform.m_offset.m_val;
155     m_offsets[1] = scaling.m_yXform.m_offset.m_val;
156     m_offsets[2] = scaling.m_zXform.m_offset.m_val;
157 }
158 
159 
basePointLen(uint8_t type)160 uint16_t LasHeader::basePointLen(uint8_t type)
161 {
162     const uint16_t len[] = { 20, 28, 26, 34, 57, 63, 30, 36, 38, 59, 67 };
163     const size_t numTypes = sizeof(len) / sizeof(len[0]);
164 
165     if (type > numTypes)
166         return 0;
167     return len[type];
168 }
169 
170 
valid() const171 bool LasHeader::valid() const
172 {
173     if (m_fileSig != FILE_SIGNATURE)
174         return false;
175     if (m_versionMinor > 10)
176         return false;
177     if (m_createDOY > 366)
178         return false;
179     if (m_createYear < 1970 || m_createYear > 2100)
180        return false;
181     return true;
182 }
183 
184 
get(ILeStream & in,Uuid & uuid)185 void LasHeader::get(ILeStream& in, Uuid& uuid)
186 {
187     std::vector<char> buf(uuid.size());
188 
189     in.get(buf.data(), uuid.size());
190     uuid.unpack(buf.data());
191 }
192 
193 
put(OLeStream & out,Uuid uuid)194 void LasHeader::put(OLeStream& out, Uuid uuid)
195 {
196     char buf[uuid.size()];
197 
198     uuid.pack(buf);
199     out.put(buf, uuid.size());
200 }
201 
202 
usedDims() const203 Dimension::IdList LasHeader::usedDims() const
204 {
205     using namespace Dimension;
206 
207     Dimension::Id dims[] = { Id::ReturnNumber, Id::NumberOfReturns,
208         Id::X, Id::Y, Id::Z, Id::Intensity, Id::ScanChannel,
209         Id::ScanDirectionFlag, Id::EdgeOfFlightLine, Id::Classification,
210         Id::UserData, Id::ScanAngleRank, Id::PointSourceId };
211 
212     Dimension::IdList ids(std::begin(dims), std::end(dims));
213 
214     if (hasTime())
215         ids.push_back(Id::GpsTime);
216     if (hasColor())
217     {
218         ids.push_back(Id::Red);
219         ids.push_back(Id::Green);
220         ids.push_back(Id::Blue);
221     }
222     if (hasInfrared())
223         ids.push_back(Id::Infrared);
224     if (has14PointFormat())
225     {
226         ids.push_back(Id::ScanChannel);
227         ids.push_back(Id::ClassFlags);
228     }
229 
230     return ids;
231 }
232 
setSrs()233 void LasHeader::setSrs()
234 {
235     // If we're not processing SRS, just return.
236     if (m_nosrs)
237         return;
238 
239     if (has14PointFormat() && !useWkt())
240     {
241         m_log->get(LogLevel::Error) << "Global encoding WKT flag not set "
242             "for point format 6 - 10." << std::endl;
243     }
244     if (findVlr(TRANSFORM_USER_ID, WKT_RECORD_ID) &&
245         findVlr(TRANSFORM_USER_ID, GEOTIFF_DIRECTORY_RECORD_ID))
246     {
247         m_log->get(LogLevel::Debug) << "File contains both "
248             "WKT and GeoTiff VLRs which is disallowed." << std::endl;
249     }
250 
251     // We always use WKT for formats 6+, regardless of the WKT global encoding bit (warning
252     // issued above.)
253     // Otherwise (formats 0-5), we only use it of the WKT bit is set and it's version 1.4
254     // or better.  For valid files the WKT bit won't be set for files < version 1.4, but
255     // we can't be sure, so we check here.
256     try
257     {
258         if ((useWkt() && m_versionMinor >= 4) || has14PointFormat())
259             setSrsFromWkt();
260         else
261             setSrsFromGeotiff();
262     }
263     catch (Geotiff::error& err)
264     {
265         m_log->get(LogLevel::Error) << "Could not create an SRS: " <<
266             err.what() << std::endl;
267     }
268     catch (...)
269     {
270         m_log->get(LogLevel::Error) << "Could not create an SRS" << std::endl;
271     }
272 }
273 
274 
removeVLR(const std::string & userId,uint16_t recordId)275 void LasHeader::removeVLR(const std::string& userId, uint16_t recordId)
276 {
277     auto matches = [&userId, recordId](const LasVLR& vlr)
278     {
279         return vlr.matches(userId, recordId);
280     };
281 
282     Utils::remove_if(m_vlrs, matches);
283     Utils::remove_if(m_eVlrs, matches);
284 }
285 
286 
removeVLR(const std::string & userId)287 void LasHeader::removeVLR(const std::string& userId)
288 {
289 
290     auto matches = [&userId ](const LasVLR& vlr)
291     {
292         return vlr.matches(userId);
293     };
294 
295     Utils::remove_if(m_vlrs, matches);
296     Utils::remove_if(m_eVlrs, matches);
297 }
298 
299 
findVlr(const std::string & userId,uint16_t recordId) const300 const LasVLR *LasHeader::findVlr(const std::string& userId,
301     uint16_t recordId) const
302 {
303     for (auto vi = m_vlrs.begin(); vi != m_vlrs.end(); ++vi)
304     {
305         const LasVLR& vlr = *vi;
306         if (vlr.matches(userId, recordId))
307             return &vlr;
308     }
309     return NULL;
310 }
311 
312 
setSrsFromWkt()313 void LasHeader::setSrsFromWkt()
314 {
315     const LasVLR *vlr = findVlr(TRANSFORM_USER_ID, WKT_RECORD_ID);
316     if (!vlr)
317         vlr = findVlr(LIBLAS_USER_ID, WKT_RECORD_ID);
318     if (!vlr || vlr->dataLen() == 0)
319         return;
320 
321     // There is supposed to be a NULL byte at the end of the data,
322     // but sometimes there isn't because some people don't follow the
323     // rules.  If there is a NULL byte, don't stick it in the
324     // wkt string.
325     size_t len = vlr->dataLen();
326     const char *c = vlr->data() + len - 1;
327     if (*c == 0)
328         len--;
329     std::string wkt(vlr->data(), len);
330     // Strip any excess NULL bytes from the WKT.
331     wkt.erase(std::find(wkt.begin(), wkt.end(), '\0'), wkt.end());
332     m_srs.set(wkt);
333 }
334 
335 
setSrsFromGeotiff()336 void LasHeader::setSrsFromGeotiff()
337 {
338     const LasVLR *vlr = findVlr(TRANSFORM_USER_ID, GEOTIFF_DIRECTORY_RECORD_ID);
339     // We must have a directory entry.
340     if (!vlr)
341         return;
342     auto data = reinterpret_cast<const uint8_t *>(vlr->data());
343     size_t dataLen = vlr->dataLen();
344 
345     std::vector<uint8_t> directoryRec(data, data + dataLen);
346 
347     vlr = findVlr(TRANSFORM_USER_ID, GEOTIFF_DOUBLES_RECORD_ID);
348     data = NULL;
349     dataLen = 0;
350     if (vlr && !vlr->isEmpty())
351     {
352         data = reinterpret_cast<const uint8_t *>(vlr->data());
353         dataLen = vlr->dataLen();
354     }
355     std::vector<uint8_t> doublesRec(data, data + dataLen);
356 
357     vlr = findVlr(TRANSFORM_USER_ID, GEOTIFF_ASCII_RECORD_ID);
358     data = NULL;
359     dataLen = 0;
360     if (vlr && !vlr->isEmpty())
361     {
362         data = reinterpret_cast<const uint8_t *>(vlr->data());
363         dataLen = vlr->dataLen();
364     }
365     std::vector<uint8_t> asciiRec(data, data + dataLen);
366 
367     GeotiffSrs geotiff(directoryRec, doublesRec, asciiRec, m_log);
368     m_geotiff_print = geotiff.gtiffPrintString();
369     SpatialReference gtiffSrs = geotiff.srs();
370     if (!gtiffSrs.empty())
371         m_srs = gtiffSrs;
372 }
373 
374 
operator >>(ILeStream & in,LasHeader & h)375 ILeStream& operator>>(ILeStream& in, LasHeader& h)
376 {
377     uint8_t versionMajor;
378     uint32_t legacyPointCount;
379     uint32_t legacyReturnCount;
380 
381     in.get(h.m_fileSig, 4);
382     if (!Utils::iequals(h.m_fileSig, LasHeader::FILE_SIGNATURE))
383         throw LasHeader::error("File signature is not 'LASF', "
384             "is this an LAS/LAZ file?");
385 
386     in >> h.m_sourceId >> h.m_globalEncoding;
387     LasHeader::get(in, h.m_projectUuid);
388     in >> versionMajor >> h.m_versionMinor;
389     in.get(h.m_systemId, 32);
390 
391     in.get(h.m_softwareId, 32);
392     in >> h.m_createDOY >> h.m_createYear >> h.m_vlrOffset >>
393         h.m_pointOffset >> h.m_vlrCount >> h.m_pointFormat >>
394         h.m_pointLen >> legacyPointCount;
395     h.m_pointCount = legacyPointCount;
396 
397     // Although it isn't part of the LAS spec, the two high bits have been used
398     // to indicate compression, though only the high bit is currently used.
399     if (h.m_pointFormat & 0x80)
400         h.setCompressed(true);
401     h.m_pointFormat &= ~0xC0;
402 
403     for (size_t i = 0; i < LasHeader::LEGACY_RETURN_COUNT; ++i)
404     {
405         in >> legacyReturnCount;
406         h.m_pointCountByReturn[i] = legacyReturnCount;
407     }
408 
409     in >> h.m_scales[0] >> h.m_scales[1] >> h.m_scales[2];
410     in >> h.m_offsets[0] >> h.m_offsets[1] >> h.m_offsets[2];
411 
412     double maxX, minX;
413     double maxY, minY;
414     double maxZ, minZ;
415     in >> maxX >> minX >> maxY >> minY >> maxZ >> minZ;
416     h.m_bounds = BOX3D(minX, minY, minZ, maxX, maxY, maxZ);
417 
418     if (h.versionAtLeast(1, 3))
419     {
420         uint64_t waveformOffset;
421         in >> waveformOffset;
422     }
423     if (h.versionAtLeast(1, 4))
424     {
425         in >> h.m_eVlrOffset >> h.m_eVlrCount >> h.m_pointCount;
426         for (size_t i = 0; i < LasHeader::RETURN_COUNT; ++i)
427             in >> h.m_pointCountByReturn[i];
428     }
429 
430     if (!h.compressed() && h.m_pointOffset > h.m_fileSize)
431         throw LasHeader::error("Invalid point offset - exceeds file size.");
432     if (!h.compressed() && h.m_pointOffset + h.m_pointCount * h.m_pointLen > h.m_fileSize)
433         throw LasHeader::error("Invalid point count. Number of points exceeds file size.");
434     if (h.m_vlrOffset > h.m_fileSize)
435         throw LasHeader::error("Invalid VLR offset - exceeds file size.");
436     // There was a bug in PDAL where it didn't write the VLR offset :(
437     /**
438     if (h.m_eVlrOffset > h.m_fileSize)
439         throw LasHeader::error("Invalid extended VLR offset - exceeds file size.");
440     **/
441 
442     // Read regular VLRs.
443     in.seek(h.m_vlrOffset);
444     for (size_t i = 0; i < h.m_vlrCount; ++i)
445     {
446         LasVLR r;
447         if (!r.read(in, h.m_pointOffset))
448             throw LasHeader::error("Invalid VLR - exceeds specified file range.");
449         h.m_vlrs.push_back(std::move(r));
450     }
451 
452     // Read extended VLRs.
453     if (h.versionAtLeast(1, 4))
454     {
455         in.seek(h.m_eVlrOffset);
456         for (size_t i = 0; i < h.m_eVlrCount; ++i)
457         {
458             ExtLasVLR r;
459             if (!r.read(in, h.m_fileSize))
460                 throw LasHeader::error("Invalid Extended VLR size - exceeds file size.");
461             h.m_vlrs.push_back(std::move(r));
462         }
463     }
464     h.setSrs();
465 
466     return in;
467 }
468 
469 
operator <<(OLeStream & out,const LasHeader & h)470 OLeStream& operator<<(OLeStream& out, const LasHeader& h)
471 {
472     uint32_t legacyPointCount = 0;
473     if (h.m_pointCount <= (std::numeric_limits<uint32_t>::max)())
474         legacyPointCount = (uint32_t)h.m_pointCount;
475 
476     out.put(h.m_fileSig, 4);
477     if (h.versionEquals(1, 0))
478         out << (uint32_t)0;
479     else if (h.versionEquals(1, 1))
480         out << h.m_sourceId << (uint16_t)0;
481     else
482         out << h.m_sourceId << h.m_globalEncoding;
483     LasHeader::put(out, h.m_projectUuid);
484     out << (uint8_t)1 << h.m_versionMinor;
485     out.put(h.m_systemId, 32);
486     out.put(h.m_softwareId, 32);
487 
488     uint8_t pointFormat = h.m_pointFormat;
489     if (h.compressed())
490         pointFormat |= 0x80;
491 
492     out << h.m_createDOY << h.m_createYear << h.m_vlrOffset <<
493         h.m_pointOffset << h.m_vlrCount << pointFormat <<
494         h.m_pointLen << legacyPointCount;
495 
496     for (size_t i = 0; i < LasHeader::LEGACY_RETURN_COUNT; ++i)
497     {
498         //ABELL - This needs fixing.  Should set to 0 when we exceed
499         // std::numeric_limits<uint32_t>::max().
500         uint32_t legacyReturnCount =
501             static_cast<uint32_t>((std::min)(h.m_pointCountByReturn[i],
502                 (uint64_t)(std::numeric_limits<uint32_t>::max)()));
503         out << legacyReturnCount;
504     }
505 
506     out << h.m_scales[0] << h.m_scales[1] << h.m_scales[2];
507     out << h.m_offsets[0] << h.m_offsets[1] << h.m_offsets[2];
508 
509     out << h.maxX() << h.minX() << h.maxY() << h.minY() << h.maxZ() << h.minZ();
510 
511     if (h.versionAtLeast(1, 3))
512         out << (uint64_t)0;
513     if (h.versionAtLeast(1, 4))
514     {
515         out << h.m_eVlrOffset << h.m_eVlrCount << h.m_pointCount;
516         for (size_t i = 0; i < LasHeader::RETURN_COUNT; ++i)
517             out << h.m_pointCountByReturn[i];
518     }
519 
520     return out;
521 }
522 
523 
operator <<(std::ostream & out,const LasHeader & h)524 std::ostream& operator<<(std::ostream& out, const LasHeader& h)
525 {
526     out << "File version = " << "1." << (int)h.m_versionMinor << "\n";
527     out << "File signature: " << h.m_fileSig << "\n";
528     out << "File source ID: " << h.m_sourceId << "\n";
529     out << "Global encoding: " << h.m_globalEncoding << "\n";
530     out << "Project UUID: " << h.m_projectUuid << "\n";
531     out << "System ID: " << h.m_systemId << "\n";
532     out << "Software ID: " << h.m_softwareId << "\n";
533     out << "Creation DOY: " << h.m_createDOY << "\n";
534     out << "Creation Year: " << h.m_createYear << "\n";
535     out << "VLR offset (header size): " << h.m_vlrOffset << "\n";
536     out << "VLR Count: " << h.m_vlrCount << "\n";
537     out << "Point format: " << (int)h.m_pointFormat << "\n";
538     out << "Point offset: " << h.m_pointOffset << "\n";
539     out << "Point count: " << h.m_pointCount << "\n";
540     for (size_t i = 0; i < LasHeader::RETURN_COUNT; ++i)
541         out << "Point count by return[" << i << "]: " <<
542             h.m_pointCountByReturn[i] << "\n";
543     out << "Scales X/Y/Z: " << h.m_scales[0] << "/" <<
544         h.m_scales[1] << "/" << h.m_scales[2] << "\n";
545     out << "Offsets X/Y/Z: " << h.m_offsets[0] << "/" <<
546         h.m_offsets[1] << "/" << h.m_offsets[2] << "\n";
547     out << "Max X/Y/Z: " << h.maxX() << "/" <<
548         h.maxY() << "/" << h.maxZ() << "\n";
549     out << "Min X/Y/Z: " << h.minX() << "/" <<
550         h.minY() << "/" << h.minZ() << "\n";
551     if (h.versionAtLeast(1, 4))
552     {
553         out << "Ext. VLR offset: " << h.m_eVlrOffset << "\n";
554         out << "Ext. VLR count: " << h.m_eVlrCount << "\n";
555     }
556     out << "Compressed: " << (h.m_isCompressed ? "true" : "false") << "\n";
557     return out;
558 }
559 
560 } // namespace pdal
561