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