├── .gitattributes ├── .github └── workflows │ ├── release.yml │ └── test.yml ├── .gitignore ├── CMakeLists.txt ├── HOWTORELEASE.md ├── LICENSE ├── README.md ├── api ├── CMakeLists.txt ├── QgisTest.cpp ├── QgisTest2.cpp ├── QgisUntwine.cpp ├── QgisUntwine.hpp ├── QgisUntwine_unix.cpp ├── QgisUntwine_win.cpp └── README ├── bu ├── BuPyramid.cpp ├── BuPyramid.hpp ├── CopcSupport.cpp ├── CopcSupport.hpp ├── FileInfo.hpp ├── OctantInfo.cpp ├── OctantInfo.hpp ├── PointAccessor.hpp ├── Processor.cpp ├── Processor.hpp ├── PyramidManager.cpp ├── PyramidManager.hpp ├── Stats.cpp ├── Stats.hpp └── VoxelInfo.hpp ├── ci └── environment.yml ├── cmake ├── compiler_options.cmake ├── cpack.cmake ├── unix_compiler_options.cmake └── win32_compiler_options.cmake ├── epf ├── BufferCache.cpp ├── BufferCache.hpp ├── Cell.cpp ├── Cell.hpp ├── Epf.cpp ├── Epf.hpp ├── EpfTypes.hpp ├── FileProcessor.cpp ├── FileProcessor.hpp ├── Grid.cpp ├── Grid.hpp ├── Reprocessor.cpp ├── Reprocessor.hpp ├── Writer.cpp └── Writer.hpp ├── lazperf ├── Extractor.hpp ├── Inserter.hpp ├── charbuf.cpp ├── charbuf.hpp ├── coderbase.hpp ├── compressor.hpp ├── decoder.hpp ├── decompressor.hpp ├── detail │ ├── field_byte10.cpp │ ├── field_byte10.hpp │ ├── field_byte14.cpp │ ├── field_byte14.hpp │ ├── field_gpstime.hpp │ ├── field_gpstime10.cpp │ ├── field_gpstime10.hpp │ ├── field_nir14.cpp │ ├── field_nir14.hpp │ ├── field_point10.cpp │ ├── field_point10.hpp │ ├── field_point14.cpp │ ├── field_point14.hpp │ ├── field_rgb10.cpp │ ├── field_rgb10.hpp │ ├── field_rgb14.cpp │ ├── field_rgb14.hpp │ └── field_xyz.hpp ├── encoder.hpp ├── excepts.hpp ├── filestream.cpp ├── filestream.hpp ├── header.cpp ├── header.hpp ├── las.hpp ├── lazperf.cpp ├── lazperf.hpp ├── lazperf_base.hpp ├── lazperf_user_base.hpp ├── model.hpp ├── portable_endian.hpp ├── readers.cpp ├── readers.hpp ├── streams.hpp ├── utils.hpp ├── vlr.cpp ├── vlr.hpp ├── writers.cpp └── writers.hpp ├── prep ├── FilePrep.cpp └── FilePrep.hpp ├── test ├── CMakeLists.txt ├── Stats.hpp ├── TestConfig.hpp.in ├── Tests.cpp ├── data │ ├── autzen_trim.laz │ └── eb.laz └── gtest │ ├── CMakeLists.txt │ ├── README.md │ ├── cmake │ ├── Config.cmake.in │ ├── gtest.pc.in │ ├── gtest_main.pc.in │ ├── internal_utils.cmake │ └── libgtest.la.in │ ├── include │ └── gtest │ │ ├── gtest-assertion-result.h │ │ ├── gtest-death-test.h │ │ ├── gtest-matchers.h │ │ ├── gtest-message.h │ │ ├── gtest-param-test.h │ │ ├── gtest-printers.h │ │ ├── gtest-spi.h │ │ ├── gtest-test-part.h │ │ ├── gtest-typed-test.h │ │ ├── gtest.h │ │ ├── gtest_pred_impl.h │ │ ├── gtest_prod.h │ │ └── internal │ │ ├── custom │ │ ├── README.md │ │ ├── gtest-port.h │ │ ├── gtest-printers.h │ │ └── gtest.h │ │ ├── gtest-death-test-internal.h │ │ ├── gtest-filepath.h │ │ ├── gtest-internal.h │ │ ├── gtest-param-util.h │ │ ├── gtest-port-arch.h │ │ ├── gtest-port.h │ │ ├── gtest-string.h │ │ └── gtest-type-util.h │ ├── samples │ ├── prime_tables.h │ ├── sample1.cc │ ├── sample1.h │ ├── sample10_unittest.cc │ ├── sample1_unittest.cc │ ├── sample2.cc │ ├── sample2.h │ ├── sample2_unittest.cc │ ├── sample3-inl.h │ ├── sample3_unittest.cc │ ├── sample4.cc │ ├── sample4.h │ ├── sample4_unittest.cc │ ├── sample5_unittest.cc │ ├── sample6_unittest.cc │ ├── sample7_unittest.cc │ ├── sample8_unittest.cc │ └── sample9_unittest.cc │ └── src │ ├── gtest-all.cc │ ├── gtest-assertion-result.cc │ ├── gtest-death-test.cc │ ├── gtest-filepath.cc │ ├── gtest-internal-inl.h │ ├── gtest-matchers.cc │ ├── gtest-port.cc │ ├── gtest-printers.cc │ ├── gtest-test-part.cc │ ├── gtest-typed-test.cc │ ├── gtest.cc │ └── gtest_main.cc └── untwine ├── Common.hpp ├── Config.hpp.in ├── FatalError.hpp ├── FileDimInfo.hpp ├── FileInfo.hpp ├── GridKey.hpp ├── Point.hpp ├── ProgressWriter.cpp ├── ProgressWriter.hpp ├── ThreadPool.cpp ├── ThreadPool.hpp ├── Untwine.cpp ├── VoxelKey.hpp ├── generic ├── dirlist.hpp ├── mapfile.hpp ├── progress.hpp └── stringconv.hpp ├── mingw ├── dirlist.hpp ├── mapfile.hpp ├── progress.hpp └── stringconv.hpp ├── osx ├── dirlist.hpp ├── mapfile.hpp ├── progress.hpp └── stringconv.hpp ├── src └── api │ └── untwine.h └── windows ├── dirlist.hpp ├── mapfile.hpp ├── progress.hpp └── stringconv.hpp /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | on: [push, pull_request] 3 | 4 | concurrency: 5 | group: ${{ github.workflow }}-${{ github.head_ref || github.ref }} 6 | cancel-in-progress: true 7 | 8 | jobs: 9 | build: 10 | name: OS ${{ matrix.os }} 11 | runs-on: ${{ matrix.os }} 12 | strategy: 13 | matrix: 14 | os: [macos-latest, windows-latest, ubuntu-latest] 15 | shared: [ON, OFF] 16 | 17 | steps: 18 | - uses: actions/checkout@v4 19 | - uses: ilammy/msvc-dev-cmd@v1 20 | if: matrix.os == 'windows-latest' 21 | - name: Support longpaths 22 | run: git config --system core.longpaths true 23 | if: matrix.os == 'windows-latest' 24 | - uses: mamba-org/setup-micromamba@v2 25 | with: 26 | init-shell: bash 27 | environment-file: ci/environment.yml 28 | environment-name: "build" 29 | cache-environment: true 30 | cache-downloads: true 31 | 32 | - name: Setup 33 | shell: bash -l {0} 34 | run: | 35 | mkdir build 36 | 37 | - name: CMake 38 | shell: bash -l {0} 39 | 40 | run: | 41 | 42 | if [ "$RUNNER_OS" == "Windows" ]; then 43 | export CC=cl.exe 44 | export CXX=cl.exe 45 | fi 46 | 47 | cmake -G "Ninja" \ 48 | -DCMAKE_BUILD_TYPE=RelWithDebInfo \ 49 | -DBUILD_SHARED_LIBS=$BUILD_SHARED_LIBS \ 50 | -DBUILD_TESTING=ON \ 51 | -DCMAKE_INSTALL_PREFIX=${CONDA_PREFIX} \ 52 | -Dgtest_force_shared_crt=ON \ 53 | .. 54 | 55 | 56 | working-directory: ./build 57 | 58 | - name: Compile 59 | shell: bash -l {0} 60 | run: | 61 | ninja 62 | working-directory: ./build 63 | 64 | - name: Test 65 | shell: bash -l {0} 66 | run: | 67 | ctest -VV --rerun-failed --output-on-failure 68 | working-directory: ./build 69 | 70 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # MacOS 2 | .DS_Store 3 | 4 | # Prerequisites 5 | *.d 6 | 7 | # Compiled Object files 8 | *.slo 9 | *.lo 10 | *.o 11 | *.obj 12 | 13 | # Precompiled Headers 14 | *.gch 15 | *.pch 16 | 17 | # Compiled Dynamic libraries 18 | *.so 19 | *.dylib 20 | *.dll 21 | 22 | # Fortran module files 23 | *.mod 24 | *.smod 25 | 26 | # Compiled Static libraries 27 | *.lai 28 | *.la 29 | *.a 30 | *.lib 31 | 32 | # Executables 33 | *.exe 34 | *.out 35 | *.app 36 | 37 | # Cmake 38 | build 39 | 40 | # vim 41 | *.sw[a-z] 42 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.5) 2 | project(Untwine VERSION 1.5.0 LANGUAGES CXX) 3 | include(FeatureSummary) 4 | include(${PROJECT_SOURCE_DIR}/cmake/compiler_options.cmake) 5 | include(${PROJECT_SOURCE_DIR}/cmake/cpack.cmake) 6 | 7 | option(BUILD_TESTING "Chose if Untwine unit tests should be built" OFF) 8 | add_feature_info("Unit tests" BUILD_TESTING "Untwine unit tests") 9 | 10 | if(DEFINED WITH_TESTS) 11 | message(DEPRECATION "WITH_TESTS has been replaced with the standard CMake BUILD_TESTING variable") 12 | set(BUILD_TESTING ${WITH_TESTS}) 13 | endif() 14 | 15 | find_package(PDAL REQUIRED CONFIG 2.6) 16 | 17 | find_package(Threads) 18 | set_package_properties(Threads PROPERTIES DESCRIPTION 19 | "The thread library of the system" TYPE REQUIRED) 20 | 21 | configure_file(untwine/Config.hpp.in include/Config.hpp) 22 | 23 | file(GLOB LAZPERF_SRCS 24 | lazperf/*.cpp 25 | lazperf/detail/*.cpp 26 | ) 27 | 28 | file(GLOB SRCS 29 | untwine/*.cpp 30 | prep/*.cpp 31 | epf/*.cpp 32 | bu/*.cpp 33 | ) 34 | 35 | # MINGW must come before WIN32 test 36 | # APPLE must come before LINUX test 37 | if (MINGW) 38 | set(UNTWINE_OS_DIR untwine/mingw) 39 | elseif (WIN32) 40 | set(UNTWINE_OS_DIR untwine/windows) 41 | elseif (APPLE) 42 | set(UNTWINE_OS_DIR untwine/osx) 43 | elseif (LINUX) 44 | set(UNTWINE_OS_DIR untwine/generic) 45 | else() 46 | message(FATAL_ERROR "OS not supported") 47 | endif() 48 | 49 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin) 50 | 51 | add_executable(untwine ${SRCS} ${LAZPERF_SRCS}) 52 | 53 | untwine_target_compile_settings(untwine) 54 | 55 | target_include_directories(untwine 56 | PRIVATE 57 | ${UNTWINE_OS_DIR} 58 | ${CMAKE_CURRENT_BINARY_DIR}/include 59 | ${PDAL_INCLUDE_DIRS} 60 | ${PROJECT_SOURCE_DIR} 61 | ) 62 | 63 | target_link_libraries(untwine 64 | PRIVATE 65 | ${CMAKE_THREAD_LIBS_INIT} 66 | ${PDAL_LIBRARIES} 67 | ) 68 | 69 | if (MSVC) 70 | target_link_options(untwine 71 | PRIVATE 72 | /SUBSYSTEM:CONSOLE /ENTRY:wmainCRTStartup 73 | ) 74 | endif() 75 | 76 | install(TARGETS untwine DESTINATION bin) 77 | 78 | if (BUILD_TESTING) 79 | enable_testing() 80 | add_subdirectory(test) 81 | endif() 82 | 83 | # 84 | # CPACK 85 | # 86 | add_custom_target(dist COMMAND ${CMAKE_MAKE_PROGRAM} package_source) 87 | 88 | 89 | -------------------------------------------------------------------------------- /HOWTORELEASE.md: -------------------------------------------------------------------------------- 1 | --- 2 | author: | 3 | Howard Butler 4 | contact: | 5 | howard@hobu.co 6 | date: 12/09/2024 7 | title: Steps for Making a Untwine Release 8 | --- 9 | 10 | # Release Process 11 | 12 | This document describes the process for releasing a new version of Untwine. 13 | 14 | 1) Set version numbers. The only place you need to set the Untwine version 15 | number is in the project() function of CMakeLists.txt. 16 | 17 | - CMakeLists.txt 18 | 19 | > project(Untwine VERSION 1.5.0 LANGUAGES CXX ) 20 | 21 | 22 | 2) Ensure CI is ✅ 23 | 24 | 25 | 3) Tag the release and push the tag to GitHub. If the tag name is 26 | `x.x.x`, a draft release will be pushed to 27 | > git tag 1.5.0 28 | > git push origin 1.5.0 29 | 30 | 4) Write and update the [draft release notes.](https://github.com/hobuinc/Untwine/releases). 31 | The safest way to do this is to go though all the commits since the last release and reference 32 | any changes worthy of the release notes. See previous release notes for a template 33 | and a starting point. If you find issues after making the release branch, 34 | add them to the release notes. 35 | 36 | 5) Update Conda package 37 | 38 | - Conda Forge machinery should detect and build a new package for 39 | Untwine once the release tag is 'Published'. 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Untwine 2 | 3 | Untwine is software from [Hobu, Inc.](https://hobu.co) for creating [Cloud Optimized Point Cloud](https://copc.io/) (COPC) web services from [PDAL](https://pdal.io)-readable point cloud data sources. 4 | 5 | 6 | License 7 | ------- 8 | 9 | Untwine is licensed under the GPLv3. Commercial licensing is possible by contacting 10 | Hobu, Inc. for pricing. 11 | 12 | Installation 13 | ------------ 14 | 15 | Untwine is available on `conda-forge` ([here](https://anaconda.org/conda-forge/untwine)) and can be installed using `conda` 16 | 17 | ``` 18 | conda install -c conda-forge untwine 19 | ``` 20 | 21 | 22 | Building Untwine: 23 | ----------------- 24 | 25 | The following steps will build the `untwine` executable: 26 | ``` 27 | mkdir build 28 | cd build 29 | cmake .. 30 | make 31 | ``` 32 | 33 | Using Untwine 34 | ------------- 35 | 36 | ``` 37 | untwine [options] 38 | ``` 39 | 40 | Example: 41 | -------- 42 | 43 | ``` 44 | untwine -i some_directory_of_input_files -o output_filename 45 | ``` 46 | 47 | or 48 | 49 | ``` 50 | untwine --files=some_directory_of_input_files --output_file=output_filename 51 | ``` 52 | 53 | 54 | Options 55 | ------- 56 | 57 | - `-i` or `--files` 58 | 59 | Input files or directories containing input files. [Required] 60 | 61 | - `-o` or `--output_dir` 62 | 63 | Output file. [Required] 64 | 65 | - `--help` 66 | 67 | Usage help 68 | 69 | - `--a_srs` 70 | 71 | Assign an output SRS. Example `--a_srs EPSG:2056` 72 | 73 | - `--no_srs` 74 | 75 | Don't read the SLR VLRs. This is only applicable to las files. [Default: false] 76 | 77 | - `--dims` 78 | 79 | List of dimensions to load. X, Y and Z are always loaded. Limiting the dimensions can 80 | speed runtime and reduce temporary disk use. 81 | 82 | - `--temp_dir` 83 | 84 | Directory in which to place tiled output. If not provided, temporary files are placed 85 | in 'output_dir'/temp. 86 | 87 | - `--cube` 88 | 89 | Create a voxel structure where each voxel is a cube. If false, the voxel structure is 90 | a rectangular solid that encloses the points. [Default: true] 91 | 92 | - `--level` 93 | 94 | Level to use when initially tiling points. If not provided, an initial level is 95 | determined from the data. [Default: none]. 96 | 97 | - `--file_limit` 98 | 99 | Only read 'file_limit' input files even if more exist in the 'files' list. Used primarily 100 | for debugging. [Default: no limit] 101 | 102 | - `--stats` 103 | 104 | Generate summary statistics in 'ept.json' similar to those produced by Entwine for EPT output 105 | Min/max stats are always generated when generating single-file output. 106 | [Default: false] 107 | 108 | - `--progress_fd` 109 | 110 | File descriptor number of a pipe using the Untwine API to send progress and error messages. 111 | [Default: -1] 112 | 113 | - `--progress_debug` 114 | 115 | Set to true to have progress messages written to standard output. Disabled if 'progress_fd' 116 | is set to '1'. [Default: false] 117 | 118 | - `-s` or `-- single_file` 119 | 120 | **Deprecated and ingored.** Generate a LAZ file with spatially arranged data and hierarchy information 121 | [(COPC)](https://github.com/copcio/copcio.github.io). [Default: false] 122 | -------------------------------------------------------------------------------- /api/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.5) 2 | project(UntwineApi VERSION 1.0 LANGUAGES CXX) 3 | include(FeatureSummary) 4 | include(${PROJECT_SOURCE_DIR}/../cmake/compiler_options.cmake) 5 | 6 | add_executable(qgt 7 | QgisTest2.cpp 8 | QgisUntwine.cpp 9 | ) 10 | 11 | untwine_target_compile_settings(qgt) 12 | 13 | -------------------------------------------------------------------------------- /api/QgisTest.cpp: -------------------------------------------------------------------------------- 1 | #ifndef _WIN32 2 | #include 3 | #endif 4 | 5 | #include 6 | 7 | #include "QgisUntwine.hpp" 8 | 9 | int main() 10 | { 11 | untwine::QgisUntwine::StringList files; 12 | untwine::QgisUntwine::Options options; 13 | // std::string exe = "C:\\Users\\andre\\untwine\\build\\untwine.exe"; 14 | std::string exe = "/Users/acbell/untwine/build/untwine"; 15 | 16 | untwine::QgisUntwine api(exe); 17 | 18 | // files.push_back("C:\\Users\\andre\\nyc2"); 19 | // std::vector funnycVec = { 0xc4, 0x8d }; 20 | // std::string funnyc(funnycVec.begin(), funnycVec.end()); 21 | // std::string v8string { "C:\\Users\\andre\\untwine\\api\\build\\" + funnyc + "\\" + funnyc + ".las" }; 22 | // files.push_back(v8string); 23 | // files.push_back("C:\\Users\\andre\\nyc2\\18TXL075075.las.laz"); 24 | // files.push_back("/Users/acbell/nyc/18TXL075075.las.laz"); 25 | // files.push_back("/Users/acbell/nyc/18TXL075090.las.laz"); 26 | // files.push_back("/Users/acbell/nyc2"); 27 | files.push_back("/Users/acbell/USGS_LPC_MD_PA_SandySupp_2014_18SUH825795_LAS_2016.laz"); 28 | 29 | // options.push_back({"dims", "X, Y, Z, Red, Green, Blue, Intensity"}); 30 | // book ok = api.start(files, ".\\out", options); 31 | bool ok = api.start(files, "./out", options); 32 | if (! ok) 33 | { 34 | std::cerr << "Couldn't start '" << exe << "!\n"; 35 | exit(-1); 36 | } 37 | 38 | bool stopped = false; 39 | while (true) 40 | { 41 | #ifdef _WIN32 42 | Sleep(1000); 43 | #else 44 | ::sleep(1); 45 | #endif 46 | int percent = api.progressPercent(); 47 | std::string s = api.progressMessage(); 48 | std::cerr << "Percent/Msg = " << percent << " / " << s << "!\n"; 49 | /** 50 | if (!stopped && percent >= 50) 51 | { 52 | stopped = true; 53 | api.stop(); 54 | } 55 | **/ 56 | if (!api.running()) 57 | break; 58 | } 59 | std::cerr << "Error = " << api.errorMessage() << "\n"; 60 | } 61 | -------------------------------------------------------------------------------- /api/QgisTest2.cpp: -------------------------------------------------------------------------------- 1 | #ifndef _WIN32 2 | #include 3 | #endif 4 | 5 | #include 6 | 7 | #include "QgisUntwine.hpp" 8 | 9 | int main() 10 | { 11 | untwine::QgisUntwine::StringList files; 12 | untwine::QgisUntwine::Options options; 13 | std::string exe = "C:\\Users\\andre\\untwine\\build\\untwine.exe"; 14 | 15 | untwine::QgisUntwine api(exe); 16 | 17 | std::vector funnycVec = { 0xc4, 0x8d }; 18 | std::string funnyc(funnycVec.begin(), funnycVec.end()); 19 | std::string v8string { "C:\\Users\\andre\\untwine\\api\\" + funnyc + "\\" + funnyc + ".las" }; 20 | files.push_back(v8string); 21 | std::string outDir { "./out_" + funnyc }; 22 | bool ok = api.start(files, outDir, options); 23 | if (! ok) 24 | { 25 | std::cerr << "Couldn't start '" << exe << "!\n"; 26 | exit(-1); 27 | } 28 | 29 | while (true) 30 | { 31 | #ifdef _WIN32 32 | Sleep(1000); 33 | #else 34 | ::sleep(1); 35 | #endif 36 | int percent = api.progressPercent(); 37 | std::string s = api.progressMessage(); 38 | std::cerr << "Percent/Msg = " << percent << " / " << s << "!\n"; 39 | if (!api.running()) 40 | break; 41 | } 42 | std::cerr << "Error = " << api.errorMessage() << "\n"; 43 | } 44 | -------------------------------------------------------------------------------- /api/QgisUntwine.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "QgisUntwine.hpp" 5 | 6 | #ifdef WIN32 7 | #include "QgisUntwine_win.cpp" 8 | #else 9 | #include "QgisUntwine_unix.cpp" 10 | #endif 11 | 12 | namespace untwine 13 | { 14 | 15 | QgisUntwine::QgisUntwine(const std::string& untwinePath) : m_path(untwinePath), m_running(false), 16 | m_percent(0) 17 | {} 18 | 19 | bool QgisUntwine::start(const StringList& files, const std::string& outputDir, 20 | const Options& argOptions) 21 | { 22 | if (m_running) 23 | return false; 24 | 25 | Options options(argOptions); 26 | if (files.size() == 0 || outputDir.empty()) 27 | return false; 28 | 29 | std::string s; 30 | for (auto ti = files.begin(); ti != files.end(); ++ti) 31 | { 32 | s += *ti; 33 | if (ti + 1 != files.end()) 34 | s += ", "; 35 | } 36 | options.push_back({"files", s}); 37 | options.push_back({"output_dir", outputDir}); 38 | 39 | return start(options); 40 | } 41 | 42 | int QgisUntwine::progressPercent() const 43 | { 44 | readPipe(); 45 | 46 | return m_percent; 47 | } 48 | 49 | std::string QgisUntwine::progressMessage() const 50 | { 51 | readPipe(); 52 | 53 | return m_progressMsg; 54 | } 55 | 56 | std::string QgisUntwine::errorMessage() const 57 | { 58 | readPipe(); 59 | 60 | if ( m_errorMsg.empty() && m_exitCode != 0 ) 61 | m_errorMsg = "Untwine exited with code: " + std::to_string( m_exitCode ); 62 | 63 | return m_errorMsg; 64 | } 65 | 66 | } // namespace untwine 67 | -------------------------------------------------------------------------------- /api/QgisUntwine.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #ifdef _WIN32 8 | #include 9 | #endif 10 | 11 | namespace untwine 12 | { 13 | 14 | class QgisUntwine 15 | { 16 | public: 17 | using Option = std::pair; 18 | using Options = std::vector