From 77307f8ee410bb55aa2feadc1217058032b4b569 Mon Sep 17 00:00:00 2001 From: Jan Caha Date: Mon, 29 Jun 2026 13:59:56 +0200 Subject: [PATCH 01/14] add parameter to force use of absolute paths --- src/vpc.cpp | 12 ++++++---- src/vpc.hpp | 2 +- tests/test_vpc.py | 61 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 69 insertions(+), 6 deletions(-) create mode 100644 tests/test_vpc.py diff --git a/src/vpc.cpp b/src/vpc.cpp index 10bde00..fbf560f 100644 --- a/src/vpc.cpp +++ b/src/vpc.cpp @@ -271,7 +271,7 @@ void geometryToJson(const Geometry &geom, const BOX3D &bbox, nlohmann::json &jso } } -bool VirtualPointCloud::write(std::string filename) +bool VirtualPointCloud::write(std::string filename, bool forceAbsolutePaths) { if (!isVpcFilename(filename)) filename += ".vpz"; @@ -288,9 +288,9 @@ bool VirtualPointCloud::write(std::string filename) for ( const File &f : files ) { std::string assetFilename; - if (pdal::Utils::isRemote(f.filename)) + if (pdal::Utils::isRemote(f.filename) || forceAbsolutePaths) { - // keep remote URLs as they are + // keep remote URLs or absolute paths as they are assetFilename = f.filename; } else @@ -379,7 +379,7 @@ bool VirtualPointCloud::write(std::string filename) for (size_t i = 0; i < f.overviewFilenames.size(); ++i) { std::string ovFilename(f.overviewFilenames[i]); - if (!pdal::Utils::isRemote(ovFilename)) + if (!pdal::Utils::isRemote(ovFilename) && !forceAbsolutePaths) { const fs::path fRelative = fs::relative(ovFilename, outputPath); ovFilename = "./" + fRelative.string(); @@ -526,6 +526,7 @@ void buildVpc(std::vector args) int max_threads = -1; bool verbose = false; bool help = false; + bool forceAbsolutePaths = false; ProgramArgs programArgs; programArgs.add("help,h", "Output command help.", help); @@ -541,6 +542,7 @@ void buildVpc(std::vector args) pdal::Arg& argThreads = programArgs.add("threads", "Max number of concurrent threads for parallel runs", max_threads); programArgs.add("verbose", "Print extra debugging output", verbose); + programArgs.add("use-absolute-paths", "Store absolute file paths instead of relative paths in the output VPC", forceAbsolutePaths); try { @@ -914,7 +916,7 @@ void buildVpc(std::vector args) } } - vpc.write(outputFile); + vpc.write(outputFile, forceAbsolutePaths); // TODO: for now hoping that all files have the same file type + CRS + point format + scaling // "dataformat_id" diff --git a/src/vpc.hpp b/src/vpc.hpp index 21bf454..eb37c84 100644 --- a/src/vpc.hpp +++ b/src/vpc.hpp @@ -75,7 +75,7 @@ struct VirtualPointCloud void clear(); void dump(); bool read(std::string filename); - bool write(std::string filename); + bool write(std::string filename, bool forceAbsolutePaths = false); point_count_t totalPoints() const; BOX3D box3d() const; diff --git a/tests/test_vpc.py b/tests/test_vpc.py new file mode 100644 index 0000000..5656375 --- /dev/null +++ b/tests/test_vpc.py @@ -0,0 +1,61 @@ +import json +import subprocess +import tempfile +from pathlib import Path + +import pytest +import utils + + +def test_build_vpc_absolute_paths(laz_files): + """Paths stored in VPC use absolute paths when --use-absolute-paths is passed.""" + with tempfile.TemporaryDirectory() as tmp_dir: + output_vpc = Path(tmp_dir) / "out.vpc" + + res = subprocess.run( + [ + utils.pdal_wrench_path(), + "build_vpc", + "--use-absolute-paths", + f"--output={output_vpc.as_posix()}", + *laz_files, + ], + check=True, + ) + + assert res.returncode == 0 + assert output_vpc.exists() + + data = json.loads(output_vpc.read_text()) + assert data["type"] == "FeatureCollection" + + for feature in data["features"]: + for asset in feature["assets"].values(): + href = asset["href"] + assert Path(href).is_absolute(), f"Expected absolute path, got: {href}" + + +def test_build_vpc_relative_paths_default(laz_files): + """Paths stored in VPC are relative by default (no --absolute-paths).""" + with tempfile.TemporaryDirectory() as tmp_dir: + output_vpc = Path(tmp_dir) / "out.vpc" + + res = subprocess.run( + [ + utils.pdal_wrench_path(), + "build_vpc", + f"--output={output_vpc.as_posix()}", + *laz_files, + ], + check=True, + ) + + assert res.returncode == 0 + assert output_vpc.exists() + + data = json.loads(output_vpc.read_text()) + assert data["type"] == "FeatureCollection" + + for feature in data["features"]: + data_href = feature["assets"]["data"]["href"] + assert data_href.startswith("./"), f"Expected relative path starting with ./, got: {data_href}" From 643983ff825ae3914d3fd58baf6d80356fa53a8d Mon Sep 17 00:00:00 2001 From: Jan Caha Date: Mon, 29 Jun 2026 14:02:06 +0200 Subject: [PATCH 02/14] version --- src/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.cpp b/src/main.cpp index 56c0b5b..d8456e2 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -30,7 +30,7 @@ extern int runTile(std::vector arglist); // tile/tile.cpp -std::string WRENCH_VERSION = "1.5.0"; +std::string WRENCH_VERSION = "1.4.1"; void printUsage() { From c2a02fe119cab34e37ee2cbd7ea6d9e26afdd076 Mon Sep 17 00:00:00 2001 From: Jan Caha Date: Tue, 30 Jun 2026 16:40:22 +0200 Subject: [PATCH 03/14] use version from main --- src/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.cpp b/src/main.cpp index d8456e2..56c0b5b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -30,7 +30,7 @@ extern int runTile(std::vector arglist); // tile/tile.cpp -std::string WRENCH_VERSION = "1.4.1"; +std::string WRENCH_VERSION = "1.5.0"; void printUsage() { From c2cf5723d4c0e531317c4e4206d937252b1d404b Mon Sep 17 00:00:00 2001 From: Jan Caha Date: Wed, 1 Jul 2026 06:32:28 +0200 Subject: [PATCH 04/14] if relative path ends with empty string do not create invalid VPC, raise error instead suggesting using absolute paths --- src/vpc.cpp | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/vpc.cpp b/src/vpc.cpp index fbf560f..b679a2d 100644 --- a/src/vpc.cpp +++ b/src/vpc.cpp @@ -297,6 +297,15 @@ bool VirtualPointCloud::write(std::string filename, bool forceAbsolutePaths) { // turn local paths to relative fs::path fRelative = fs::relative(f.filename, outputPath); + + if (fRelative.empty()) { + std::cerr << "failed to make filename relative to output path: " + << f.filename + << " consider using --use-absolute-paths" + << std::endl; + return false; + } + assetFilename = "./" + fRelative.string(); } std::string fileId = fs::path(f.filename).stem().string(); // TODO: we should make sure the ID is unique @@ -382,6 +391,16 @@ bool VirtualPointCloud::write(std::string filename, bool forceAbsolutePaths) if (!pdal::Utils::isRemote(ovFilename) && !forceAbsolutePaths) { const fs::path fRelative = fs::relative(ovFilename, outputPath); + + if (fRelative.empty()) + { + std::cerr << "failed to make overview filename relative to output path: " + << ovFilename + << " consider using --use-absolute-paths" + << std::endl; + return false; + } + ovFilename = "./" + fRelative.string(); } const std::string key = f.overviewFilenames.size() > 1 ? ("overview-" + std::to_string(i + 1)) : "overview"; From aa35aba3668a0b00ffa6f92fc678c1d188b155c9 Mon Sep 17 00:00:00 2001 From: Jan Caha Date: Wed, 1 Jul 2026 10:13:12 +0200 Subject: [PATCH 05/14] check if absolute paths are required due to the input files setup --- src/vpc.cpp | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/vpc.cpp b/src/vpc.cpp index b679a2d..5ef4114 100644 --- a/src/vpc.cpp +++ b/src/vpc.cpp @@ -284,6 +284,19 @@ bool VirtualPointCloud::write(std::string filename, bool forceAbsolutePaths) fs::path outputPath = fs::path(filenameAbsolute).parent_path(); + bool forceAbsolutePaths = false; + for (const File &f : files) { + fs::path fRelative = fs::relative(f.filename, outputPath); + if (fRelative.empty()) { + forceAbsolutePaths = true; + std::cerr << "failed to make filename relative to output path: " + << f.filename + << " using absolute paths in the output VPC file" + << std::endl; + break; + } + } + std::vector jFiles; for ( const File &f : files ) { From 59ac30015f51e02f62812c5b9ea6d144de7279a4 Mon Sep 17 00:00:00 2001 From: Jan Caha Date: Wed, 1 Jul 2026 10:16:19 +0200 Subject: [PATCH 06/14] remove parameter from write --- src/vpc.cpp | 4 ++-- src/vpc.hpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vpc.cpp b/src/vpc.cpp index 5ef4114..ebd3ce2 100644 --- a/src/vpc.cpp +++ b/src/vpc.cpp @@ -271,7 +271,7 @@ void geometryToJson(const Geometry &geom, const BOX3D &bbox, nlohmann::json &jso } } -bool VirtualPointCloud::write(std::string filename, bool forceAbsolutePaths) +bool VirtualPointCloud::write(std::string filename) { if (!isVpcFilename(filename)) filename += ".vpz"; @@ -948,7 +948,7 @@ void buildVpc(std::vector args) } } - vpc.write(outputFile, forceAbsolutePaths); + vpc.write(outputFile); // TODO: for now hoping that all files have the same file type + CRS + point format + scaling // "dataformat_id" diff --git a/src/vpc.hpp b/src/vpc.hpp index eb37c84..21bf454 100644 --- a/src/vpc.hpp +++ b/src/vpc.hpp @@ -75,7 +75,7 @@ struct VirtualPointCloud void clear(); void dump(); bool read(std::string filename); - bool write(std::string filename, bool forceAbsolutePaths = false); + bool write(std::string filename); point_count_t totalPoints() const; BOX3D box3d() const; From 620fd586d93f844391ec4a24528abb90d9657255 Mon Sep 17 00:00:00 2001 From: Jan Caha Date: Wed, 1 Jul 2026 10:16:29 +0200 Subject: [PATCH 07/14] drop argument --- src/vpc.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/vpc.cpp b/src/vpc.cpp index ebd3ce2..898e291 100644 --- a/src/vpc.cpp +++ b/src/vpc.cpp @@ -574,7 +574,6 @@ void buildVpc(std::vector args) pdal::Arg& argThreads = programArgs.add("threads", "Max number of concurrent threads for parallel runs", max_threads); programArgs.add("verbose", "Print extra debugging output", verbose); - programArgs.add("use-absolute-paths", "Store absolute file paths instead of relative paths in the output VPC", forceAbsolutePaths); try { From 5b4dafbe7e300e8c6e6898a98e084a7ef2638f1f Mon Sep 17 00:00:00 2001 From: Jan Caha Date: Wed, 1 Jul 2026 10:19:11 +0200 Subject: [PATCH 08/14] drop variable --- src/vpc.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/vpc.cpp b/src/vpc.cpp index 898e291..65bd33c 100644 --- a/src/vpc.cpp +++ b/src/vpc.cpp @@ -558,7 +558,6 @@ void buildVpc(std::vector args) int max_threads = -1; bool verbose = false; bool help = false; - bool forceAbsolutePaths = false; ProgramArgs programArgs; programArgs.add("help,h", "Output command help.", help); From 844324b3e9050b0eb866a0c868ac36f71eae290f Mon Sep 17 00:00:00 2001 From: Jan Caha Date: Wed, 1 Jul 2026 10:38:44 +0200 Subject: [PATCH 09/14] drop test --- tests/test_vpc.py | 28 ---------------------------- 1 file changed, 28 deletions(-) diff --git a/tests/test_vpc.py b/tests/test_vpc.py index 5656375..bfeaeed 100644 --- a/tests/test_vpc.py +++ b/tests/test_vpc.py @@ -7,34 +7,6 @@ import utils -def test_build_vpc_absolute_paths(laz_files): - """Paths stored in VPC use absolute paths when --use-absolute-paths is passed.""" - with tempfile.TemporaryDirectory() as tmp_dir: - output_vpc = Path(tmp_dir) / "out.vpc" - - res = subprocess.run( - [ - utils.pdal_wrench_path(), - "build_vpc", - "--use-absolute-paths", - f"--output={output_vpc.as_posix()}", - *laz_files, - ], - check=True, - ) - - assert res.returncode == 0 - assert output_vpc.exists() - - data = json.loads(output_vpc.read_text()) - assert data["type"] == "FeatureCollection" - - for feature in data["features"]: - for asset in feature["assets"].values(): - href = asset["href"] - assert Path(href).is_absolute(), f"Expected absolute path, got: {href}" - - def test_build_vpc_relative_paths_default(laz_files): """Paths stored in VPC are relative by default (no --absolute-paths).""" with tempfile.TemporaryDirectory() as tmp_dir: From 7cf2348553b31f90128467268938bdd24f7b526d Mon Sep 17 00:00:00 2001 From: Jan Caha Date: Wed, 1 Jul 2026 12:20:46 +0200 Subject: [PATCH 10/14] check also remote files --- src/vpc.cpp | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/src/vpc.cpp b/src/vpc.cpp index 65bd33c..0f767c3 100644 --- a/src/vpc.cpp +++ b/src/vpc.cpp @@ -285,7 +285,8 @@ bool VirtualPointCloud::write(std::string filename) fs::path outputPath = fs::path(filenameAbsolute).parent_path(); bool forceAbsolutePaths = false; - for (const File &f : files) { + for (const File &f : files) + { fs::path fRelative = fs::relative(f.filename, outputPath); if (fRelative.empty()) { forceAbsolutePaths = true; @@ -295,6 +296,24 @@ bool VirtualPointCloud::write(std::string filename) << std::endl; break; } + + for (size_t i = 0; i < f.overviewFilenames.size(); ++i) + { + std::string ovFilename(f.overviewFilenames[i]); + if (!pdal::Utils::isRemote(ovFilename)) + { + const fs::path fRelative = fs::relative(ovFilename, outputPath); + if (fRelative.empty()) + { + forceAbsolutePaths = true; + std::cerr << "failed to make overview filename relative to output path: " + << ovFilename + << " using absolute paths in the output VPC file" + << std::endl; + break; + } + } + } } std::vector jFiles; @@ -310,15 +329,6 @@ bool VirtualPointCloud::write(std::string filename) { // turn local paths to relative fs::path fRelative = fs::relative(f.filename, outputPath); - - if (fRelative.empty()) { - std::cerr << "failed to make filename relative to output path: " - << f.filename - << " consider using --use-absolute-paths" - << std::endl; - return false; - } - assetFilename = "./" + fRelative.string(); } std::string fileId = fs::path(f.filename).stem().string(); // TODO: we should make sure the ID is unique @@ -404,16 +414,6 @@ bool VirtualPointCloud::write(std::string filename) if (!pdal::Utils::isRemote(ovFilename) && !forceAbsolutePaths) { const fs::path fRelative = fs::relative(ovFilename, outputPath); - - if (fRelative.empty()) - { - std::cerr << "failed to make overview filename relative to output path: " - << ovFilename - << " consider using --use-absolute-paths" - << std::endl; - return false; - } - ovFilename = "./" + fRelative.string(); } const std::string key = f.overviewFilenames.size() > 1 ? ("overview-" + std::to_string(i + 1)) : "overview"; From 62d957a2f64160bad1d6c9f28c081fa7c0c6d5a7 Mon Sep 17 00:00:00 2001 From: Jan Caha Date: Wed, 1 Jul 2026 12:25:29 +0200 Subject: [PATCH 11/14] only use local paths locally in the write --- src/vpc.cpp | 6 ++---- src/vpc.hpp | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/vpc.cpp b/src/vpc.cpp index 9451297..89fa3e8 100644 --- a/src/vpc.cpp +++ b/src/vpc.cpp @@ -306,7 +306,7 @@ void geometryToJson(const Geometry &geom, const BOX3D &bbox, nlohmann::json &jso } } -bool VirtualPointCloud::write(std::string filename, bool forceAbsolutePaths) +bool VirtualPointCloud::write(std::string filename) { if (!isVpcFilename(filename)) filename += ".vpz"; @@ -612,7 +612,6 @@ void buildVpc(std::vector args) int max_threads = -1; bool verbose = false; bool help = false; - bool forceAbsolutePaths = false; ProgramArgs programArgs; programArgs.add("help,h", "Output command help.", help); @@ -628,7 +627,6 @@ void buildVpc(std::vector args) pdal::Arg& argThreads = programArgs.add("threads", "Max number of concurrent threads for parallel runs", max_threads); programArgs.add("verbose", "Print extra debugging output", verbose); - programArgs.add("use-absolute-paths", "Store absolute file paths instead of relative paths in the output VPC", forceAbsolutePaths); try { @@ -1002,7 +1000,7 @@ void buildVpc(std::vector args) } } - vpc.write(outputFile, forceAbsolutePaths); + vpc.write(outputFile); // TODO: for now hoping that all files have the same file type + CRS + point format + scaling // "dataformat_id" diff --git a/src/vpc.hpp b/src/vpc.hpp index fd2a070..7ca2549 100644 --- a/src/vpc.hpp +++ b/src/vpc.hpp @@ -78,7 +78,7 @@ struct VirtualPointCloud void clear(); void dump(); bool read(std::string filename); - bool write(std::string filename, bool forceAbsolutePaths = false); + bool write(std::string filename); point_count_t totalPoints() const; BOX3D box3d() const; From 3ef3ad4ccb8b0bae067c2ca069b290c40b448d3a Mon Sep 17 00:00:00 2001 From: Jan Caha Date: Wed, 1 Jul 2026 12:26:04 +0200 Subject: [PATCH 12/14] fix file link --- tests/test_clip.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_clip.py b/tests/test_clip.py index 8e78f04..56b2a7c 100644 --- a/tests/test_clip.py +++ b/tests/test_clip.py @@ -51,7 +51,7 @@ def test_input_file_output_file( (utils.test_data_filepath("data_copc.vpc"), utils.test_data_filepath("clipped-vpc-copc-files.copc.laz"), 66911), (utils.test_data_filepath("data_copc.vpz"), utils.test_data_filepath("clipped-vpz-copc-files.vpc"), 66911), (utils.test_data_filepath("data_copc.vpz"), utils.test_data_filepath("clipped-vpz-copc-files.copc.laz"), 66911), - ("https://raw.githubusercontent.com/PDAL/wrench/f4b156c5081dd9a1d44fccfdb67f2c36e91e3566/tests/data/stadium.vpc", utils.test_data_filepath("clipped-vpz-copc-files.copc.laz"), 66905), + ("https://raw.githubusercontent.com/PDAL/wrench/refs/heads/main/tests/data/stadium.vpc", utils.test_data_filepath("clipped-vpz-copc-files.copc.laz"), 66905), ], ) def test_clip_vpc( From 2cf0af6fd206b003ca23850e2c34340fe58c91fa Mon Sep 17 00:00:00 2001 From: Jan Caha Date: Wed, 1 Jul 2026 12:27:43 +0200 Subject: [PATCH 13/14] warning --- src/vpc.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vpc.cpp b/src/vpc.cpp index 89fa3e8..fa8c240 100644 --- a/src/vpc.cpp +++ b/src/vpc.cpp @@ -325,7 +325,7 @@ bool VirtualPointCloud::write(std::string filename) fs::path fRelative = fs::relative(f.filename, outputPath); if (fRelative.empty()) { forceAbsolutePaths = true; - std::cerr << "failed to make filename relative to output path: " + std::cerr << "Warning: failed to make filename relative to output path: " << f.filename << " using absolute paths in the output VPC file" << std::endl; @@ -341,7 +341,7 @@ bool VirtualPointCloud::write(std::string filename) if (fRelative.empty()) { forceAbsolutePaths = true; - std::cerr << "failed to make overview filename relative to output path: " + std::cerr << "Warning: failed to make overview filename relative to output path: " << ovFilename << " using absolute paths in the output VPC file" << std::endl; From 78b87dd5ff088ac6a6447d5ae01173bf5e63130c Mon Sep 17 00:00:00 2001 From: Jan Caha Date: Wed, 1 Jul 2026 12:45:29 +0200 Subject: [PATCH 14/14] fix messages --- src/vpc.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vpc.cpp b/src/vpc.cpp index fa8c240..3717784 100644 --- a/src/vpc.cpp +++ b/src/vpc.cpp @@ -327,7 +327,7 @@ bool VirtualPointCloud::write(std::string filename) forceAbsolutePaths = true; std::cerr << "Warning: failed to make filename relative to output path: " << f.filename - << " using absolute paths in the output VPC file" + << " ; using absolute paths in the output VPC file" << std::endl; break; } @@ -343,7 +343,7 @@ bool VirtualPointCloud::write(std::string filename) forceAbsolutePaths = true; std::cerr << "Warning: failed to make overview filename relative to output path: " << ovFilename - << " using absolute paths in the output VPC file" + << " ; using absolute paths in the output VPC file" << std::endl; break; }