Size COPC octree from union bbox when merging multiple inputs - #296
Open
agrigoriev wants to merge 10 commits into
Open
Size COPC octree from union bbox when merging multiple inputs#296agrigoriev wants to merge 10 commits into
agrigoriev wants to merge 10 commits into
Conversation
agrigoriev
force-pushed
the
copc-multifile-merge
branch
from
April 27, 2026 02:53
513aeb4 to
8abccea
Compare
agrigoriev
added a commit
that referenced
this pull request
Apr 27, 2026
Fix 5 from the maintainer review on #296. The catalog union-bbox override in set_header was applied to every initialization, including per-file COPC mode (`*.copc.laz` template with a `*` placeholder) where each output file should use its own tile's bbox. Per-file outputs were being inflated to cover the whole input set. Gate the override on (catalog_bbox_valid && merged && path_has_copc_suffix(template_filename)) so: - single-file COPC (merged) keeps the union override (correct) - per-file COPC (`*` placeholder) uses each tile's own bbox - non-COPC writes are untouched (they ignore bbox at open anyway, but explicit gate keeps it clear) Verified: per-file COPC writes of bcts_1+bcts_2 produce two outputs with their own per-file bbox in the COPC info VLR (file 1 center_y ~629279, not the union 629429). Merge mode unchanged.
agrigoriev
added a commit
that referenced
this pull request
Apr 27, 2026
Two follow-up fixes for #296: Issue 1 — auto max_depth used file-1's count, not the merged total. LASRlaswriter::set_header overrode bbox for merged COPC but left header.number_of_point_records as the per-tile count. LASio::init saves whatever count it sees, and COPCwriter's compute_max_depth heuristic divides total points by max_points_per_octant (100000) to pick depth — so a 1.35M-point merge picked depth from file-1's 531k count alone, ending up too shallow for the merged data set. - FileCollection::get_total_points() sums header counts across every input. - LASRlaswriter::process(FileCollection*&) captures the total. - set_header overrides h.number_of_point_records with the catalog total when writing merged COPC. Verified: bcts_1+bcts_2 merge now produces 88 hierarchy entries vs 40 with file-1's count alone, and depth-3 reads now return all 1.35M points. Issue 4 — VPC z bounds were dropped, catalog_bbox_valid only checked x/y. read_vpc parsed the 6-element STAC proj:bbox using indices [0,1,3,4] (skipping z at [2] and [5]); FileCollection's zmin/zmax stayed at numeric_limits defaults. Combined with the write-side check that ignored z, merged COPC from a VPC could get bogus z bounds and either fail late (degenerate-bbox check) or silently clamp. - read_vpc reads min_z/max_z from 6-element bbox and updates headers + catalog z accumulators. - catalog_bbox_valid now requires zmin ≤ zmax in addition to x/y validity.
agrigoriev
force-pushed
the
copc-multifile-merge
branch
from
April 27, 2026 10:33
8abccea to
b614726
Compare
agrigoriev
added a commit
that referenced
this pull request
Apr 27, 2026
Issue 10 from the latest review on #296. The lasR-emitted VPC was a closed loop bug: write_vpc wrote properties.proj:bbox as 4 elements (no z) and a 6D top-level "bbox" field, but read_vpc only inspects properties.proj:bbox. Round-tripping a lasR-generated VPC into a merged COPC write therefore lost z bounds on the catalog side, the writer's catalog_bbox_valid check (which now requires z) returned false, and the union bbox/count override was silently skipped — falling back to the per-tile bbox of file 1. Switch the proj:bbox write to the 6-element form [minx, miny, minz, maxx, maxy, maxz]. The reader already handles both the 4D and 6D forms (post-#296), so this stays compatible with VPCs emitted by other tools. Separately, pc:count was parsed as `int`. The STAC pointcloud extension puts no upper bound on the field, and a catalog over ~2.1B points would silently truncate, mis-sizing the auto max_depth heuristic. Read as uint64_t.
agrigoriev
added a commit
that referenced
this pull request
Apr 27, 2026
Fix 5 from the maintainer review on #296. The catalog union-bbox override in set_header was applied to every initialization, including per-file COPC mode (`*.copc.laz` template with a `*` placeholder) where each output file should use its own tile's bbox. Per-file outputs were being inflated to cover the whole input set. Gate the override on (catalog_bbox_valid && merged && path_has_copc_suffix(template_filename)) so: - single-file COPC (merged) keeps the union override (correct) - per-file COPC (`*` placeholder) uses each tile's own bbox - non-COPC writes are untouched (they ignore bbox at open anyway, but explicit gate keeps it clear) Verified: per-file COPC writes of bcts_1+bcts_2 produce two outputs with their own per-file bbox in the COPC info VLR (file 1 center_y ~629279, not the union 629429). Merge mode unchanged.
agrigoriev
added a commit
that referenced
this pull request
Apr 27, 2026
Two follow-up fixes for #296: Issue 1 — auto max_depth used file-1's count, not the merged total. LASRlaswriter::set_header overrode bbox for merged COPC but left header.number_of_point_records as the per-tile count. LASio::init saves whatever count it sees, and COPCwriter's compute_max_depth heuristic divides total points by max_points_per_octant (100000) to pick depth — so a 1.35M-point merge picked depth from file-1's 531k count alone, ending up too shallow for the merged data set. - FileCollection::get_total_points() sums header counts across every input. - LASRlaswriter::process(FileCollection*&) captures the total. - set_header overrides h.number_of_point_records with the catalog total when writing merged COPC. Verified: bcts_1+bcts_2 merge now produces 88 hierarchy entries vs 40 with file-1's count alone, and depth-3 reads now return all 1.35M points. Issue 4 — VPC z bounds were dropped, catalog_bbox_valid only checked x/y. read_vpc parsed the 6-element STAC proj:bbox using indices [0,1,3,4] (skipping z at [2] and [5]); FileCollection's zmin/zmax stayed at numeric_limits defaults. Combined with the write-side check that ignored z, merged COPC from a VPC could get bogus z bounds and either fail late (degenerate-bbox check) or silently clamp. - read_vpc reads min_z/max_z from 6-element bbox and updates headers + catalog z accumulators. - catalog_bbox_valid now requires zmin ≤ zmax in addition to x/y validity.
agrigoriev
added a commit
that referenced
this pull request
Apr 27, 2026
Issue 10 from the latest review on #296. The lasR-emitted VPC was a closed loop bug: write_vpc wrote properties.proj:bbox as 4 elements (no z) and a 6D top-level "bbox" field, but read_vpc only inspects properties.proj:bbox. Round-tripping a lasR-generated VPC into a merged COPC write therefore lost z bounds on the catalog side, the writer's catalog_bbox_valid check (which now requires z) returned false, and the union bbox/count override was silently skipped — falling back to the per-tile bbox of file 1. Switch the proj:bbox write to the 6-element form [minx, miny, minz, maxx, maxy, maxz]. The reader already handles both the 4D and 6D forms (post-#296), so this stays compatible with VPCs emitted by other tools. Separately, pc:count was parsed as `int`. The STAC pointcloud extension puts no upper bound on the field, and a catalog over ~2.1B points would silently truncate, mis-sizing the auto max_depth heuristic. Read as uint64_t.
agrigoriev
force-pushed
the
copc-multifile-merge
branch
from
April 27, 2026 10:48
b614726 to
14bb4d5
Compare
agrigoriev
force-pushed
the
enhance-copc-writing
branch
from
April 27, 2026 10:48
c67fb7f to
215a71d
Compare
agrigoriev
added a commit
that referenced
this pull request
Apr 27, 2026
Fix 5 from the maintainer review on #296. The catalog union-bbox override in set_header was applied to every initialization, including per-file COPC mode (`*.copc.laz` template with a `*` placeholder) where each output file should use its own tile's bbox. Per-file outputs were being inflated to cover the whole input set. Gate the override on (catalog_bbox_valid && merged && path_has_copc_suffix(template_filename)) so: - single-file COPC (merged) keeps the union override (correct) - per-file COPC (`*` placeholder) uses each tile's own bbox - non-COPC writes are untouched (they ignore bbox at open anyway, but explicit gate keeps it clear) Verified: per-file COPC writes of bcts_1+bcts_2 produce two outputs with their own per-file bbox in the COPC info VLR (file 1 center_y ~629279, not the union 629429). Merge mode unchanged.
agrigoriev
added a commit
that referenced
this pull request
Apr 27, 2026
Two follow-up fixes for #296: Issue 1 — auto max_depth used file-1's count, not the merged total. LASRlaswriter::set_header overrode bbox for merged COPC but left header.number_of_point_records as the per-tile count. LASio::init saves whatever count it sees, and COPCwriter's compute_max_depth heuristic divides total points by max_points_per_octant (100000) to pick depth — so a 1.35M-point merge picked depth from file-1's 531k count alone, ending up too shallow for the merged data set. - FileCollection::get_total_points() sums header counts across every input. - LASRlaswriter::process(FileCollection*&) captures the total. - set_header overrides h.number_of_point_records with the catalog total when writing merged COPC. Verified: bcts_1+bcts_2 merge now produces 88 hierarchy entries vs 40 with file-1's count alone, and depth-3 reads now return all 1.35M points. Issue 4 — VPC z bounds were dropped, catalog_bbox_valid only checked x/y. read_vpc parsed the 6-element STAC proj:bbox using indices [0,1,3,4] (skipping z at [2] and [5]); FileCollection's zmin/zmax stayed at numeric_limits defaults. Combined with the write-side check that ignored z, merged COPC from a VPC could get bogus z bounds and either fail late (degenerate-bbox check) or silently clamp. - read_vpc reads min_z/max_z from 6-element bbox and updates headers + catalog z accumulators. - catalog_bbox_valid now requires zmin ≤ zmax in addition to x/y validity.
agrigoriev
added a commit
that referenced
this pull request
Apr 27, 2026
Issue 10 from the latest review on #296. The lasR-emitted VPC was a closed loop bug: write_vpc wrote properties.proj:bbox as 4 elements (no z) and a 6D top-level "bbox" field, but read_vpc only inspects properties.proj:bbox. Round-tripping a lasR-generated VPC into a merged COPC write therefore lost z bounds on the catalog side, the writer's catalog_bbox_valid check (which now requires z) returned false, and the union bbox/count override was silently skipped — falling back to the per-tile bbox of file 1. Switch the proj:bbox write to the 6-element form [minx, miny, minz, maxx, maxy, maxz]. The reader already handles both the 4D and 6D forms (post-#296), so this stays compatible with VPCs emitted by other tools. Separately, pc:count was parsed as `int`. The STAC pointcloud extension puts no upper bound on the field, and a catalog over ~2.1B points would silently truncate, mis-sizing the auto max_depth heuristic. Read as uint64_t.
agrigoriev
force-pushed
the
copc-multifile-merge
branch
from
April 27, 2026 12:39
14bb4d5 to
25f15f0
Compare
agrigoriev
added a commit
that referenced
this pull request
Apr 27, 2026
Fix 5 from the maintainer review on #296. The catalog union-bbox override in set_header was applied to every initialization, including per-file COPC mode (`*.copc.laz` template with a `*` placeholder) where each output file should use its own tile's bbox. Per-file outputs were being inflated to cover the whole input set. Gate the override on (catalog_bbox_valid && merged && path_has_copc_suffix(template_filename)) so: - single-file COPC (merged) keeps the union override (correct) - per-file COPC (`*` placeholder) uses each tile's own bbox - non-COPC writes are untouched (they ignore bbox at open anyway, but explicit gate keeps it clear) Verified: per-file COPC writes of bcts_1+bcts_2 produce two outputs with their own per-file bbox in the COPC info VLR (file 1 center_y ~629279, not the union 629429). Merge mode unchanged.
agrigoriev
added a commit
that referenced
this pull request
Apr 27, 2026
Two follow-up fixes for #296: Issue 1 — auto max_depth used file-1's count, not the merged total. LASRlaswriter::set_header overrode bbox for merged COPC but left header.number_of_point_records as the per-tile count. LASio::init saves whatever count it sees, and COPCwriter's compute_max_depth heuristic divides total points by max_points_per_octant (100000) to pick depth — so a 1.35M-point merge picked depth from file-1's 531k count alone, ending up too shallow for the merged data set. - FileCollection::get_total_points() sums header counts across every input. - LASRlaswriter::process(FileCollection*&) captures the total. - set_header overrides h.number_of_point_records with the catalog total when writing merged COPC. Verified: bcts_1+bcts_2 merge now produces 88 hierarchy entries vs 40 with file-1's count alone, and depth-3 reads now return all 1.35M points. Issue 4 — VPC z bounds were dropped, catalog_bbox_valid only checked x/y. read_vpc parsed the 6-element STAC proj:bbox using indices [0,1,3,4] (skipping z at [2] and [5]); FileCollection's zmin/zmax stayed at numeric_limits defaults. Combined with the write-side check that ignored z, merged COPC from a VPC could get bogus z bounds and either fail late (degenerate-bbox check) or silently clamp. - read_vpc reads min_z/max_z from 6-element bbox and updates headers + catalog z accumulators. - catalog_bbox_valid now requires zmin ≤ zmax in addition to x/y validity.
agrigoriev
added a commit
that referenced
this pull request
Apr 27, 2026
Issue 10 from the latest review on #296. The lasR-emitted VPC was a closed loop bug: write_vpc wrote properties.proj:bbox as 4 elements (no z) and a 6D top-level "bbox" field, but read_vpc only inspects properties.proj:bbox. Round-tripping a lasR-generated VPC into a merged COPC write therefore lost z bounds on the catalog side, the writer's catalog_bbox_valid check (which now requires z) returned false, and the union bbox/count override was silently skipped — falling back to the per-tile bbox of file 1. Switch the proj:bbox write to the 6-element form [minx, miny, minz, maxx, maxy, maxz]. The reader already handles both the 4D and 6D forms (post-#296), so this stays compatible with VPCs emitted by other tools. Separately, pc:count was parsed as `int`. The STAC pointcloud extension puts no upper bound on the field, and a catalog over ~2.1B points would silently truncate, mis-sizing the auto max_depth heuristic. Read as uint64_t.
agrigoriev
force-pushed
the
copc-multifile-merge
branch
from
April 27, 2026 14:11
25f15f0 to
dde7712
Compare
agrigoriev
added a commit
that referenced
this pull request
Apr 27, 2026
Fix 5 from the maintainer review on #296. The catalog union-bbox override in set_header was applied to every initialization, including per-file COPC mode (`*.copc.laz` template with a `*` placeholder) where each output file should use its own tile's bbox. Per-file outputs were being inflated to cover the whole input set. Gate the override on (catalog_bbox_valid && merged && path_has_copc_suffix(template_filename)) so: - single-file COPC (merged) keeps the union override (correct) - per-file COPC (`*` placeholder) uses each tile's own bbox - non-COPC writes are untouched (they ignore bbox at open anyway, but explicit gate keeps it clear) Verified: per-file COPC writes of bcts_1+bcts_2 produce two outputs with their own per-file bbox in the COPC info VLR (file 1 center_y ~629279, not the union 629429). Merge mode unchanged.
agrigoriev
added a commit
that referenced
this pull request
Apr 27, 2026
Two follow-up fixes for #296: Issue 1 — auto max_depth used file-1's count, not the merged total. LASRlaswriter::set_header overrode bbox for merged COPC but left header.number_of_point_records as the per-tile count. LASio::init saves whatever count it sees, and COPCwriter's compute_max_depth heuristic divides total points by max_points_per_octant (100000) to pick depth — so a 1.35M-point merge picked depth from file-1's 531k count alone, ending up too shallow for the merged data set. - FileCollection::get_total_points() sums header counts across every input. - LASRlaswriter::process(FileCollection*&) captures the total. - set_header overrides h.number_of_point_records with the catalog total when writing merged COPC. Verified: bcts_1+bcts_2 merge now produces 88 hierarchy entries vs 40 with file-1's count alone, and depth-3 reads now return all 1.35M points. Issue 4 — VPC z bounds were dropped, catalog_bbox_valid only checked x/y. read_vpc parsed the 6-element STAC proj:bbox using indices [0,1,3,4] (skipping z at [2] and [5]); FileCollection's zmin/zmax stayed at numeric_limits defaults. Combined with the write-side check that ignored z, merged COPC from a VPC could get bogus z bounds and either fail late (degenerate-bbox check) or silently clamp. - read_vpc reads min_z/max_z from 6-element bbox and updates headers + catalog z accumulators. - catalog_bbox_valid now requires zmin ≤ zmax in addition to x/y validity.
agrigoriev
added a commit
that referenced
this pull request
Apr 27, 2026
Issue 10 from the latest review on #296. The lasR-emitted VPC was a closed loop bug: write_vpc wrote properties.proj:bbox as 4 elements (no z) and a 6D top-level "bbox" field, but read_vpc only inspects properties.proj:bbox. Round-tripping a lasR-generated VPC into a merged COPC write therefore lost z bounds on the catalog side, the writer's catalog_bbox_valid check (which now requires z) returned false, and the union bbox/count override was silently skipped — falling back to the per-tile bbox of file 1. Switch the proj:bbox write to the 6-element form [minx, miny, minz, maxx, maxy, maxz]. The reader already handles both the 4D and 6D forms (post-#296), so this stays compatible with VPCs emitted by other tools. Separately, pc:count was parsed as `int`. The STAC pointcloud extension puts no upper bound on the field, and a catalog over ~2.1B points would silently truncate, mis-sizing the auto max_depth heuristic. Read as uint64_t.
agrigoriev
force-pushed
the
copc-multifile-merge
branch
from
April 27, 2026 14:14
dde7712 to
3f24668
Compare
agrigoriev
added a commit
that referenced
this pull request
Apr 28, 2026
Fix 5 from the maintainer review on #296. The catalog union-bbox override in set_header was applied to every initialization, including per-file COPC mode (`*.copc.laz` template with a `*` placeholder) where each output file should use its own tile's bbox. Per-file outputs were being inflated to cover the whole input set. Gate the override on (catalog_bbox_valid && merged && path_has_copc_suffix(template_filename)) so: - single-file COPC (merged) keeps the union override (correct) - per-file COPC (`*` placeholder) uses each tile's own bbox - non-COPC writes are untouched (they ignore bbox at open anyway, but explicit gate keeps it clear) Verified: per-file COPC writes of bcts_1+bcts_2 produce two outputs with their own per-file bbox in the COPC info VLR (file 1 center_y ~629279, not the union 629429). Merge mode unchanged.
agrigoriev
added a commit
that referenced
this pull request
Apr 28, 2026
Two follow-up fixes for #296: Issue 1 — auto max_depth used file-1's count, not the merged total. LASRlaswriter::set_header overrode bbox for merged COPC but left header.number_of_point_records as the per-tile count. LASio::init saves whatever count it sees, and COPCwriter's compute_max_depth heuristic divides total points by max_points_per_octant (100000) to pick depth — so a 1.35M-point merge picked depth from file-1's 531k count alone, ending up too shallow for the merged data set. - FileCollection::get_total_points() sums header counts across every input. - LASRlaswriter::process(FileCollection*&) captures the total. - set_header overrides h.number_of_point_records with the catalog total when writing merged COPC. Verified: bcts_1+bcts_2 merge now produces 88 hierarchy entries vs 40 with file-1's count alone, and depth-3 reads now return all 1.35M points. Issue 4 — VPC z bounds were dropped, catalog_bbox_valid only checked x/y. read_vpc parsed the 6-element STAC proj:bbox using indices [0,1,3,4] (skipping z at [2] and [5]); FileCollection's zmin/zmax stayed at numeric_limits defaults. Combined with the write-side check that ignored z, merged COPC from a VPC could get bogus z bounds and either fail late (degenerate-bbox check) or silently clamp. - read_vpc reads min_z/max_z from 6-element bbox and updates headers + catalog z accumulators. - catalog_bbox_valid now requires zmin ≤ zmax in addition to x/y validity.
agrigoriev
added a commit
that referenced
this pull request
Apr 28, 2026
Issue 10 from the latest review on #296. The lasR-emitted VPC was a closed loop bug: write_vpc wrote properties.proj:bbox as 4 elements (no z) and a 6D top-level "bbox" field, but read_vpc only inspects properties.proj:bbox. Round-tripping a lasR-generated VPC into a merged COPC write therefore lost z bounds on the catalog side, the writer's catalog_bbox_valid check (which now requires z) returned false, and the union bbox/count override was silently skipped — falling back to the per-tile bbox of file 1. Switch the proj:bbox write to the 6-element form [minx, miny, minz, maxx, maxy, maxz]. The reader already handles both the 4D and 6D forms (post-#296), so this stays compatible with VPCs emitted by other tools. Separately, pc:count was parsed as `int`. The STAC pointcloud extension puts no upper bound on the field, and a catalog over ~2.1B points would silently truncate, mis-sizing the auto max_depth heuristic. Read as uint64_t.
agrigoriev
force-pushed
the
copc-multifile-merge
branch
from
April 28, 2026 00:46
3f24668 to
9878a91
Compare
agrigoriev
added a commit
that referenced
this pull request
Apr 28, 2026
Fix 5 from the maintainer review on #296. The catalog union-bbox override in set_header was applied to every initialization, including per-file COPC mode (`*.copc.laz` template with a `*` placeholder) where each output file should use its own tile's bbox. Per-file outputs were being inflated to cover the whole input set. Gate the override on (catalog_bbox_valid && merged && path_has_copc_suffix(template_filename)) so: - single-file COPC (merged) keeps the union override (correct) - per-file COPC (`*` placeholder) uses each tile's own bbox - non-COPC writes are untouched (they ignore bbox at open anyway, but explicit gate keeps it clear) Verified: per-file COPC writes of bcts_1+bcts_2 produce two outputs with their own per-file bbox in the COPC info VLR (file 1 center_y ~629279, not the union 629429). Merge mode unchanged.
agrigoriev
added a commit
that referenced
this pull request
Apr 28, 2026
Two follow-up fixes for #296: Issue 1 — auto max_depth used file-1's count, not the merged total. LASRlaswriter::set_header overrode bbox for merged COPC but left header.number_of_point_records as the per-tile count. LASio::init saves whatever count it sees, and COPCwriter's compute_max_depth heuristic divides total points by max_points_per_octant (100000) to pick depth — so a 1.35M-point merge picked depth from file-1's 531k count alone, ending up too shallow for the merged data set. - FileCollection::get_total_points() sums header counts across every input. - LASRlaswriter::process(FileCollection*&) captures the total. - set_header overrides h.number_of_point_records with the catalog total when writing merged COPC. Verified: bcts_1+bcts_2 merge now produces 88 hierarchy entries vs 40 with file-1's count alone, and depth-3 reads now return all 1.35M points. Issue 4 — VPC z bounds were dropped, catalog_bbox_valid only checked x/y. read_vpc parsed the 6-element STAC proj:bbox using indices [0,1,3,4] (skipping z at [2] and [5]); FileCollection's zmin/zmax stayed at numeric_limits defaults. Combined with the write-side check that ignored z, merged COPC from a VPC could get bogus z bounds and either fail late (degenerate-bbox check) or silently clamp. - read_vpc reads min_z/max_z from 6-element bbox and updates headers + catalog z accumulators. - catalog_bbox_valid now requires zmin ≤ zmax in addition to x/y validity.
agrigoriev
added a commit
that referenced
this pull request
Apr 28, 2026
Issue 10 from the latest review on #296. The lasR-emitted VPC was a closed loop bug: write_vpc wrote properties.proj:bbox as 4 elements (no z) and a 6D top-level "bbox" field, but read_vpc only inspects properties.proj:bbox. Round-tripping a lasR-generated VPC into a merged COPC write therefore lost z bounds on the catalog side, the writer's catalog_bbox_valid check (which now requires z) returned false, and the union bbox/count override was silently skipped — falling back to the per-tile bbox of file 1. Switch the proj:bbox write to the 6-element form [minx, miny, minz, maxx, maxy, maxz]. The reader already handles both the 4D and 6D forms (post-#296), so this stays compatible with VPCs emitted by other tools. Separately, pc:count was parsed as `int`. The STAC pointcloud extension puts no upper bound on the field, and a catalog over ~2.1B points would silently truncate, mis-sizing the auto max_depth heuristic. Read as uint64_t.
agrigoriev
force-pushed
the
copc-multifile-merge
branch
from
April 28, 2026 03:16
5caf976 to
bc00dd5
Compare
agrigoriev
added a commit
that referenced
this pull request
Apr 28, 2026
Fix 5 from the maintainer review on #296. The catalog union-bbox override in set_header was applied to every initialization, including per-file COPC mode (`*.copc.laz` template with a `*` placeholder) where each output file should use its own tile's bbox. Per-file outputs were being inflated to cover the whole input set. Gate the override on (catalog_bbox_valid && merged && path_has_copc_suffix(template_filename)) so: - single-file COPC (merged) keeps the union override (correct) - per-file COPC (`*` placeholder) uses each tile's own bbox - non-COPC writes are untouched (they ignore bbox at open anyway, but explicit gate keeps it clear) Verified: per-file COPC writes of bcts_1+bcts_2 produce two outputs with their own per-file bbox in the COPC info VLR (file 1 center_y ~629279, not the union 629429). Merge mode unchanged.
agrigoriev
added a commit
that referenced
this pull request
Apr 28, 2026
Two follow-up fixes for #296: Issue 1 — auto max_depth used file-1's count, not the merged total. LASRlaswriter::set_header overrode bbox for merged COPC but left header.number_of_point_records as the per-tile count. LASio::init saves whatever count it sees, and COPCwriter's compute_max_depth heuristic divides total points by max_points_per_octant (100000) to pick depth — so a 1.35M-point merge picked depth from file-1's 531k count alone, ending up too shallow for the merged data set. - FileCollection::get_total_points() sums header counts across every input. - LASRlaswriter::process(FileCollection*&) captures the total. - set_header overrides h.number_of_point_records with the catalog total when writing merged COPC. Verified: bcts_1+bcts_2 merge now produces 88 hierarchy entries vs 40 with file-1's count alone, and depth-3 reads now return all 1.35M points. Issue 4 — VPC z bounds were dropped, catalog_bbox_valid only checked x/y. read_vpc parsed the 6-element STAC proj:bbox using indices [0,1,3,4] (skipping z at [2] and [5]); FileCollection's zmin/zmax stayed at numeric_limits defaults. Combined with the write-side check that ignored z, merged COPC from a VPC could get bogus z bounds and either fail late (degenerate-bbox check) or silently clamp. - read_vpc reads min_z/max_z from 6-element bbox and updates headers + catalog z accumulators. - catalog_bbox_valid now requires zmin ≤ zmax in addition to x/y validity.
agrigoriev
added a commit
that referenced
this pull request
Apr 28, 2026
Issue 10 from the latest review on #296. The lasR-emitted VPC was a closed loop bug: write_vpc wrote properties.proj:bbox as 4 elements (no z) and a 6D top-level "bbox" field, but read_vpc only inspects properties.proj:bbox. Round-tripping a lasR-generated VPC into a merged COPC write therefore lost z bounds on the catalog side, the writer's catalog_bbox_valid check (which now requires z) returned false, and the union bbox/count override was silently skipped — falling back to the per-tile bbox of file 1. Switch the proj:bbox write to the 6-element form [minx, miny, minz, maxx, maxy, maxz]. The reader already handles both the 4D and 6D forms (post-#296), so this stays compatible with VPCs emitted by other tools. Separately, pc:count was parsed as `int`. The STAC pointcloud extension puts no upper bound on the field, and a catalog over ~2.1B points would silently truncate, mis-sizing the auto max_depth heuristic. Read as uint64_t.
agrigoriev
force-pushed
the
copc-multifile-merge
branch
from
April 28, 2026 05:48
bc00dd5 to
9c239a5
Compare
agrigoriev
added a commit
that referenced
this pull request
Apr 28, 2026
Fix 5 from the maintainer review on #296. The catalog union-bbox override in set_header was applied to every initialization, including per-file COPC mode (`*.copc.laz` template with a `*` placeholder) where each output file should use its own tile's bbox. Per-file outputs were being inflated to cover the whole input set. Gate the override on (catalog_bbox_valid && merged && path_has_copc_suffix(template_filename)) so: - single-file COPC (merged) keeps the union override (correct) - per-file COPC (`*` placeholder) uses each tile's own bbox - non-COPC writes are untouched (they ignore bbox at open anyway, but explicit gate keeps it clear) Verified: per-file COPC writes of bcts_1+bcts_2 produce two outputs with their own per-file bbox in the COPC info VLR (file 1 center_y ~629279, not the union 629429). Merge mode unchanged.
agrigoriev
added a commit
that referenced
this pull request
Apr 28, 2026
Two follow-up fixes for #296: Issue 1 — auto max_depth used file-1's count, not the merged total. LASRlaswriter::set_header overrode bbox for merged COPC but left header.number_of_point_records as the per-tile count. LASio::init saves whatever count it sees, and COPCwriter's compute_max_depth heuristic divides total points by max_points_per_octant (100000) to pick depth — so a 1.35M-point merge picked depth from file-1's 531k count alone, ending up too shallow for the merged data set. - FileCollection::get_total_points() sums header counts across every input. - LASRlaswriter::process(FileCollection*&) captures the total. - set_header overrides h.number_of_point_records with the catalog total when writing merged COPC. Verified: bcts_1+bcts_2 merge now produces 88 hierarchy entries vs 40 with file-1's count alone, and depth-3 reads now return all 1.35M points. Issue 4 — VPC z bounds were dropped, catalog_bbox_valid only checked x/y. read_vpc parsed the 6-element STAC proj:bbox using indices [0,1,3,4] (skipping z at [2] and [5]); FileCollection's zmin/zmax stayed at numeric_limits defaults. Combined with the write-side check that ignored z, merged COPC from a VPC could get bogus z bounds and either fail late (degenerate-bbox check) or silently clamp. - read_vpc reads min_z/max_z from 6-element bbox and updates headers + catalog z accumulators. - catalog_bbox_valid now requires zmin ≤ zmax in addition to x/y validity.
agrigoriev
added a commit
that referenced
this pull request
Apr 28, 2026
Issue 10 from the latest review on #296. The lasR-emitted VPC was a closed loop bug: write_vpc wrote properties.proj:bbox as 4 elements (no z) and a 6D top-level "bbox" field, but read_vpc only inspects properties.proj:bbox. Round-tripping a lasR-generated VPC into a merged COPC write therefore lost z bounds on the catalog side, the writer's catalog_bbox_valid check (which now requires z) returned false, and the union bbox/count override was silently skipped — falling back to the per-tile bbox of file 1. Switch the proj:bbox write to the 6-element form [minx, miny, minz, maxx, maxy, maxz]. The reader already handles both the 4D and 6D forms (post-#296), so this stays compatible with VPCs emitted by other tools. Separately, pc:count was parsed as `int`. The STAC pointcloud extension puts no upper bound on the field, and a catalog over ~2.1B points would silently truncate, mis-sizing the auto max_depth heuristic. Read as uint64_t.
agrigoriev
force-pushed
the
copc-multifile-merge
branch
from
April 28, 2026 11:27
9c239a5 to
ed249b5
Compare
agrigoriev
added a commit
that referenced
this pull request
Apr 29, 2026
Fix 5 from the maintainer review on #296. The catalog union-bbox override in set_header was applied to every initialization, including per-file COPC mode (`*.copc.laz` template with a `*` placeholder) where each output file should use its own tile's bbox. Per-file outputs were being inflated to cover the whole input set. Gate the override on (catalog_bbox_valid && merged && path_has_copc_suffix(template_filename)) so: - single-file COPC (merged) keeps the union override (correct) - per-file COPC (`*` placeholder) uses each tile's own bbox - non-COPC writes are untouched (they ignore bbox at open anyway, but explicit gate keeps it clear) Verified: per-file COPC writes of bcts_1+bcts_2 produce two outputs with their own per-file bbox in the COPC info VLR (file 1 center_y ~629279, not the union 629429). Merge mode unchanged.
agrigoriev
added a commit
that referenced
this pull request
Apr 29, 2026
Two follow-up fixes for #296: Issue 1 — auto max_depth used file-1's count, not the merged total. LASRlaswriter::set_header overrode bbox for merged COPC but left header.number_of_point_records as the per-tile count. LASio::init saves whatever count it sees, and COPCwriter's compute_max_depth heuristic divides total points by max_points_per_octant (100000) to pick depth — so a 1.35M-point merge picked depth from file-1's 531k count alone, ending up too shallow for the merged data set. - FileCollection::get_total_points() sums header counts across every input. - LASRlaswriter::process(FileCollection*&) captures the total. - set_header overrides h.number_of_point_records with the catalog total when writing merged COPC. Verified: bcts_1+bcts_2 merge now produces 88 hierarchy entries vs 40 with file-1's count alone, and depth-3 reads now return all 1.35M points. Issue 4 — VPC z bounds were dropped, catalog_bbox_valid only checked x/y. read_vpc parsed the 6-element STAC proj:bbox using indices [0,1,3,4] (skipping z at [2] and [5]); FileCollection's zmin/zmax stayed at numeric_limits defaults. Combined with the write-side check that ignored z, merged COPC from a VPC could get bogus z bounds and either fail late (degenerate-bbox check) or silently clamp. - read_vpc reads min_z/max_z from 6-element bbox and updates headers + catalog z accumulators. - catalog_bbox_valid now requires zmin ≤ zmax in addition to x/y validity.
agrigoriev
added a commit
that referenced
this pull request
Apr 29, 2026
Issue 10 from the latest review on #296. The lasR-emitted VPC was a closed loop bug: write_vpc wrote properties.proj:bbox as 4 elements (no z) and a 6D top-level "bbox" field, but read_vpc only inspects properties.proj:bbox. Round-tripping a lasR-generated VPC into a merged COPC write therefore lost z bounds on the catalog side, the writer's catalog_bbox_valid check (which now requires z) returned false, and the union bbox/count override was silently skipped — falling back to the per-tile bbox of file 1. Switch the proj:bbox write to the 6-element form [minx, miny, minz, maxx, maxy, maxz]. The reader already handles both the 4D and 6D forms (post-#296), so this stays compatible with VPCs emitted by other tools. Separately, pc:count was parsed as `int`. The STAC pointcloud extension puts no upper bound on the field, and a catalog over ~2.1B points would silently truncate, mis-sizing the auto max_depth heuristic. Read as uint64_t.
agrigoriev
force-pushed
the
copc-multifile-merge
branch
from
April 29, 2026 02:11
ed249b5 to
69f40d2
Compare
agrigoriev
added a commit
that referenced
this pull request
Apr 29, 2026
Fix 5 from the maintainer review on #296. The catalog union-bbox override in set_header was applied to every initialization, including per-file COPC mode (`*.copc.laz` template with a `*` placeholder) where each output file should use its own tile's bbox. Per-file outputs were being inflated to cover the whole input set. Gate the override on (catalog_bbox_valid && merged && path_has_copc_suffix(template_filename)) so: - single-file COPC (merged) keeps the union override (correct) - per-file COPC (`*` placeholder) uses each tile's own bbox - non-COPC writes are untouched (they ignore bbox at open anyway, but explicit gate keeps it clear) Verified: per-file COPC writes of bcts_1+bcts_2 produce two outputs with their own per-file bbox in the COPC info VLR (file 1 center_y ~629279, not the union 629429). Merge mode unchanged.
agrigoriev
added a commit
that referenced
this pull request
Apr 29, 2026
Two follow-up fixes for #296: Issue 1 — auto max_depth used file-1's count, not the merged total. LASRlaswriter::set_header overrode bbox for merged COPC but left header.number_of_point_records as the per-tile count. LASio::init saves whatever count it sees, and COPCwriter's compute_max_depth heuristic divides total points by max_points_per_octant (100000) to pick depth — so a 1.35M-point merge picked depth from file-1's 531k count alone, ending up too shallow for the merged data set. - FileCollection::get_total_points() sums header counts across every input. - LASRlaswriter::process(FileCollection*&) captures the total. - set_header overrides h.number_of_point_records with the catalog total when writing merged COPC. Verified: bcts_1+bcts_2 merge now produces 88 hierarchy entries vs 40 with file-1's count alone, and depth-3 reads now return all 1.35M points. Issue 4 — VPC z bounds were dropped, catalog_bbox_valid only checked x/y. read_vpc parsed the 6-element STAC proj:bbox using indices [0,1,3,4] (skipping z at [2] and [5]); FileCollection's zmin/zmax stayed at numeric_limits defaults. Combined with the write-side check that ignored z, merged COPC from a VPC could get bogus z bounds and either fail late (degenerate-bbox check) or silently clamp. - read_vpc reads min_z/max_z from 6-element bbox and updates headers + catalog z accumulators. - catalog_bbox_valid now requires zmin ≤ zmax in addition to x/y validity.
agrigoriev
added a commit
that referenced
this pull request
Apr 29, 2026
Issue 10 from the latest review on #296. The lasR-emitted VPC was a closed loop bug: write_vpc wrote properties.proj:bbox as 4 elements (no z) and a 6D top-level "bbox" field, but read_vpc only inspects properties.proj:bbox. Round-tripping a lasR-generated VPC into a merged COPC write therefore lost z bounds on the catalog side, the writer's catalog_bbox_valid check (which now requires z) returned false, and the union bbox/count override was silently skipped — falling back to the per-tile bbox of file 1. Switch the proj:bbox write to the 6-element form [minx, miny, minz, maxx, maxy, maxz]. The reader already handles both the 4D and 6D forms (post-#296), so this stays compatible with VPCs emitted by other tools. Separately, pc:count was parsed as `int`. The STAC pointcloud extension puts no upper bound on the field, and a catalog over ~2.1B points would silently truncate, mis-sizing the auto max_depth heuristic. Read as uint64_t.
agrigoriev
force-pushed
the
copc-multifile-merge
branch
from
April 29, 2026 04:43
69f40d2 to
e93fc9f
Compare
agrigoriev
added a commit
that referenced
this pull request
Apr 29, 2026
Fix 5 from the maintainer review on #296. The catalog union-bbox override in set_header was applied to every initialization, including per-file COPC mode (`*.copc.laz` template with a `*` placeholder) where each output file should use its own tile's bbox. Per-file outputs were being inflated to cover the whole input set. Gate the override on (catalog_bbox_valid && merged && path_has_copc_suffix(template_filename)) so: - single-file COPC (merged) keeps the union override (correct) - per-file COPC (`*` placeholder) uses each tile's own bbox - non-COPC writes are untouched (they ignore bbox at open anyway, but explicit gate keeps it clear) Verified: per-file COPC writes of bcts_1+bcts_2 produce two outputs with their own per-file bbox in the COPC info VLR (file 1 center_y ~629279, not the union 629429). Merge mode unchanged.
agrigoriev
added a commit
that referenced
this pull request
Apr 29, 2026
Two follow-up fixes for #296: Issue 1 — auto max_depth used file-1's count, not the merged total. LASRlaswriter::set_header overrode bbox for merged COPC but left header.number_of_point_records as the per-tile count. LASio::init saves whatever count it sees, and COPCwriter's compute_max_depth heuristic divides total points by max_points_per_octant (100000) to pick depth — so a 1.35M-point merge picked depth from file-1's 531k count alone, ending up too shallow for the merged data set. - FileCollection::get_total_points() sums header counts across every input. - LASRlaswriter::process(FileCollection*&) captures the total. - set_header overrides h.number_of_point_records with the catalog total when writing merged COPC. Verified: bcts_1+bcts_2 merge now produces 88 hierarchy entries vs 40 with file-1's count alone, and depth-3 reads now return all 1.35M points. Issue 4 — VPC z bounds were dropped, catalog_bbox_valid only checked x/y. read_vpc parsed the 6-element STAC proj:bbox using indices [0,1,3,4] (skipping z at [2] and [5]); FileCollection's zmin/zmax stayed at numeric_limits defaults. Combined with the write-side check that ignored z, merged COPC from a VPC could get bogus z bounds and either fail late (degenerate-bbox check) or silently clamp. - read_vpc reads min_z/max_z from 6-element bbox and updates headers + catalog z accumulators. - catalog_bbox_valid now requires zmin ≤ zmax in addition to x/y validity.
Fix 5 from the maintainer review on #296. The catalog union-bbox override in set_header was applied to every initialization, including per-file COPC mode (`*.copc.laz` template with a `*` placeholder) where each output file should use its own tile's bbox. Per-file outputs were being inflated to cover the whole input set. Gate the override on (catalog_bbox_valid && merged && path_has_copc_suffix(template_filename)) so: - single-file COPC (merged) keeps the union override (correct) - per-file COPC (`*` placeholder) uses each tile's own bbox - non-COPC writes are untouched (they ignore bbox at open anyway, but explicit gate keeps it clear) Verified: per-file COPC writes of bcts_1+bcts_2 produce two outputs with their own per-file bbox in the COPC info VLR (file 1 center_y ~629279, not the union 629429). Merge mode unchanged.
Two follow-up fixes for #296: Issue 1 — auto max_depth used file-1's count, not the merged total. LASRlaswriter::set_header overrode bbox for merged COPC but left header.number_of_point_records as the per-tile count. LASio::init saves whatever count it sees, and COPCwriter's compute_max_depth heuristic divides total points by max_points_per_octant (100000) to pick depth — so a 1.35M-point merge picked depth from file-1's 531k count alone, ending up too shallow for the merged data set. - FileCollection::get_total_points() sums header counts across every input. - LASRlaswriter::process(FileCollection*&) captures the total. - set_header overrides h.number_of_point_records with the catalog total when writing merged COPC. Verified: bcts_1+bcts_2 merge now produces 88 hierarchy entries vs 40 with file-1's count alone, and depth-3 reads now return all 1.35M points. Issue 4 — VPC z bounds were dropped, catalog_bbox_valid only checked x/y. read_vpc parsed the 6-element STAC proj:bbox using indices [0,1,3,4] (skipping z at [2] and [5]); FileCollection's zmin/zmax stayed at numeric_limits defaults. Combined with the write-side check that ignored z, merged COPC from a VPC could get bogus z bounds and either fail late (degenerate-bbox check) or silently clamp. - read_vpc reads min_z/max_z from 6-element bbox and updates headers + catalog z accumulators. - catalog_bbox_valid now requires zmin ≤ zmax in addition to x/y validity.
Issue 10 from the latest review on #296. The lasR-emitted VPC was a closed loop bug: write_vpc wrote properties.proj:bbox as 4 elements (no z) and a 6D top-level "bbox" field, but read_vpc only inspects properties.proj:bbox. Round-tripping a lasR-generated VPC into a merged COPC write therefore lost z bounds on the catalog side, the writer's catalog_bbox_valid check (which now requires z) returned false, and the union bbox/count override was silently skipped — falling back to the per-tile bbox of file 1. Switch the proj:bbox write to the 6-element form [minx, miny, minz, maxx, maxy, maxz]. The reader already handles both the 4D and 6D forms (post-#296), so this stays compatible with VPCs emitted by other tools. Separately, pc:count was parsed as `int`. The STAC pointcloud extension puts no upper bound on the field, and a catalog over ~2.1B points would silently truncate, mis-sizing the auto max_depth heuristic. Read as uint64_t.
Issue 15 — write_vpc reused the EPSG:4979-transformed z values for properties.proj:bbox. The OGR transform mutates z in-place when called with a 3D source/target, so by the time proj:bbox was written zmin/zmax no longer carried source-CRS values while x/y still did — making the proj:bbox internally inconsistent if the vertical transform shifted z. Use a local copy of zmin/zmax for the WGS-84 top-level "bbox" and keep the originals for proj:bbox. Issue 16 — read_vpc accepted a mix of 4D and 6D proj:bbox in the same VPC. The catalog's z-bounds accumulator only ran on 6D features, but total point count covered every feature; on a merged COPC write that sized the auto max_depth from the full count but the octree z extent from only the 3D-bbox subset, producing a skewed octree. Track the ratio of 3D-vs-2D entries and reject loudly if mixed, with a message pointing the user at the proj:bbox shape they need.
If a merged COPC write reaches set_header without a valid 3D catalog bbox — typically a legacy VPC whose proj:bbox is 4-element so the reader can't accumulate z bounds — set_header used to silently fall back to file-1's per-tile bbox/count. The result was a merged COPC sized from a single tile: octree bbox under-covers the data (clamping at close), auto max_depth is computed from one file's count (too shallow), and PR review chain caught both downstream. Add an explicit check: merged && copc-suffix && !catalog_bbox_valid is now a stage-level error with a message naming the likely cause (legacy 4D VPC) and the two ways out (regenerate the VPC with 6D proj:bbox, or use a '*' placeholder for per-tile COPC). The user sees the failure at the start of writing rather than as a clamp warning at close. Also: explicit <algorithm> include now that path_has_copc_suffix uses std::equal — relying on transitive includes for std::equal, std::exception_ptr, etc. is fragile across compilers. Pair with the <exception> include added to writelas.cpp on PR 1.
The "fail fast on invalid catalog bbox" guard added for the new streaming COPC writer assumes the writer needs an accurate union bbox at open() to size its octree. LASlib's LASwriterCOPC (now the default) inventories at close instead, so that guard is unnecessary on the old path and would only refuse work the old writer can handle. Restrict the guard to experimental_writer = TRUE and update the multi-file merge test to opt in.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This pull request improves the robustness and correctness of merged COPC (Cloud Optimized Point Cloud) file writing by ensuring accurate 3D bounding boxes and point counts are used throughout the pipeline. It enforces strict validation of input metadata, updates the handling of 3D bounds in both reading and writing, and adds safeguards to prevent malformed outputs when merging multiple files. A new test verifies the correctness of the merged COPC output.
Improvements to 3D bounding box and point count handling:
proj:bboxwith 6 elements); mixed 2D/3D bboxes now cause a clear error, preventing silent creation of malformed merged outputs. [1] [2] [3] [4]read_vpcto use 64-bit integers, preventing silent truncation for large datasets.FileCollection, and used when merging files into a single COPC output. [1] [2] [3] [4] [5]COPC writing and validation enhancements:
LASRlaswriter, captures the union bounding box and total point count from the input collection and uses them to size the COPC octree and compute the correct auto max_depth for merged outputs; refuses to write merged COPC files if the input lacks a valid 3D bounding box. [1] [2] [3] [4]proj:bboxarrays, aligning with the stricter requirements for merged COPC outputs. [1] [2] [3]Testing improvements: