Skip to content

Spatial Indexing#3

Open
ryanstocks00 wants to merge 32 commits intomainfrom
spatial_index
Open

Spatial Indexing#3
ryanstocks00 wants to merge 32 commits intomainfrom
spatial_index

Conversation

@ryanstocks00
Copy link
Copy Markdown
Contributor

No description provided.

@codecov
Copy link
Copy Markdown

codecov bot commented Nov 7, 2025

Codecov Report

❌ Patch coverage is 94.96644% with 75 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/spatial_index.hpp 84.38% 42 Missing ⚠️
src/vlr.hpp 50.00% 10 Missing ⚠️
src/las_header.hpp 78.04% 9 Missing ⚠️
src/las_reader.hpp 93.60% 8 Missing ⚠️
src/utilities/printing.hpp 89.74% 4 Missing ⚠️
src/laz/stream.hpp 94.44% 1 Missing ⚠️
src/utilities/thread_pool.hpp 96.77% 1 Missing ⚠️
Flag Coverage Δ
unittests 93.28% <94.96%> (?)

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
src/example_custom_las_point.hpp 100.00% <ø> (ø)
src/las_point.hpp 44.25% <ø> (ø)
src/las_writer.hpp 95.68% <100.00%> (ø)
src/laz/bytes14_encoder.hpp 100.00% <100.00%> (ø)
src/laz/chunktable.hpp 100.00% <ø> (ø)
src/laz/layered_stream.hpp 100.00% <ø> (ø)
src/laz/laz_reader.hpp 87.50% <ø> (ø)
src/laz/laz_writer.hpp 90.47% <ø> (ø)
src/laz/symbol_encoder.hpp 100.00% <100.00%> (ø)
src/laz/tests/test_bytes14_encoder.cpp 100.00% <100.00%> (ø)
... and 16 more
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 QuadtreeSpatialIndex and 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.

ryanstocks00 and others added 7 commits March 8, 2026 22:13
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>
@codacy-production
Copy link
Copy Markdown

codacy-production bot commented Apr 3, 2026

Up to standards ✅

🟢 Issues 26 medium

Results:
26 new issues

Category Results
BestPractice 8 medium
ErrorProne 2 medium
Complexity 16 medium

View in Codacy

🟢 Metrics 245 complexity

Metric Results
Complexity 245

View in Codacy

TIP This summary will be updated as you push new changes. Give us feedback

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 35 out of 35 changed files in this pull request and generated 6 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +109 to 115
// LASPP_DEBUG_ASSERT compiles out entirely in release (zero overhead on hot paths).
// In debug builds it is identical to LASPP_ASSERT.
#ifdef LASPP_DEBUG_ASSERTS
#define LASPP_DEBUG_ASSERT(condition, ...) LASPP_ASSERT(condition, __VA_ARGS__)
#define LASPP_DEBUG_ASSERT_BIN_OP(a, b, op, nop, ...) \
LASPP_ASSERT_BIN_OP(a, b, op, nop, __VA_ARGS__)
#else
Copy link

Copilot AI Apr 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LASPP_DEBUG_ASSERT / LASPP_DEBUG_ASSERT_BIN_OP (and the derived LASPP_DEBUG_ASSERT_* shorthands) forward __VA_ARGS__ by always inserting a comma. When these macros are invoked without an extra message (e.g. LASPP_DEBUG_ASSERT_LT(symbol, NSymbols);), this expands to an invalid call with a trailing comma and will fail to compile when LASPP_DEBUG_ASSERTS is enabled (tests enable it). Consider using __VA_OPT__ (C++20) to conditionally insert the comma, or otherwise make the forwarding macros accept empty varargs safely.

Copilot uses AI. Check for mistakes.
Comment on lines +143 to +156
template <typename T, typename Func>
void parallel_for_reduction(size_t begin, size_t end, T& result, Func func,
size_t chunk_size = 1) {
if (begin >= end) {
return;
}

size_t requested_threads = get_num_threads();
ensure_threads(requested_threads);
size_t active_threads = std::min(requested_threads, m_max_threads);

const size_t total_work = end - begin;
const size_t num_chunks = (total_work + chunk_size - 1) / chunk_size; // Ceiling division

Copy link

Copilot AI Apr 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

parallel_for_reduction computes num_chunks using (total_work + chunk_size - 1) / chunk_size without guarding chunk_size. Passing chunk_size == 0 will cause division-by-zero/UB. Please add a validation (assert or early return / treat 0 as 1) before using chunk_size.

Copilot uses AI. Check for mistakes.
Comment on lines +108 to +119
friend std::ostream& operator<<(std::ostream& os, const LimitedMap<K, V>& limited) {
size_t count = 0;
for (const auto& kv : limited.m_map) {
if (count++ >= limited.m_limit) {
break;
}
os << kv.second << '\n';
}
if (limited.m_show_remaining && limited.m_map.size() > limited.m_limit) {
os << "... (" << (limited.m_map.size() - limited.m_limit) << " more)" << '\n';
}
return os;
Copy link

Copilot AI Apr 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LimitedMap prints only kv.second and drops the key entirely. For many maps (e.g., std::map<int,std::string>) this loses essential information and makes output ambiguous. Consider printing key: value pairs (or at least providing an option to include the key) so the wrapper is generally useful.

Copilot uses AI. Check for mistakes.
Comment on lines +85 to +92
template <typename K, typename V>
inline std::ostream& operator<<(std::ostream& os, const std::map<K, V>& map) {
os << "{";
for (const auto& [k, v] : map) {
os << k << ": " << v << ", ";
}
return os << "}";
}
Copy link

Copilot AI Apr 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

operator<< for std::map always emits a trailing ", " before the closing } (e.g. {1: one, }). This makes the output look malformed and harder to diff/parse. Consider writing delimiters only between elements (or trimming the last delimiter).

Copilot uses AI. Check for mistakes.
Comment on lines +447 to +461
// Determine which level a cell index belongs to
// Returns the 0-based level: 0 = root, 1 = first subdivision, 2 = second subdivision, etc.
uint32_t get_cell_level_from_index(int32_t cell_index) const {
if (m_quadtree_header.levels == 0 || cell_index == 0) return 0;

// Find the highest level offset that is <= cell_index
// Level offsets: offset[0]=0, offset[1]=1, offset[2]=5, offset[3]=21, etc.
// Cell index 1 is at level 1 (first subdivision), which is 0-indexed as level 0
// But we want to return the actual level number (1-based converted to 0-based)
for (uint32_t level = m_quadtree_header.levels; level > 0; --level) {
uint32_t offset = calculate_level_offset(level);
if (static_cast<uint32_t>(cell_index) >= offset) {
// level is 1-based here, so level 1 -> return 0, level 2 -> return 1, etc.
return level - 1;
}
Copy link

Copilot AI Apr 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The documentation for get_cell_level_from_index says it returns “0 = root, 1 = first subdivision, 2 = second subdivision, …”, but the implementation (and tests) return 0 for level-1 cells and 1 for level-2 cells. This mismatch is confusing for callers. Please either adjust the returned value to match the documented semantics, or update the comment to reflect the current 0-based scheme used by the code/tests.

Copilot uses AI. Check for mistakes.
Comment on lines +333 to +362
// Write raw point data (for cases where you have raw bytes, e.g., with extra bytes)
// This updates the point count and offset automatically
void write_raw_point_data(std::span<const std::byte> raw_point_data, size_t num_points) {
LASPP_ASSERT_LE(m_stage, WritingStage::POINTS);
LASPP_ASSERT(!m_header.is_laz_compressed(),
"Raw point data writing not supported for compressed files");
LASPP_ASSERT_EQ(raw_point_data.size(), num_points * m_header.point_data_record_length(),
"Raw point data size doesn't match expected size");

// Set stage to POINTS if not already there
if (m_stage < WritingStage::POINTS) {
m_stage = WritingStage::POINTS;
// Update offset_to_point_data to current stream position
header().m_offset_to_point_data = static_cast<uint32_t>(m_output_stream.tellp());
}

// Write raw point data
m_output_stream.write(reinterpret_cast<const char*>(raw_point_data.data()),
static_cast<int64_t>(raw_point_data.size()));

// Update point count (normally done automatically by write_points())
header().m_number_of_point_records = num_points;
if (header().m_number_of_point_records < std::numeric_limits<uint32_t>::max() &&
(header().point_format() < 6 ||
(header().point_format() >= 128 && header().point_format() < 128 + 6))) {
header().m_legacy_number_of_point_records = static_cast<uint32_t>(num_points);
} else {
header().m_legacy_number_of_point_records = 0;
}
}
Copy link

Copilot AI Apr 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

write_raw_point_data updates point counts/offsets but does not update derived header fields like bounds and per-return counts. As a public API this can easily produce a structurally valid file with incorrect metadata. Consider either (a) documenting that the caller must update bounds/return counts before destruction, (b) providing optional parameters to supply/compute these, or (c) making this helper private/internal if it’s only intended for narrow use cases.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants