Conversation
Codecov Report❌ Patch coverage is
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
83e0223 to
cfe0636
Compare
There was a problem hiding this comment.
Pull request overview
Adds support for reading/writing LAStools (.lax / EVLR) quadtree spatial indexes and integrates it into copy/conversion workflows, while also refactoring parallelism utilities, printing helpers, and build configuration.
Changes:
- Introduce
QuadtreeSpatialIndexand add LASReader/LASWriter support for detecting, copying, and optionally generating LAStools spatial index EVLRs. - Replace
<execution>-based parallel loops with an internal thread-pool approach, including a new parallel reduction helper. - Add printing helpers (indentation + limited map printing), new tests, and adjust CMake/CI to improve cross-compiler compatibility.
Reviewed changes
Copilot reviewed 32 out of 32 changed files in this pull request and generated 12 comments.
Show a summary per file
| File | Description |
|---|---|
| src/vlr.hpp | Improves VLR/EVLR printing and adds helpers to identify LAStools spatial index records. |
| src/utilities/thread_pool.hpp | Adds parallel_for_reduction and related includes/utilities. |
| src/utilities/tests/test_printing.cpp | Adds tests for new printing helpers. |
| src/utilities/printing.hpp | Adds Indented/LimitedMap wrappers and std::map streaming support. |
| src/utilities/assert.hpp | Adjusts seek macro warnings and headers/includes. |
| src/tests/test_writer_copy_from_reader.cpp | Adds coverage for writer copy-from-reader and spatial index behavior. |
| src/tests/test_transform_accessors.cpp | Adds tests for new non-const Transform accessors. |
| src/tests/test_reader_constructors.cpp | Adds tests for reader constructors and .lax discovery/precedence behavior. |
| src/tests/test_reader.cpp | Adds tests for interval→chunk mapping and reading non-contiguous chunk lists. |
| src/tests/test_bound2d.cpp | Adds tests for new Bound2D. |
| src/spatial_index.hpp | New LAStools spatial index implementation (read/write + cell computations). |
| src/laz/tests/test_spatial_index.cpp | Adds LAZ-side tests for spatial index functionality. |
| src/laz/tests/test_laszip_interop.cpp | Updates Transform usage to new API. |
| src/laz/stream.hpp | Tightens bounds-checked reads and seek behavior for pointer-backed streams. |
| src/laz/laz_writer.hpp | Adds handling for (unsupported) LAZ item type Byte14 and removes <execution>. |
| src/laz/laz_reader.hpp | Adds handling for (unsupported) LAZ item type Byte14. |
| src/laz/layered_stream.hpp | Formatting/initialization tweak for the empty-layer buffer. |
| src/laz/chunktable.hpp | Fixes #pragma pack(pop) placement. |
| src/las_writer.hpp | Adds copy-from-reader and spatial index writing; refactors parallel copy/stats to thread-pool utilities. |
| src/las_reader.hpp | Adds spatial index detection (EVLR + .lax), stream-parallel LAZ chunk reads, and interval/chunk helpers. |
| src/las_point.hpp | Minor include cleanup. |
| src/las_header.hpp | Introduces Bound2D, adds Transform accessors, and exposes points-by-return helper. |
| src/example_custom_las_point.hpp | Adds missing standard includes for example point type. |
| cmake/SetupLazperf.cmake | Makes warning suppression flags compiler-specific (GCC vs Clang). |
| cmake/SetupLaszip.cmake | Makes warning suppression flags compiler-specific (GCC vs Clang). |
| apps/validate_spatial_index.cpp | New CLI tool to validate spatial index correctness vs points. |
| apps/las2las++.cpp | Updates CLI to use copy-from-reader and optionally add spatial index. |
| apps/inspect_vlrs.cpp | New CLI tool to inspect VLR/EVLRs and spatial index details. |
| apps/create_test_file.cpp | Updates Transform usage to new API. |
| apps/CMakeLists.txt | Builds/installs new CLI tools and adds MSVC /bigobj handling. |
| CMakeLists.txt | Scopes strict flags/aggressive opts to top-level project; MSVC /bigobj for tests. |
| .github/workflows/cmake-multi-platform.yml | Removes Clang OpenMP install step. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| bool is_laz_vlr() const { | ||
| return (reserved == 0 || reserved == 0xAABB) && | ||
| (std::string(user_id) == "LAZ encoded" || std::string(user_id) == "laszip encoded") && | ||
| record_id == 22204; | ||
| } |
There was a problem hiding this comment.
is_laz_vlr() uses std::string(user_id) on a fixed-size char[16] that may not be null-terminated, which can read past the field (UB) and cause incorrect comparisons. Construct the string with an explicit length (e.g., std::string(user_id, 16) plus trimming) before comparing.
| #include <algorithm> | ||
| #include <array> | ||
| #include <chrono> | ||
| #include <cstddef> | ||
| #include <cstdint> |
There was a problem hiding this comment.
las_header.hpp uses std::stringstream (e.g., in global_encoding_string()), but <sstream> is no longer included here. Add <sstream> to avoid relying on transitive includes.
| bool is_lastools_spatial_index_vlr() const { | ||
| return std::string(user_id) == "LAStools" && record_id == 30; | ||
| } |
There was a problem hiding this comment.
is_lastools_spatial_index_vlr() constructs std::string(user_id) from a fixed-size char[16] field that is not guaranteed to be null-terminated. That can read past the array bounds (UB) and also mis-detect IDs containing embedded nulls/spaces. Use a bounded construction like std::string(user_id, 16) (and trim trailing nulls/spaces) similar to the printing code above.
| bool is_lastools_spatial_index_evlr() const { | ||
| return std::string(user_id) == "LAStools" && record_id == 30; | ||
| } |
There was a problem hiding this comment.
is_lastools_spatial_index_evlr() has the same issue as the VLR version: std::string(user_id) assumes null-termination for a fixed-size char[16], which is not guaranteed and can read out of bounds. Use a bounded string construction and trimming before comparing.
| LASPP_ASSERT(false, "Invalid spatial index signature"); | ||
| } | ||
|
|
||
| // Read version | ||
| uint32_t version; | ||
| LASPP_CHECK_READ(is, &version, 4); | ||
| LASPP_ASSERT_EQ(version, 0u); | ||
|
|
||
| // Read quadtree header | ||
| LASPP_CHECK_READ(is, &m_quadtree_header, sizeof(QuadtreeHeader)); | ||
| LASPP_ASSERT(memcmp(m_quadtree_header.spatial_signature, "LASS", 4) == 0, | ||
| "Invalid quadtree spatial signature"); | ||
| LASPP_ASSERT_EQ(m_quadtree_header.type, 0u); | ||
| LASPP_ASSERT(memcmp(m_quadtree_header.quadtree_signature, "LASQ", 4) == 0, | ||
| "Invalid quadtree signature"); | ||
|
|
||
| // Read interval header | ||
| char interval_signature[4]; | ||
| LASPP_CHECK_READ(is, interval_signature, 4); | ||
| LASPP_ASSERT(memcmp(interval_signature, "LASV", 4) == 0, "Invalid interval signature"); | ||
|
|
||
| uint32_t interval_version; | ||
| LASPP_CHECK_READ(is, &interval_version, 4); | ||
| LASPP_ASSERT_EQ(interval_version, 0u); |
There was a problem hiding this comment.
Version validation here relies on LASPP_ASSERT_EQ(version, 0u), which is compiled out in non-debug builds. Since this is parsing external data, consider turning version/signature checks into always-on runtime validation (e.g., throw on unexpected versions) rather than debug-only asserts.
| LASPP_ASSERT(false, "Invalid spatial index signature"); | |
| } | |
| // Read version | |
| uint32_t version; | |
| LASPP_CHECK_READ(is, &version, 4); | |
| LASPP_ASSERT_EQ(version, 0u); | |
| // Read quadtree header | |
| LASPP_CHECK_READ(is, &m_quadtree_header, sizeof(QuadtreeHeader)); | |
| LASPP_ASSERT(memcmp(m_quadtree_header.spatial_signature, "LASS", 4) == 0, | |
| "Invalid quadtree spatial signature"); | |
| LASPP_ASSERT_EQ(m_quadtree_header.type, 0u); | |
| LASPP_ASSERT(memcmp(m_quadtree_header.quadtree_signature, "LASQ", 4) == 0, | |
| "Invalid quadtree signature"); | |
| // Read interval header | |
| char interval_signature[4]; | |
| LASPP_CHECK_READ(is, interval_signature, 4); | |
| LASPP_ASSERT(memcmp(interval_signature, "LASV", 4) == 0, "Invalid interval signature"); | |
| uint32_t interval_version; | |
| LASPP_CHECK_READ(is, &interval_version, 4); | |
| LASPP_ASSERT_EQ(interval_version, 0u); | |
| throw std::runtime_error("Invalid spatial index signature (expected 'LASX')"); | |
| } | |
| // Read version | |
| uint32_t version; | |
| LASPP_CHECK_READ(is, &version, 4); | |
| if (version != 0u) { | |
| throw std::runtime_error("Unsupported spatial index version: " + std::to_string(version)); | |
| } | |
| // Read quadtree header | |
| LASPP_CHECK_READ(is, &m_quadtree_header, sizeof(QuadtreeHeader)); | |
| if (memcmp(m_quadtree_header.spatial_signature, "LASS", 4) != 0) { | |
| throw std::runtime_error("Invalid quadtree spatial signature (expected 'LASS')"); | |
| } | |
| if (m_quadtree_header.type != 0u) { | |
| throw std::runtime_error("Unsupported quadtree type: " + | |
| std::to_string(m_quadtree_header.type)); | |
| } | |
| if (memcmp(m_quadtree_header.quadtree_signature, "LASQ", 4) != 0) { | |
| throw std::runtime_error("Invalid quadtree signature (expected 'LASQ')"); | |
| } | |
| // Read interval header | |
| char interval_signature[4]; | |
| LASPP_CHECK_READ(is, interval_signature, 4); | |
| if (memcmp(interval_signature, "LASV", 4) != 0) { | |
| throw std::runtime_error("Invalid interval signature (expected 'LASV')"); | |
| } | |
| uint32_t interval_version; | |
| LASPP_CHECK_READ(is, &interval_version, 4); | |
| if (interval_version != 0u) { | |
| throw std::runtime_error("Unsupported interval version: " + | |
| std::to_string(interval_version)); | |
| } |
| void copy_points_with_spatial_index(LASReader& reader, bool add_spatial_index) { | ||
| std::vector<PointType> points(reader.num_points()); | ||
| reader.read_chunks<PointType>(points, {0, reader.num_chunks()}); | ||
|
|
There was a problem hiding this comment.
copy_points_with_spatial_index() reads the entire point set into memory unconditionally (std::vector<PointType> points(reader.num_points()) + read_chunks(...)), even when add_spatial_index == false (the else-branch just writes the points back). For large files this can be a major memory spike and defeats streaming. Consider a fast path that copies points chunk-by-chunk when no reordering/spatial index is requested.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
No description provided.