From 23a46e6b3126b7e6a70ee63cf6f946204743d32b Mon Sep 17 00:00:00 2001 From: uclaros Date: Sat, 7 Mar 2026 00:10:15 +0200 Subject: [PATCH 1/3] Add .vpz (zipped VPC) support - Add `isVpcFilename()` helper in utils.hpp matching .vpc and .vpz and replace all `ends_with(x, ".vpc")` checks - VirtualPointCloud::read() transparently reads .vpz (ZIP containing a single .vpc file) - VirtualPointCloud::write() writes .vpz when the extension is .vpz, and auto-appends .vpc when no extension is provided - Add tests for creating and using .vpz file - In Merge algorithm, check all input files for vpc content --- CMakeLists.txt | 2 + src/alg.cpp | 4 +- src/boundary.cpp | 2 +- src/classify_ground.cpp | 6 +- src/clip.cpp | 4 +- src/compare.cpp | 4 +- src/density.cpp | 2 +- src/filter_noise.cpp | 6 +- src/height_above_ground.cpp | 4 +- src/info.cpp | 2 +- src/merge.cpp | 4 +- src/thin.cpp | 6 +- src/tile/tile.cpp | 4 +- src/to_raster.cpp | 2 +- src/to_raster_tin.cpp | 2 +- src/to_vector.cpp | 4 +- src/translate.cpp | 4 +- src/utils.cpp | 6 +- src/utils.hpp | 5 ++ src/vpc.cpp | 153 +++++++++++++++++++++++++++++------- tests/conftest.py | 38 +++++++++ tests/test_boundary.py | 2 + tests/test_clip.py | 2 + tests/test_merge.py | 5 ++ tests/test_thin.py | 1 + tests/test_to_vector.py | 1 + tests/test_translate.py | 10 +++ 27 files changed, 226 insertions(+), 59 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 259974e..9355a85 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,6 +11,7 @@ set (PEDANTIC TRUE CACHE BOOL "Determines if we should compile in pedantic mode. find_package(PDAL REQUIRED) find_package(GDAL REQUIRED) find_package(Threads REQUIRED) +find_package(libzip REQUIRED) add_executable(pdal_wrench src/main.cpp @@ -55,6 +56,7 @@ target_link_libraries(pdal_wrench ${PDAL_LIBRARIES} ${GDAL_LIBRARY} ${CMAKE_THREAD_LIBS_INIT} + libzip::zip ) install(TARGETS pdal_wrench DESTINATION bin) diff --git a/src/alg.cpp b/src/alg.cpp index 7da6396..ca6bb7e 100644 --- a/src/alg.cpp +++ b/src/alg.cpp @@ -39,7 +39,7 @@ bool runAlg(std::vector args, Alg &alg) if (alg.hasSingleInput) { - if (ends_with(alg.inputFile, ".vpc")) + if (isVpcFilename(alg.inputFile)) { VirtualPointCloud vpc; if (!vpc.read(alg.inputFile)) @@ -184,4 +184,4 @@ fs::path fileStem(const std::string &filename) } return inputBasename; -} \ No newline at end of file +} diff --git a/src/boundary.cpp b/src/boundary.cpp index adcce0e..039a96d 100644 --- a/src/boundary.cpp +++ b/src/boundary.cpp @@ -111,7 +111,7 @@ static std::unique_ptr pipeline(ParallelJobInfo *tile, double r void Boundary::preparePipelines(std::vector>& pipelines) { - if (ends_with(inputFile, ".vpc")) + if (isVpcFilename(inputFile)) { // VPC handling VirtualPointCloud vpc; diff --git a/src/classify_ground.cpp b/src/classify_ground.cpp index 6bae1af..b0590fd 100644 --- a/src/classify_ground.cpp +++ b/src/classify_ground.cpp @@ -61,7 +61,7 @@ bool ClassifyGround::checkArgs() } } - if ( ends_with(outputFile, ".vpc") && outputFormatVpc == "copc" ) + if ( isVpcFilename(outputFile) && outputFormatVpc == "copc" ) { isStreaming = false; } @@ -120,7 +120,7 @@ void ClassifyGround::preparePipelines(std::vector>&) return; buildOutput(outputFile, tileOutputFiles); -} \ No newline at end of file +} diff --git a/src/clip.cpp b/src/clip.cpp index 3e73276..c6b1231 100644 --- a/src/clip.cpp +++ b/src/clip.cpp @@ -60,7 +60,7 @@ bool Clip::checkArgs() } } - if ( ends_with(outputFile, ".vpc") && outputFormatVpc == "copc" ) + if ( isVpcFilename(outputFile) && outputFormatVpc == "copc" ) { isStreaming = false; } @@ -162,7 +162,7 @@ void Clip::preparePipelines(std::vector>& pipel if (!loadPolygons(polygonFile, crop_opts, bbox)) return; - if (ends_with(inputFile, ".vpc")) + if (isVpcFilename(inputFile)) { // for /tmp/hello.vpc we will use /tmp/hello dir for all results fs::path outputParentDir = fs::path(outputFile).parent_path(); diff --git a/src/compare.cpp b/src/compare.cpp index aca89d4..2915b30 100644 --- a/src/compare.cpp +++ b/src/compare.cpp @@ -45,12 +45,12 @@ void ComparePointClouds::addArgs() bool ComparePointClouds::checkArgs() { - if (ends_with(inputFile, ".vpc")) { + if (isVpcFilename(inputFile)) { std::cerr << "input cannot be a VPC file" << std::endl; return false; } - if (ends_with(comparedInputFile, ".vpc")) { + if (isVpcFilename(comparedInputFile)) { std::cerr << "compared input cannot be a VPC file" << std::endl; return false; } diff --git a/src/density.cpp b/src/density.cpp index 9925972..944c09a 100644 --- a/src/density.cpp +++ b/src/density.cpp @@ -156,7 +156,7 @@ std::unique_ptr Density::pipeline(ParallelJobInfo *tile) const void Density::preparePipelines(std::vector>& pipelines) { - if (ends_with(inputFile, ".vpc")) + if (isVpcFilename(inputFile)) { // using spatial processing diff --git a/src/filter_noise.cpp b/src/filter_noise.cpp index 6ac4b95..229b42a 100644 --- a/src/filter_noise.cpp +++ b/src/filter_noise.cpp @@ -65,7 +65,7 @@ bool FilterNoise::checkArgs() } } - if ( ends_with(outputFile, ".vpc") && outputFormatVpc == "copc" ) + if ( isVpcFilename(outputFile) && outputFormatVpc == "copc" ) { isStreaming = false; } @@ -163,7 +163,7 @@ void FilterNoise::preparePipelines(std::vector> noiseFilterOptions.add(pdal::Option("multiplier", statisticalMultiplier)); } - if (ends_with(inputFile, ".vpc")) + if (isVpcFilename(inputFile)) { // for /tmp/hello.vpc we will use /tmp/hello dir for all results fs::path outputParentDir = fs::path(outputFile).parent_path(); @@ -203,4 +203,4 @@ void FilterNoise::finalize(std::vector>&) return; buildOutput(outputFile, tileOutputFiles); -} \ No newline at end of file +} diff --git a/src/height_above_ground.cpp b/src/height_above_ground.cpp index 07619eb..d02e04a 100644 --- a/src/height_above_ground.cpp +++ b/src/height_above_ground.cpp @@ -64,7 +64,7 @@ bool HeightAboveGround::checkArgs() } } - if ( ends_with(outputFile, ".vpc") && outputFormatVpc == "copc" ) + if ( isVpcFilename(outputFile) && outputFormatVpc == "copc" ) { isStreaming = false; } @@ -179,7 +179,7 @@ static std::unique_ptr pipeline(ParallelJobInfo *tile, std::str void HeightAboveGround::preparePipelines(std::vector>& pipelines) { - if (ends_with(inputFile, ".vpc")) + if (isVpcFilename(inputFile)) { // for /tmp/hello.vpc we will use /tmp/hello dir for all results fs::path outputParentDir = fs::path(outputFile).parent_path(); diff --git a/src/info.cpp b/src/info.cpp index 4b8529e..a813d6e 100644 --- a/src/info.cpp +++ b/src/info.cpp @@ -103,7 +103,7 @@ static void formatCrsInfo(const std::string &crsWkt, std::string &crs, std::stri void Info::preparePipelines(std::vector>&) { - if (ends_with(inputFile, ".vpc")) + if (isVpcFilename(inputFile)) { VirtualPointCloud vpc; if (!vpc.read(inputFile)) diff --git a/src/merge.cpp b/src/merge.cpp index b4aa033..b13f4e0 100644 --- a/src/merge.cpp +++ b/src/merge.cpp @@ -123,6 +123,8 @@ void Merge::preparePipelines(std::vector>& pipe { ParallelJobInfo tile(ParallelJobInfo::Single, BOX2D(), filterExpression, filterBounds); std::vector inputFilesToProcess; + // move any input files to inputFilesToProcess, so they go through processInputFile() + std::swap(inputFilesToProcess, inputFiles); if (!inputFileList.empty()) { std::ifstream inputFile(inputFileList); @@ -143,7 +145,7 @@ void Merge::preparePipelines(std::vector>& pipe std::function processInputFile; processInputFile = [&processInputFile,this](const std::string& inputFile) { - if (ends_with(inputFile, ".vpc")) + if (isVpcFilename(inputFile)) { VirtualPointCloud vpc; if (!vpc.read(inputFile)) diff --git a/src/thin.cpp b/src/thin.cpp index 3476c57..2b06f09 100644 --- a/src/thin.cpp +++ b/src/thin.cpp @@ -87,7 +87,7 @@ bool Thin::checkArgs() } } - if ( ends_with(outputFile, ".vpc") && outputFormatVpc == "copc" ) + if ( isVpcFilename(outputFile) && outputFormatVpc == "copc" ) { isStreaming = false; } @@ -149,7 +149,7 @@ static std::unique_ptr pipeline(ParallelJobInfo *tile, std::str void Thin::preparePipelines(std::vector>& pipelines) { - if (ends_with(inputFile, ".vpc")) + if (isVpcFilename(inputFile)) { // for /tmp/hello.vpc we will use /tmp/hello dir for all results fs::path outputParentDir = fs::path(outputFile).parent_path(); @@ -197,7 +197,7 @@ void Thin::finalize(std::vector>&) for (std::string f : tileOutputFiles) args.push_back(f); - if (ends_with(outputFile, ".vpc")) + if (isVpcFilename(outputFile)) { // now build a new output VPC buildVpc(args); diff --git a/src/tile/tile.cpp b/src/tile/tile.cpp index 7801534..9190d17 100644 --- a/src/tile/tile.cpp +++ b/src/tile/tile.cpp @@ -173,7 +173,7 @@ static PointCount createFileInfo(const StringList& input, StringList dimNames, std::vector dirfiles = directoryList(filename); filenames.insert(filenames.end(), dirfiles.begin(), dirfiles.end()); } - else if (ends_with(filename, ".vpc")) + else if (isVpcFilename(filename)) { VirtualPointCloud vpc; if (!vpc.read(filename)) @@ -474,7 +474,7 @@ bool handleOptions(pdal::StringList& arglist, BaseInfo::Options& options) throw FatalError(err.what()); } - if (ends_with(options.outputDir, ".vpc")) + if (isVpcFilename(options.outputDir)) { options.outputDir = options.outputDir.substr(0, options.outputDir.size()-4); options.buildVpc = true; diff --git a/src/to_raster.cpp b/src/to_raster.cpp index 3467a55..cec08db 100644 --- a/src/to_raster.cpp +++ b/src/to_raster.cpp @@ -180,7 +180,7 @@ static std::unique_ptr pipeline(ParallelJobInfo *tile, double r void ToRaster::preparePipelines(std::vector>& pipelines) { - if (ends_with(inputFile, ".vpc")) + if (isVpcFilename(inputFile)) { // using spatial processing diff --git a/src/to_raster_tin.cpp b/src/to_raster_tin.cpp index 0bface7..142ad78 100644 --- a/src/to_raster_tin.cpp +++ b/src/to_raster_tin.cpp @@ -194,7 +194,7 @@ std::unique_ptr pipeline(ParallelJobInfo *tile, double resoluti void ToRasterTin::preparePipelines(std::vector>& pipelines) { - if (ends_with(inputFile, ".vpc")) + if (isVpcFilename(inputFile)) { // using spatial processing diff --git a/src/to_vector.cpp b/src/to_vector.cpp index 6ba0494..7f803b6 100644 --- a/src/to_vector.cpp +++ b/src/to_vector.cpp @@ -93,7 +93,7 @@ static std::unique_ptr pipeline(ParallelJobInfo *tile, const st void ToVector::preparePipelines(std::vector>& pipelines) { - if (ends_with(inputFile, ".vpc")) + if (isVpcFilename(inputFile)) { // for /tmp/hello.vpc we will use /tmp/hello dir for all results fs::path outputParentDir = fs::path(outputFile).parent_path(); @@ -134,7 +134,7 @@ void ToVector::finalize(std::vector>&) if (tileOutputFiles.empty()) return; - if (ends_with(inputFile, ".vpc")) + if (isVpcFilename(inputFile)) { // for /tmp/hello.vpc we will use /tmp/hello dir for all results fs::path outputParentDir = fs::path(outputFile).parent_path(); diff --git a/src/translate.cpp b/src/translate.cpp index 953396f..688404a 100644 --- a/src/translate.cpp +++ b/src/translate.cpp @@ -63,7 +63,7 @@ bool Translate::checkArgs() } } - if ( ends_with(outputFile, ".vpc") && outputFormatVpc == "copc" ) + if ( isVpcFilename(outputFile) && outputFormatVpc == "copc" ) { isStreaming = false; } @@ -164,7 +164,7 @@ static std::unique_ptr pipeline(ParallelJobInfo *tile, std::str void Translate::preparePipelines(std::vector>& pipelines) { - if (ends_with(inputFile, ".vpc")) + if (isVpcFilename(inputFile)) { // for /tmp/hello.vpc we will use /tmp/hello dir for all results fs::path outputParentDir = fs::path(outputFile).parent_path(); diff --git a/src/utils.cpp b/src/utils.cpp index 92ce54c..c676c32 100644 --- a/src/utils.cpp +++ b/src/utils.cpp @@ -335,7 +335,7 @@ void buildOutput(std::string outputFile, std::vector &tileOutputFil for (std::string f : tileOutputFiles) args.push_back(f); - if (ends_with(outputFile, ".vpc")) + if (isVpcFilename(outputFile)) { // now build a new output VPC buildVpc(args); @@ -368,7 +368,7 @@ std::string tileOutputFileName(const std::string &outputFile, const std::string // output is not a VPC, it will be merged later into a single file, // use las to avoid zipping the file, it will be removed after merging - if (!ends_with(outputFile, ".vpc")) + if (!isVpcFilename(outputFile)) { return fullFileNameWithoutExt + ".las"; } @@ -383,4 +383,4 @@ std::string tileOutputFileName(const std::string &outputFile, const std::string } return fullFileNameWithoutExt + "." + ext; -} \ No newline at end of file +} diff --git a/src/utils.hpp b/src/utils.hpp index e568b45..3849000 100644 --- a/src/utils.hpp +++ b/src/utils.hpp @@ -246,6 +246,11 @@ inline bool ends_with(std::string const & value, std::string const & ending) return std::equal(ending.rbegin(), ending.rend(), value.rbegin()); } +inline bool isVpcFilename(const std::string& filename) +{ + return ends_with(filename, ".vpc") || ends_with(filename, ".vpz"); +} + inline std::string join_strings(const std::vector& list, char delimiter) { diff --git a/src/vpc.cpp b/src/vpc.cpp index dd78477..e9778b9 100644 --- a/src/vpc.cpp +++ b/src/vpc.cpp @@ -24,6 +24,7 @@ namespace fs = std::filesystem; #include #include "nlohmann/json.hpp" +#include using json = nlohmann::json; @@ -54,24 +55,87 @@ bool VirtualPointCloud::read(std::string filename) { clear(); - std::ifstream inputJson(filename); - if (!inputJson.good()) - { - std::cerr << "Failed to read input VPC file: " << filename << std::endl; - return false; - } - fs::path filenameParent = fs::path(filename).parent_path(); json data; - try + + if (ends_with(filename, ".vpz")) { - data = json::parse(inputJson); + int err = 0; + zip_t *za = zip_open(filename.c_str(), ZIP_RDONLY, &err); + if (!za) + { + std::cerr << "Failed to open VPZ file: " << filename << std::endl; + return false; + } + + // find the single .vpc entry + const zip_int64_t numEntries = zip_get_num_entries(za, 0); + zip_int64_t vpcIndex = -1; + for (zip_int64_t i = 0; i < numEntries; ++i) + { + const char *name = zip_get_name(za, i, 0); + if (name && ends_with(std::string(name), ".vpc")) + { + if (vpcIndex != -1) + { + std::cerr << "VPZ file contains more than one .vpc entry: " << filename << std::endl; + zip_close(za); + return false; + } + vpcIndex = i; + } + } + if (vpcIndex == -1) + { + std::cerr << "VPZ file contains no .vpc entry: " << filename << std::endl; + zip_close(za); + return false; + } + + zip_stat_t st; + zip_stat_index(za, vpcIndex, 0, &st); + zip_file_t *zf = zip_fopen_index(za, vpcIndex, 0); + if (!zf) + { + std::cerr << "Failed to open .vpc entry inside VPZ: " << filename << std::endl; + zip_close(za); + return false; + } + + std::string content(st.size, '\0'); + zip_fread(zf, &content[0], st.size); + zip_fclose(zf); + zip_close(za); + + try + { + data = json::parse(content); + } + catch (std::exception &e) + { + std::cerr << "JSON parsing error: " << e.what() << std::endl; + return false; + } } - catch (std::exception &e) + else { - std::cerr << "JSON parsing error: " << e.what() << std::endl; - return false; + std::ifstream inputJson(filename); + if (!inputJson.good()) + { + std::cerr << "Failed to read input VPC file: " << filename << std::endl; + return false; + } + + try + { + data = json::parse(inputJson); + } + catch (std::exception &e) + { + std::cerr << "JSON parsing error: " << e.what() << std::endl; + return false; + } } if (data["type"] != "FeatureCollection") @@ -204,19 +268,15 @@ void geometryToJson(const Geometry &geom, const BOX3D &bbox, nlohmann::json &jso bool VirtualPointCloud::write(std::string filename) { + if (!isVpcFilename(filename)) + filename += ".vpz"; + std::string filenameAbsolute = filename; if (!fs::path(filename).is_absolute()) { filenameAbsolute = fs::absolute(filename).string(); } - std::ofstream outputJson(filenameAbsolute); - if (!outputJson.good()) - { - std::cerr << "Failed to create file: " << filenameAbsolute << std::endl; - return false; - } - fs::path outputPath = fs::path(filenameAbsolute).parent_path(); std::vector jFiles; @@ -356,8 +416,53 @@ bool VirtualPointCloud::write(std::string filename) nlohmann::ordered_json j = { { "type", "FeatureCollection" }, { "features", jFiles } }; - outputJson << std::setw(2) << j << std::endl; - outputJson.close(); + if (ends_with(filenameAbsolute, ".vpz")) + { + const std::string content = j.dump() + "\n"; + const std::string entryName = fs::path(filenameAbsolute).stem().string() + ".vpc"; + + int err = 0; + zip_t *za = zip_open(filenameAbsolute.c_str(), ZIP_CREATE | ZIP_TRUNCATE, &err); + if (!za) + { + std::cerr << "Failed to create VPZ file: " << filenameAbsolute << std::endl; + return false; + } + + zip_source_t *source = zip_source_buffer(za, content.c_str(), content.size(), 0); + if (!source) + { + std::cerr << "Failed to create zip source buffer" << std::endl; + zip_discard(za); + return false; + } + + if (zip_file_add(za, entryName.c_str(), source, ZIP_FL_OVERWRITE) < 0) + { + std::cerr << "Failed to add .vpc entry to VPZ: " << zip_strerror(za) << std::endl; + zip_source_free(source); + zip_discard(za); + return false; + } + + if (zip_close(za) != 0) + { + std::cerr << "Failed to write VPZ file: " << filenameAbsolute << std::endl; + return false; + } + } + else + { + std::ofstream outputJson(filenameAbsolute); + if (!outputJson.good()) + { + std::cerr << "Failed to create file: " << filenameAbsolute << std::endl; + return false; + } + + outputJson << j << std::endl; + outputJson.close(); + } return true; } @@ -681,14 +786,8 @@ void buildVpc(std::vector args) } } - // - - vpc.dump(); - vpc.write(outputFile); - vpc.read(outputFile); - // TODO: for now hoping that all files have the same file type + CRS + point format + scaling // "dataformat_id" // "spatialreference" diff --git a/tests/conftest.py b/tests/conftest.py index 97f8a31..4ef13d8 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,5 +1,8 @@ import subprocess +import tempfile import typing +import zipfile +from pathlib import Path import pdal import pytest @@ -121,6 +124,7 @@ def _prepare_data(): assert clipped_copc_file_hag.exists() + # build vpc file with las files vpc_file = utils.test_data_filepath("data.vpc") if not vpc_file.exists(): @@ -144,6 +148,7 @@ def _prepare_data(): assert number_points == 338163 + # build vpc file with copc files vpc_copc_file = utils.test_data_filepath("data_copc.vpc") if not vpc_copc_file.exists(): @@ -167,6 +172,39 @@ def _prepare_data(): assert number_points == 338163 + # build vpz file with copc files + vpz_copc_file = utils.test_data_filepath("data_copc.vpz") + + if not vpz_copc_file.exists(): + res = subprocess.run( + [ + utils.pdal_wrench_path(), + "build_vpc", + "--output", + vpz_copc_file.as_posix(), + *[f.as_posix() for f in files_for_vpc_copc], + ], + check=True, + ) + + assert res.returncode == 0 + + assert vpz_copc_file.exists() + + assert zipfile.is_zipfile(vpz_copc_file.as_posix()) + + with zipfile.ZipFile(vpz_copc_file.as_posix()) as zf: + with tempfile.TemporaryDirectory() as temp_dir: + zf.extractall(temp_dir) + + assert (Path(temp_dir) / vpz_copc_file).exists() + + vpc_copc = pdal.Reader(vpc_copc_file.as_posix()).pipeline() + number_points = vpc_copc.execute() + + assert number_points == 338163 + + # build translated copc file base_copc_data = utils.test_data_filepath("stadium-utm.copc.laz") if not base_copc_data.exists(): diff --git a/tests/test_boundary.py b/tests/test_boundary.py index ca5b6b1..5cdd075 100644 --- a/tests/test_boundary.py +++ b/tests/test_boundary.py @@ -11,6 +11,8 @@ (utils.test_data_filepath("stadium-utm.laz"), utils.test_data_filepath("boundary-laz.gpkg")), (utils.test_data_filepath("stadium-utm.copc.laz"), utils.test_data_filepath("boundary_copc.gpkg")), (utils.test_data_filepath("data.vpc"), utils.test_data_filepath("boundary-vpc.gpkg")), + (utils.test_data_filepath("data_copc.vpc"), utils.test_data_filepath("boundary-vpc.gpkg")), + (utils.test_data_filepath("data_copc.vpz"), utils.test_data_filepath("boundary-vpc.gpkg")), ], ) def test_boundary(input_path: Path, output_path: Path): diff --git a/tests/test_clip.py b/tests/test_clip.py index 78a1cfb..29962f2 100644 --- a/tests/test_clip.py +++ b/tests/test_clip.py @@ -49,6 +49,8 @@ def test_input_file_output_file( (utils.test_data_filepath("data.vpc"), utils.test_data_filepath("clipped-vpc.copc.laz")), (utils.test_data_filepath("data_copc.vpc"), utils.test_data_filepath("clipped-vpc-copc-files.vpc")), (utils.test_data_filepath("data_copc.vpc"), utils.test_data_filepath("clipped-vpc-copc-files.copc.laz")), + (utils.test_data_filepath("data_copc.vpz"), utils.test_data_filepath("clipped-vpc-copc-files.vpc")), + (utils.test_data_filepath("data_copc.vpz"), utils.test_data_filepath("clipped-vpc-copc-files.copc.laz")), ], ) def test_clip_vpc( diff --git a/tests/test_merge.py b/tests/test_merge.py index 06f0d20..b235862 100644 --- a/tests/test_merge.py +++ b/tests/test_merge.py @@ -46,6 +46,11 @@ def test_merge_to_file(output_path: Path, laz_files: typing.List[str]): ), (utils.test_data_filepath("data.vpc"), utils.test_data_output_filepath("merged-vpc.copc.laz", "merge")), (utils.test_data_filepath("data.vpc"), utils.test_data_output_filepath("merged-vpc.las", "merge")), + (utils.test_data_filepath("data_copc.vpz"), utils.test_data_output_filepath("merged-copc-vpc.las", "merge")), + ( + utils.test_data_filepath("data_copc.vpz"), + utils.test_data_output_filepath("merged-copc-vpc.copc.laz", "merge"), + ), ], ) def test_merge_vpc( diff --git a/tests/test_thin.py b/tests/test_thin.py index 2439eb5..39dd5eb 100644 --- a/tests/test_thin.py +++ b/tests/test_thin.py @@ -13,6 +13,7 @@ (utils.test_data_filepath("stadium-utm.laz"), utils.test_data_filepath("thin.copc.laz"), 138779), (utils.test_data_filepath("stadium-utm.copc.laz"), utils.test_data_filepath("thin-copc-input.laz"), 138779), (utils.test_data_filepath("data.vpc"), utils.test_data_filepath("thin-vpc.copc.laz"), 67634), + (utils.test_data_filepath("data_copc.vpz"), utils.test_data_filepath("thin-vpc.copc.laz"), 67634), ], ) def test_thin(input_path: Path, output_path: Path, point_count: int): diff --git a/tests/test_to_vector.py b/tests/test_to_vector.py index cdbca6b..9a737e3 100644 --- a/tests/test_to_vector.py +++ b/tests/test_to_vector.py @@ -13,6 +13,7 @@ [ (utils.test_data_filepath("stadium-utm.laz"), utils.test_data_filepath("points-laz.gpkg"), 693895), (utils.test_data_filepath("data.vpc"), utils.test_data_filepath("points-vpc.gpkg"), 338163), + (utils.test_data_filepath("data_copc.vpz"), utils.test_data_filepath("points-vpc.gpkg"), 338163), ], ) def test_to_vector(input_path: Path, gpkg_file: Path, point_count: int): diff --git a/tests/test_translate.py b/tests/test_translate.py index 5a63a64..607c4f7 100644 --- a/tests/test_translate.py +++ b/tests/test_translate.py @@ -34,6 +34,11 @@ utils.test_data_output_filepath("translate.vpc", "translate"), 338163, ), + ( + utils.test_data_filepath("data_copc.vpz"), + utils.test_data_output_filepath("translate.vpc", "translate"), + 338163, + ), ], ) def test_translate_files(input_path: Path, output_path: Path, point_count: int): @@ -88,6 +93,11 @@ def test_translate_files(input_path: Path, output_path: Path, point_count: int): utils.test_data_output_filepath("translate-transform.vpc", "translate-transform"), 338163, ), + ( + utils.test_data_filepath("data_copc.vpz"), + utils.test_data_output_filepath("translate-transform.vpc", "translate-transform"), + 338163, + ), ], ) def test_translate_with_transform_files(input_path: Path, output_path: Path, point_count: int): From fc08e97ac16f1065565c06248292e839ce276092 Mon Sep 17 00:00:00 2001 From: uclaros Date: Wed, 11 Mar 2026 19:07:15 +0200 Subject: [PATCH 2/3] write test results to new files --- tests/test_boundary.py | 4 ++-- tests/test_clip.py | 4 ++-- tests/test_merge.py | 4 ++-- tests/test_thin.py | 2 +- tests/test_to_vector.py | 2 +- tests/test_translate.py | 4 ++-- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/tests/test_boundary.py b/tests/test_boundary.py index 5cdd075..ee8a23d 100644 --- a/tests/test_boundary.py +++ b/tests/test_boundary.py @@ -11,8 +11,8 @@ (utils.test_data_filepath("stadium-utm.laz"), utils.test_data_filepath("boundary-laz.gpkg")), (utils.test_data_filepath("stadium-utm.copc.laz"), utils.test_data_filepath("boundary_copc.gpkg")), (utils.test_data_filepath("data.vpc"), utils.test_data_filepath("boundary-vpc.gpkg")), - (utils.test_data_filepath("data_copc.vpc"), utils.test_data_filepath("boundary-vpc.gpkg")), - (utils.test_data_filepath("data_copc.vpz"), utils.test_data_filepath("boundary-vpc.gpkg")), + (utils.test_data_filepath("data_copc.vpc"), utils.test_data_filepath("boundary-copc-vpc.gpkg")), + (utils.test_data_filepath("data_copc.vpz"), utils.test_data_filepath("boundary-copc-vpz.gpkg")), ], ) def test_boundary(input_path: Path, output_path: Path): diff --git a/tests/test_clip.py b/tests/test_clip.py index 29962f2..8693168 100644 --- a/tests/test_clip.py +++ b/tests/test_clip.py @@ -49,8 +49,8 @@ def test_input_file_output_file( (utils.test_data_filepath("data.vpc"), utils.test_data_filepath("clipped-vpc.copc.laz")), (utils.test_data_filepath("data_copc.vpc"), utils.test_data_filepath("clipped-vpc-copc-files.vpc")), (utils.test_data_filepath("data_copc.vpc"), utils.test_data_filepath("clipped-vpc-copc-files.copc.laz")), - (utils.test_data_filepath("data_copc.vpz"), utils.test_data_filepath("clipped-vpc-copc-files.vpc")), - (utils.test_data_filepath("data_copc.vpz"), utils.test_data_filepath("clipped-vpc-copc-files.copc.laz")), + (utils.test_data_filepath("data_copc.vpz"), utils.test_data_filepath("clipped-vpz-copc-files.vpc")), + (utils.test_data_filepath("data_copc.vpz"), utils.test_data_filepath("clipped-vpz-copc-files.copc.laz")), ], ) def test_clip_vpc( diff --git a/tests/test_merge.py b/tests/test_merge.py index b235862..90aa6e9 100644 --- a/tests/test_merge.py +++ b/tests/test_merge.py @@ -46,10 +46,10 @@ def test_merge_to_file(output_path: Path, laz_files: typing.List[str]): ), (utils.test_data_filepath("data.vpc"), utils.test_data_output_filepath("merged-vpc.copc.laz", "merge")), (utils.test_data_filepath("data.vpc"), utils.test_data_output_filepath("merged-vpc.las", "merge")), - (utils.test_data_filepath("data_copc.vpz"), utils.test_data_output_filepath("merged-copc-vpc.las", "merge")), + (utils.test_data_filepath("data_copc.vpz"), utils.test_data_output_filepath("merged-copc-vpz.las", "merge")), ( utils.test_data_filepath("data_copc.vpz"), - utils.test_data_output_filepath("merged-copc-vpc.copc.laz", "merge"), + utils.test_data_output_filepath("merged-copc-vpz.copc.laz", "merge"), ), ], ) diff --git a/tests/test_thin.py b/tests/test_thin.py index 39dd5eb..94db67f 100644 --- a/tests/test_thin.py +++ b/tests/test_thin.py @@ -13,7 +13,7 @@ (utils.test_data_filepath("stadium-utm.laz"), utils.test_data_filepath("thin.copc.laz"), 138779), (utils.test_data_filepath("stadium-utm.copc.laz"), utils.test_data_filepath("thin-copc-input.laz"), 138779), (utils.test_data_filepath("data.vpc"), utils.test_data_filepath("thin-vpc.copc.laz"), 67634), - (utils.test_data_filepath("data_copc.vpz"), utils.test_data_filepath("thin-vpc.copc.laz"), 67634), + (utils.test_data_filepath("data_copc.vpz"), utils.test_data_filepath("thin-vpz.copc.laz"), 67634), ], ) def test_thin(input_path: Path, output_path: Path, point_count: int): diff --git a/tests/test_to_vector.py b/tests/test_to_vector.py index 9a737e3..1a9f912 100644 --- a/tests/test_to_vector.py +++ b/tests/test_to_vector.py @@ -13,7 +13,7 @@ [ (utils.test_data_filepath("stadium-utm.laz"), utils.test_data_filepath("points-laz.gpkg"), 693895), (utils.test_data_filepath("data.vpc"), utils.test_data_filepath("points-vpc.gpkg"), 338163), - (utils.test_data_filepath("data_copc.vpz"), utils.test_data_filepath("points-vpc.gpkg"), 338163), + (utils.test_data_filepath("data_copc.vpz"), utils.test_data_filepath("points-vpz.gpkg"), 338163), ], ) def test_to_vector(input_path: Path, gpkg_file: Path, point_count: int): diff --git a/tests/test_translate.py b/tests/test_translate.py index 607c4f7..a2ba012 100644 --- a/tests/test_translate.py +++ b/tests/test_translate.py @@ -36,7 +36,7 @@ ), ( utils.test_data_filepath("data_copc.vpz"), - utils.test_data_output_filepath("translate.vpc", "translate"), + utils.test_data_output_filepath("translate-vpz.vpc", "translate"), 338163, ), ], @@ -95,7 +95,7 @@ def test_translate_files(input_path: Path, output_path: Path, point_count: int): ), ( utils.test_data_filepath("data_copc.vpz"), - utils.test_data_output_filepath("translate-transform.vpc", "translate-transform"), + utils.test_data_output_filepath("translate-transform-vpz.vpc", "translate-transform"), 338163, ), ], From 663189bceaacb7176f37bcd064bcbda904ecacb7 Mon Sep 17 00:00:00 2001 From: uclaros Date: Wed, 11 Mar 2026 19:07:46 +0200 Subject: [PATCH 3/3] add libzip to environment --- environment.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/environment.yml b/environment.yml index fac9fb6..fe76360 100644 --- a/environment.yml +++ b/environment.yml @@ -8,4 +8,5 @@ dependencies: - ninja - cmake - pdal + - libzip