diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2e48bb99..9d4f94da 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,77 +2,178 @@ name: CI on: push: - branches: [ "main", "tile-downloader" ] pull_request: - branches: [ "main", "tile-downloader" ] + branches: [ "main" ] env: - BUILD_TYPE: Release + CCACHE_DIR: ${{ github.workspace }}/.ccache + CCACHE_MAXSIZE: 1G jobs: - build-terrainbuilder: - runs-on: ubuntu-latest + build-and-test: + name: ${{ matrix.name }} + runs-on: ubuntu-24.04 + strategy: + fail-fast: false + matrix: + include: + - name: gcc-14-normal + cc: gcc-14 + cxx: g++-14 + build_type: Release + sanitizer: normal + cmake_flags: "" + - name: clang-23-asan + cc: clang-23 + cxx: clang++-23 + build_type: RelWithDebInfo + sanitizer: asan + cmake_flags: -DALP_ENABLE_ADDRESS_SANITIZER=ON + - name: clang-23-tsan + cc: clang-23 + cxx: clang++-23 + build_type: RelWithDebInfo + sanitizer: tsan + cmake_flags: -DALP_ENABLE_THREAD_SANITIZER=ON + - name: clang-23-unity + cc: clang-23 + cxx: clang++-23 + build_type: Release + sanitizer: normal + cmake_flags: -DALP_ENABLE_UNITY_BUILD=ON steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: - submodules: true + submodules: true - - name: Install dependencies - run: sudo apt-get install libfmt-dev libglm-dev libgdal-dev catch2 libfreeimage-dev libtbb-dev - - - name: Configure CMake - run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + - name: Install system dependencies + run: | + sudo apt-get update + sudo apt-get install -y --no-install-recommends \ + ccache \ + extra-cmake-modules \ + libcurl4-openssl-dev \ + libegl1-mesa-dev \ + libgl1-mesa-dev \ + libglm-dev \ + libgmp-dev \ + libjpeg-dev \ + libmpfr-dev \ + libpng-dev \ + libsqlite3-dev \ + libtbb-dev \ + libtiff-dev \ + libwayland-bin \ + libwayland-dev \ + libxkbcommon-dev \ + ninja-build \ + pkg-config \ + sqlite3 \ + wayland-protocols \ + xorg-dev \ + zlib1g-dev - - name: Build - run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --target terrainbuilder - - build-tile-downloader: - if: github.ref == 'refs/heads/tile-downloader' - runs-on: ubuntu-latest + - name: Install LLVM 23 + if: matrix.cxx == 'clang++-23' + run: | + wget -qO /tmp/llvm.sh https://apt.llvm.org/llvm.sh + chmod +x /tmp/llvm.sh + sudo /tmp/llvm.sh 23 + sudo apt-get install -y --no-install-recommends libclang-rt-23-dev - steps: - - uses: actions/checkout@v3 + - name: Restore installed CMake dependencies + id: cmake_dependency_cache + uses: actions/cache/restore@v4 with: - submodules: true + path: | + build/alp_external/boost + build/alp_external/proj + build/alp_external/gdal + build/alp_external/cgal + build/alp_external/opencv + key: ${{ runner.os }}-alp-installs-${{ matrix.name }}-${{ matrix.build_type }}-${{ matrix.sanitizer }}-${{ hashFiles('CMakeLists.txt', 'src/CMakeLists.txt', 'cmake/*.cmake') }} + restore-keys: | + ${{ runner.os }}-alp-installs-${{ matrix.name }}-${{ matrix.build_type }}-${{ matrix.sanitizer }}- - - name: Install dependencies - run: sudo apt-get install libfmt-dev libglm-dev libgdal-dev catch2 libfreeimage-dev libtbb-dev libcurl4-openssl-dev - - - name: Configure CMake - run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + - name: Restore ccache + id: ccache_cache + uses: actions/cache/restore@v4 + with: + path: ${{ env.CCACHE_DIR }} + key: ${{ runner.os }}-ccache-${{ matrix.name }}-${{ matrix.sanitizer }}-${{ github.ref_name }}-${{ github.run_id }}-${{ github.run_attempt }} + restore-keys: | + ${{ runner.os }}-ccache-${{ matrix.name }}-${{ matrix.sanitizer }}-${{ github.ref_name }}- + ${{ runner.os }}-ccache-${{ matrix.name }}-${{ matrix.sanitizer }}- - - name: Build - run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --target tile-downloader - - test: - runs-on: ubuntu-latest + - name: Configure ccache + run: | + ccache --max-size="${CCACHE_MAXSIZE}" + ccache --zero-stats - steps: - - uses: actions/checkout@v3 + - name: Configure CMake + run: > + cmake + -S "${{ github.workspace }}" + -B "${{ github.workspace }}/build" + -G Ninja + -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} + -DCMAKE_C_COMPILER=${{ matrix.cc }} + -DCMAKE_CXX_COMPILER=${{ matrix.cxx }} + -DCMAKE_C_COMPILER_LAUNCHER=ccache + -DCMAKE_CXX_COMPILER_LAUNCHER=ccache + ${{ matrix.cmake_flags }} + + - name: Save installed CMake dependencies + if: steps.cmake_dependency_cache.outputs.cache-hit != 'true' + uses: actions/cache/save@v4 with: - submodules: true + path: | + build/alp_external/boost + build/alp_external/proj + build/alp_external/gdal + build/alp_external/cgal + build/alp_external/opencv + key: ${{ runner.os }}-alp-installs-${{ matrix.name }}-${{ matrix.build_type }}-${{ matrix.sanitizer }}-${{ hashFiles('CMakeLists.txt', 'src/CMakeLists.txt', 'cmake/*.cmake') }} - - name: Install dependencies - run: sudo apt-get install libfmt-dev libglm-dev libgdal-dev catch2 libfreeimage-dev libtbb-dev - - - name: Configure CMake - run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + - name: Build all targets + run: cmake --build "${{ github.workspace }}/build" --config ${{ matrix.build_type }} --parallel --target all + + - name: ccache stats + if: always() + run: ccache --show-stats + + - name: Save ccache + if: always() + uses: actions/cache/save@v4 + with: + path: ${{ env.CCACHE_DIR }} + key: ${{ runner.os }}-ccache-${{ matrix.name }}-${{ matrix.sanitizer }}-${{ github.ref_name }}-${{ github.run_id }}-${{ github.run_attempt }} - - name: Build - run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --target unittests_terrainlib - - - uses: actions/cache@v3 + - name: Cache unit test data id: terrain_builder_unittest_data + uses: actions/cache@v4 with: - path: ./unittests/data - key: terrain_builder_unittest_data.tar.gz - - - name: Download data - if: steps.cache.outputs.cache-hit != 'true' - run: wget -qO- https://gataki.cg.tuwien.ac.at/raw/terrain_builder_unittest_data.tar.gz | tar xvz -C ./unittests/ + path: unittests/data + key: terrain-builder-unittest-data-v1 - - name: Test - working-directory: ${{github.workspace}}/build - run: ./unittests/unittests_terrainlib + - name: Download unit test data + if: steps.terrain_builder_unittest_data.outputs.cache-hit != 'true' + run: wget -qO- https://gataki.cg.tuwien.ac.at/raw/terrain_builder_unittest_data.tar.gz | tar xz -C unittests + - name: Run unit tests + working-directory: ${{ github.workspace }}/build + run: | + export PROJ_DATA="${{ github.workspace }}/build/alp_external/proj/share/proj" + export PROJ_LIB="${PROJ_DATA}" + export GDAL_DATA="${{ github.workspace }}/build/alp_external/gdal/share/gdal" + if [[ "${{ matrix.sanitizer }}" == "tsan" ]]; then + export TSAN_OPTIONS="suppressions=${{ github.workspace }}/extern/tbb/cmake/suppressions/tsan.suppressions" + fi + status=0 + ./unittests/unittests_terrainlib "~mesh::clip_on_bounds benchmark" || status=$? + ./unittests/unittests_tilebuilder || status=$? + ./unittests/unittests_meshbuilder || status=$? + ./unittests/unittests_dagbuilder || status=$? + ./unittests/unittests_sfmerger || status=$? + exit $status diff --git a/.gitignore b/.gitignore index 3382e319..be34b3ce 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,5 @@ build-release/ .vs /extern/* src/scratch +/.qtcreator/ +/build*/ diff --git a/CMakeLists.txt b/CMakeLists.txt index 24882125..f8cef9fe 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,6 +3,42 @@ cmake_minimum_required(VERSION 3.21) project(alpine-terrain-builder) option(ALP_UNITTESTS "include unit test targets in the buildsystem" ON) +# ASAN & TSAN +option(ALP_ENABLE_THREAD_SANITIZER "compiles with thread sanitizer enabled" OFF) +option(ALP_ENABLE_ADDRESS_SANITIZER "compiles with address sanitizer enabled" OFF) + +if(ALP_ENABLE_ADDRESS_SANITIZER) + if(ALP_ENABLE_THREAD_SANITIZER) + message(FATAL_ERROR "Cannot enable both AddressSanitizer and ThreadSanitizer") + endif() +endif() + +if(ALP_ENABLE_ADDRESS_SANITIZER) + message(STATUS "AddressSanitizer enabled") + if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") + set(ALP_SANITIZER_FLAGS -fsanitize=address -fsanitize=leak -fsanitize=undefined -fno-omit-frame-pointer -g) + add_compile_options(${ALP_SANITIZER_FLAGS}) + add_link_options(${ALP_SANITIZER_FLAGS}) + else() + message(WARNING "ALP_ENABLE_ADDRESS_SANITIZER set but compiler is not GCC/Clang; ignoring") + endif() +endif() + +if(ALP_ENABLE_THREAD_SANITIZER) + if(CMAKE_BUILD_TYPE STREQUAL "Release") + message(WARNING "ThreadSanitizer is not recommended with Release builds. " + "Use Debug or RelWithDebInfo for accurate race detection.") + endif() + message(STATUS "ThreadSanitizer enabled") + if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") + set(ALP_SANITIZER_FLAGS -fsanitize=thread -fno-omit-frame-pointer -fno-optimize-sibling-calls -g) + add_compile_options(${ALP_SANITIZER_FLAGS}) + add_link_options(${ALP_SANITIZER_FLAGS}) + else() + message(WARNING "ALP_ENABLE_THREAD_SANITIZER set but compiler is not GCC/Clang; ignoring") + endif() +endif() + include(cmake/AddRepo.cmake) include(cmake/SetupCMakeProject.cmake) diff --git a/cmake/SetupCMakeProject.cmake b/cmake/SetupCMakeProject.cmake index ad72db11..09245677 100644 --- a/cmake/SetupCMakeProject.cmake +++ b/cmake/SetupCMakeProject.cmake @@ -23,11 +23,20 @@ endif() function(_alp_build_and_install NAME SRC_DIR BUILD_DIR INSTALL_DIR) message(STATUS "[alp] Configuring ${NAME}") + string(JOIN " " _alp_sanitizer_flags ${ALP_SANITIZER_FLAGS}) + execute_process( COMMAND ${CMAKE_COMMAND} -G ${CMAKE_GENERATOR} -S ${SRC_DIR} -B ${BUILD_DIR} + -DCMAKE_C_COMPILER=${CMAKE_C_COMPILER} + -DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER} + "-DCMAKE_C_FLAGS=${CMAKE_C_FLAGS} ${_alp_sanitizer_flags}" + "-DCMAKE_CXX_FLAGS=${CMAKE_CXX_FLAGS} ${_alp_sanitizer_flags}" + "-DCMAKE_EXE_LINKER_FLAGS=${CMAKE_EXE_LINKER_FLAGS} ${_alp_sanitizer_flags}" + "-DCMAKE_MODULE_LINKER_FLAGS=${CMAKE_MODULE_LINKER_FLAGS} ${_alp_sanitizer_flags}" + "-DCMAKE_SHARED_LINKER_FLAGS=${CMAKE_SHARED_LINKER_FLAGS} ${_alp_sanitizer_flags}" -DCMAKE_PREFIX_PATH=${CMAKE_PREFIX_PATH} -DCMAKE_INSTALL_PREFIX=${INSTALL_DIR} -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} @@ -64,27 +73,47 @@ function(alp_setup_cmake_project arg_NAME) set(version_var "ALP_INSTALLED_${arg_NAME}_VERSION") set(path_var "ALP_INSTALLED_${arg_NAME}_PATH") - - if(DEFINED ${version_var} AND "${${version_var}}" STREQUAL "${arg_COMMITISH}${arg_CMAKE_ARGUMENTS}" AND DEFINED ${path_var} AND EXISTS "${${path_var}}") + set(build_dir "${CMAKE_BINARY_DIR}/alp_external/${arg_NAME}_build") + set(install_dir "${CMAKE_BINARY_DIR}/alp_external/${arg_NAME}") + set(stamp_file "${install_dir}/.alp_install_signature") + set(version_signature "${arg_COMMITISH}${arg_CMAKE_ARGUMENTS}${ALP_SANITIZER_FLAGS}") + set(cache_signature "URL=${arg_URL} +COMMITISH=${arg_COMMITISH} +BUILD_TYPE=${CMAKE_BUILD_TYPE} +SYSTEM=${CMAKE_SYSTEM_NAME}-${CMAKE_SYSTEM_PROCESSOR} +CXX=${CMAKE_CXX_COMPILER_ID}-${CMAKE_CXX_COMPILER_VERSION} +SANITIZER_FLAGS=${ALP_SANITIZER_FLAGS} +ARGS=${arg_CMAKE_ARGUMENTS}") + + if(DEFINED ${version_var} AND "${${version_var}}" STREQUAL "${version_signature}" AND DEFINED ${path_var} AND EXISTS "${${path_var}}") list(PREPEND CMAKE_PREFIX_PATH "${${path_var}}") set(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} PARENT_SCOPE) return() endif() + if(EXISTS "${install_dir}" AND EXISTS "${stamp_file}") + file(READ "${stamp_file}" installed_signature) + if(installed_signature STREQUAL cache_signature) + message(STATUS "[alp] Using cached install for ${arg_NAME}: ${install_dir}") + list(PREPEND CMAKE_PREFIX_PATH "${install_dir}") + set(CMAKE_PREFIX_PATH "${CMAKE_PREFIX_PATH}" PARENT_SCOPE) + set(${path_var} "${install_dir}" CACHE PATH "Install path for ${arg_NAME}" FORCE) + set(${version_var} "${version_signature}" CACHE STRING "Installed commit/tag for $ + build flags" FORCE) + return() + endif() + endif() + alp_add_git_repository(${arg_NAME} URL ${arg_URL} COMMITISH ${arg_COMMITISH} DO_NOT_ADD_SUBPROJECT) set(src_dir "${${arg_NAME}_SOURCE_DIR}") - set(build_dir "${CMAKE_BINARY_DIR}/alp_external/${arg_NAME}_build") - set(install_dir "${CMAKE_BINARY_DIR}/alp_external/${arg_NAME}") file(REMOVE_RECURSE "${install_dir}") _alp_build_and_install(${arg_NAME} ${src_dir} ${build_dir} ${install_dir} ${arg_CMAKE_ARGUMENTS}) + file(WRITE "${stamp_file}" "${cache_signature}") list(PREPEND CMAKE_PREFIX_PATH "${install_dir}") set(CMAKE_PREFIX_PATH "${CMAKE_PREFIX_PATH}" PARENT_SCOPE) set(${path_var} "${install_dir}" CACHE PATH "Install path for ${arg_NAME}" FORCE) - set(${version_var} "${arg_COMMITISH}${arg_CMAKE_ARGUMENTS}" CACHE STRING "Installed commit/tag for $ + build flags" FORCE) + set(${version_var} "${version_signature}" CACHE STRING "Installed commit/tag for $ + build flags" FORCE) endfunction() - - diff --git a/cmake/SetupGDAL.cmake b/cmake/SetupGDAL.cmake index b16081ba..c1e2185c 100644 --- a/cmake/SetupGDAL.cmake +++ b/cmake/SetupGDAL.cmake @@ -46,9 +46,9 @@ function(alp_setup_gdal) -DBUILD_CSHARP_BINDINGS=OFF -DGDAL_USE_ICONV=OFF -DGDAL_USE_EXTERNAL_LIBS=OFF + "-DCMAKE_INSTALL_RPATH=\$ORIGIN/../../proj/lib" ) find_package(GDAL CONFIG REQUIRED) set(CMAKE_PREFIX_PATH "${CMAKE_PREFIX_PATH}" PARENT_SCOPE) endfunction() - diff --git a/cmake/SetupOpenCV.cmake b/cmake/SetupOpenCV.cmake index 50afb1fa..e73cbbe9 100644 --- a/cmake/SetupOpenCV.cmake +++ b/cmake/SetupOpenCV.cmake @@ -21,9 +21,15 @@ if(NOT COMMAND alp_setup_cmake_project) endif() function(alp_setup_opencv version) + set(_alp_opencv_sanitizer_arguments) + if(ALP_ENABLE_ADDRESS_SANITIZER OR ALP_ENABLE_THREAD_SANITIZER) + list(APPEND _alp_opencv_sanitizer_arguments -DOPENCV_SKIP_LINK_NO_UNDEFINED=ON) + endif() + alp_setup_cmake_project(opencv URL https://github.com/opencv/opencv.git COMMITISH ${version} CMAKE_ARGUMENTS + ${_alp_opencv_sanitizer_arguments} -DBUILD_TESTS=OFF -DBUILD_PERF_TESTS=OFF -DBUILD_EXAMPLES=OFF @@ -41,4 +47,3 @@ function(alp_setup_opencv version) ) find_package(OpenCV REQUIRED) endfunction() - diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index c9efb5aa..05452981 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -11,42 +11,6 @@ endif() option(ALP_ENABLE_OVERVIEW_READING "enable GDAL overview reading (broken with newer GDAL versions)" OFF) -# ASAN & TSAN -option(ALP_ENABLE_THREAD_SANITIZER "compiles with thread sanitizer enabled" OFF) -option(ALP_ENABLE_ADDRESS_SANITIZER "compiles with address sanitizer enabled" OFF) - -if(ALP_ENABLE_ADDRESS_SANITIZER) - if(ALP_ENABLE_THREAD_SANITIZER) - message(FATAL_ERROR "Cannot enable both AddressSanitizer and ThreadSanitizer") - endif() -endif() - -if(ALP_ENABLE_ADDRESS_SANITIZER) - message(STATUS "AddressSanitizer enabled") - if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") - set(ASAN_FLAGS -fsanitize=address -fsanitize=leak -fsanitize=undefined -fno-omit-frame-pointer -g) - add_compile_options(${ASAN_FLAGS}) - add_link_options(${ASAN_FLAGS}) - else() - message(WARNING "ALP_ENABLE_ADDRESS_SANITIZER set but compiler is not GCC/Clang; ignoring") - endif() -endif() - -if(ALP_ENABLE_THREAD_SANITIZER) - if(CMAKE_BUILD_TYPE STREQUAL "Release") - message(WARNING "ThreadSanitizer is not recommended with Release builds. " - "Use Debug or RelWithDebInfo for accurate race detection.") - endif() - message(STATUS "ThreadSanitizer enabled") - if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") - set(TSAN_FLAGS -fsanitize=thread -fno-omit-frame-pointer -fno-optimize-sibling-calls -g) - add_compile_options(${TSAN_FLAGS}) - add_link_options(${TSAN_FLAGS}) - else() - message(WARNING "ALP_ENABLE_THREAD_SANITIZER set but compiler is not GCC/Clang; ignoring") - endif() -endif() - set(ALP_RELEASE_OPT_LEVEL "3" CACHE STRING "Optimization level (0=O0,1=O1,2=O2,3=O3)") if (CMAKE_BUILD_TYPE STREQUAL "Release") @@ -54,20 +18,22 @@ if (CMAKE_BUILD_TYPE STREQUAL "Release") set(CMAKE_CXX_FLAGS_RELEASE "-O${ALP_RELEASE_OPT_LEVEL}") endif() -find_package(TBB REQUIRED) find_package(ZLIB REQUIRED) find_package(CURL REQUIRED) -alp_add_git_repository(fmt URL https://github.com/fmtlib/fmt COMMITISH 10.2.1) +alp_add_git_repository(fmt URL https://github.com/fmtlib/fmt COMMITISH 12.2.0) alp_add_git_repository(cli11 URL https://github.com/CLIUtils/CLI11.git COMMITISH v2.6.1) alp_add_git_repository(eigen URL https://gitlab.com/libeigen/eigen.git COMMITISH 3.4.0 DO_NOT_ADD_SUBPROJECT) add_library(Eigen3 INTERFACE) target_include_directories(Eigen3 INTERFACE SYSTEM ${eigen_SOURCE_DIR}) -alp_setup_cmake_project(boost URL https://github.com/boostorg/boost.git COMMITISH "boost-1.88.0" CMAKE_ARGUMENTS -DBOOST_ENABLE_PYTHON=OFF -DBUILD_TESTING=OFF "-DBOOST_INCLUDE_LIBRARIES='graph\;multiprecision\;heap\;format'") +alp_setup_cmake_project(boost URL https://github.com/boostorg/boost.git COMMITISH "boost-1.88.0" CMAKE_ARGUMENTS -DBOOST_ENABLE_PYTHON=OFF -DBUILD_TESTING=OFF "-DBOOST_INCLUDE_LIBRARIES='graph\;multiprecision\;heap\;format\;logic'") find_package(Boost CONFIG REQUIRED) +alp_setup_cmake_project(tbb URL https://github.com/uxlfoundation/oneTBB.git COMMITISH v2023.1.0 CMAKE_ARGUMENTS -DTBB_TEST=OFF) +find_package(TBB REQUIRED) + include(../cmake/SetupGDAL.cmake) alp_setup_gdal(GDAL_VERSION v3.10.3 PROJ_VERSION 9.6.0) @@ -95,10 +61,10 @@ add_library(tl_expected INTERFACE) target_include_directories(tl_expected INTERFACE SYSTEM ${tl_expected_SOURCE_DIR}/include) set(SPDLOG_FMT_EXTERNAL ON CACHE BOOL "SPDLOG_FMT_EXTERNAL" FORCE) -alp_add_git_repository(spdlog URL https://github.com/gabime/spdlog.git COMMITISH v1.13.0) +alp_add_git_repository(spdlog URL https://github.com/gabime/spdlog.git COMMITISH v1.17.0) add_compile_definitions(SPDLOG_ACTIVE_LEVEL=SPDLOG_LEVEL_TRACE) -alp_add_git_repository(zpp_bits URL https://github.com/eyalz800/zpp_bits.git COMMITISH v4.5 DO_NOT_ADD_SUBPROJECT) +alp_add_git_repository(zpp_bits URL https://github.com/eyalz800/zpp_bits.git COMMITISH v4.7.2 DO_NOT_ADD_SUBPROJECT) add_library(zpp_bits INTERFACE) target_include_directories(zpp_bits INTERFACE SYSTEM ${zpp_bits_SOURCE_DIR}) diff --git a/src/browser/core/Buffer.cpp b/src/browser/core/Buffer.cpp index 84c82d4c..1de7cffc 100644 --- a/src/browser/core/Buffer.cpp +++ b/src/browser/core/Buffer.cpp @@ -1,7 +1,7 @@ #include "Buffer.h" #include -Buffer::Buffer(GLenum target = GL_ARRAY_BUFFER, GLenum usage = GL_STATIC_DRAW) : m_target(target), m_usage(usage) { +Buffer::Buffer(GLenum target, GLenum usage) : m_target(target), m_usage(usage) { glCreateBuffers(1, &m_handle); } diff --git a/src/dag_builder/dag_node.h b/src/dag_builder/dag_node.h index 5bb44598..13970d72 100644 --- a/src/dag_builder/dag_node.h +++ b/src/dag_builder/dag_node.h @@ -37,7 +37,7 @@ std::vector> build_parent_map(const std::vector auto serialize(Archive &archive, const dag::Id &id) { @@ -58,4 +58,4 @@ template auto serialize(Archive &archive, dag::ClusterBatch &node) { return archive(node.clustering, node.child_map); } -} +} // namespace dag diff --git a/src/dag_builder/encoded.h b/src/dag_builder/encoded.h index c1ff780c..769e567f 100644 --- a/src/dag_builder/encoded.h +++ b/src/dag_builder/encoded.h @@ -18,48 +18,56 @@ #include "mesh/io/texture.h" #include "meshopt.h" -namespace zpp::bits { - template auto serialize(Archive &archive, const TextureSet &textures) { size_t size = textures.size(); auto result = archive(size); - if (failure(result)) { + using Result = std::remove_cvref_t; + if constexpr (!std::is_same_v && !std::is_same_v) { return result; - } - - for (const auto &texture : textures) { - const mesh::io::ImageAndExt item{texture, ".jpeg"}; - result = archive(item); - if (failure(result)) { + } else { + if (zpp::bits::failure(result)) { return result; } - } - return result; + for (const auto &texture : textures) { + const mesh::io::ImageAndExt item{texture, ".jpeg"}; + result = archive(item); + if (zpp::bits::failure(result)) { + return result; + } + } + + return result; + } } template auto serialize(Archive &archive, TextureSet &textures) { size_t size; auto result = archive(size); - if (failure(result)) { + using Result = std::remove_cvref_t; + if constexpr (!std::is_same_v && !std::is_same_v) { return result; - } - - std::vector images; - images.reserve(size); - for (size_t i = 0; i < size; i++) { - mesh::io::ImageAndExt item; - result = archive(item); - if (failure(result)) { + } else { + if (zpp::bits::failure(result)) { return result; } - images.push_back(item.image); - } - textures = TextureSet(images); + std::vector images; + images.reserve(size); + for (size_t i = 0; i < size; i++) { + mesh::io::ImageAndExt item; + result = archive(item); + if (zpp::bits::failure(result)) { + return result; + } + + images.push_back(item.image); + } + textures = TextureSet(images); - return result; + return result; + } } template @@ -104,15 +112,20 @@ auto serialize(Archive &archive, Cluster &cluster) { cluster.texture_id, cluster.absolute_error); - if (failure(result)) { + using Result = std::remove_cvref_t; + if constexpr (!std::is_same_v && !std::is_same_v) { return result; - } + } else { + if (zpp::bits::failure(result)) { + return result; + } - meshopt::decode_index_buffer(cluster.local_triangles, triangle_count, encoded_triangles); - meshopt::decode_vertex_buffer(cluster.vertex_indices, vertex_count, encoded_vertex_indices); - meshopt::decode_vertex_buffer(cluster.uvs, uv_count, encoded_uvs); + meshopt::decode_index_buffer(cluster.local_triangles, triangle_count, encoded_triangles); + meshopt::decode_vertex_buffer(cluster.vertex_indices, vertex_count, encoded_vertex_indices); + meshopt::decode_vertex_buffer(cluster.uvs, uv_count, encoded_uvs); - return result; + return result; + } } template @@ -138,17 +151,20 @@ auto serialize(Archive &archive, Clustering &clustering) { clustering.clusters, clustering.textures); - if (failure(result)) { + using Result = std::remove_cvref_t; + if constexpr (!std::is_same_v && !std::is_same_v) { return result; - } + } else { + if (zpp::bits::failure(result)) { + return result; + } - meshopt::decode_vertex_buffer(clustering.positions, vertex_count, encoded_positions); + meshopt::decode_vertex_buffer(clustering.positions, vertex_count, encoded_positions); - return result; + return result; + } } -} // namespace zpp::bits - inline tl::expected save_clustering(const Clustering &clustering, const std::filesystem::path &path, diff --git a/src/mesh_builder/texture_assembler.h b/src/mesh_builder/texture_assembler.h index 3e795d4c..90ee1ac2 100644 --- a/src/mesh_builder/texture_assembler.h +++ b/src/mesh_builder/texture_assembler.h @@ -24,7 +24,7 @@ namespace terrainbuilder { /// Estimates the zoom level of the target bounds in relation to some reference tile given by its zoom level and bounds. -[[nodiscard]] unsigned int estimate_zoom_level( +[[nodiscard]] inline unsigned int estimate_zoom_level( const unsigned int reference_zoom_level, const radix::tile::SrsBounds reference_tile_bounds, const radix::tile::SrsBounds target_bounds) { @@ -38,7 +38,7 @@ namespace terrainbuilder { /// Find all the tiles needed to construct the texture for the given target bounds under the root tile. /// The result tiles are ordered in such a way that they can be sequentially written to a texture. /// Larger tiles are only included if they are not covered by smaller tiles. -[[nodiscard]] std::vector find_relevant_tiles_to_splatter_in_bounds( +[[nodiscard]] inline std::vector find_relevant_tiles_to_splatter_in_bounds( /// The root tile specifying the tiles to consider. const radix::tile::Id root_tile, /// Specifes the grid used to organize the image tiles. @@ -119,7 +119,7 @@ namespace terrainbuilder { } /// Calculate the offset and size of the target bounds inside the root tile. -[[nodiscard]] radix::geometry::Aabb2ui calculate_target_image_region( +[[nodiscard]] inline radix::geometry::Aabb2ui calculate_target_image_region( /// The bounds for which texture data should be created. const radix::tile::SrsBounds target_bounds, /// The bounds of the root tile. @@ -143,7 +143,7 @@ namespace terrainbuilder { return target_image_region; } -[[nodiscard]] radix::geometry::Aabb2ui calculate_pixel_tile_bounds( +[[nodiscard]] inline radix::geometry::Aabb2ui calculate_pixel_tile_bounds( radix::tile::Id tile, const radix::tile::Id root_tile, const glm::uvec2 tile_image_pixel_size, @@ -157,7 +157,7 @@ namespace terrainbuilder { return radix::geometry::Aabb2ui(tile_position, tile_position + tile_size); } -[[nodiscard]] cv::Rect to_cv_rect(radix::geometry::Aabb2ui aabb) { +[[nodiscard]] inline cv::Rect to_cv_rect(radix::geometry::Aabb2ui aabb) { const glm::uvec2 size = aabb.size(); return cv::Rect{ static_cast(aabb.min.x), @@ -166,14 +166,14 @@ namespace terrainbuilder { static_cast(size.y) }; } -[[nodiscard]] cv::Size to_cv_size(glm::uvec2 vec) { +[[nodiscard]] inline cv::Size to_cv_size(glm::uvec2 vec) { return cv::Size{ static_cast(vec.x), static_cast(vec.y) }; } -void copy_paste_image( +inline void copy_paste_image( cv::Mat &target, const cv::Mat &source, const radix::geometry::Aabb2i target_bounds, @@ -254,7 +254,7 @@ void copy_paste_image( final_source.copyTo(target(target_rect)); } -void copy_paste_image( +inline void copy_paste_image( cv::Mat &target, const cv::Mat &source, const glm::ivec2 target_position, @@ -264,7 +264,7 @@ void copy_paste_image( } namespace { -std::optional try_get_tile_path(const radix::tile::Id tile, const TileProvider &tile_provider) { +inline std::optional try_get_tile_path(const radix::tile::Id tile, const TileProvider &tile_provider) { const TilePathProvider *tile_path_provider = dynamic_cast(&tile_provider); if (tile_path_provider != nullptr) { return tile_path_provider->get_tile_path(tile).value(); @@ -273,7 +273,7 @@ std::optional try_get_tile_path(const radix::tile::Id til } } -[[nodiscard]] cv::Mat splatter_tiles_to_texture( +[[nodiscard]] inline cv::Mat splatter_tiles_to_texture( const radix::tile::Id root_tile, /// Specifes the grid used to organize the image tiles. const ctb::Grid &grid, @@ -358,7 +358,7 @@ std::optional try_get_tile_path(const radix::tile::Id til } /// Creates a texture for the given region. -[[nodiscard]] std::optional assemble_texture_from_tiles( +[[nodiscard]] inline std::optional assemble_texture_from_tiles( /// Specifes the grid used to organize the image tiles. const ctb::Grid &grid, /// Specifies the srs the target bounds are in. diff --git a/src/mesh_simplify/cli.h b/src/mesh_simplify/cli.h index 147290a1..ea102601 100644 --- a/src/mesh_simplify/cli.h +++ b/src/mesh_simplify/cli.h @@ -42,11 +42,11 @@ namespace cli { } template - Args cli::parse(const std::span args) { + Args parse(const std::span args) { return _parse(args); } template - Args cli::parse(const std::span args) { + Args parse(const std::span args) { return _parse(args); } } diff --git a/src/sf_merger/merge.h b/src/sf_merger/merge.h index 26a674be..ea3a1b18 100644 --- a/src/sf_merger/merge.h +++ b/src/sf_merger/merge.h @@ -124,7 +124,7 @@ class Merger { case get_index(Status::left_status, Status::right_status): { \ merge::NodeData left_node(id, this->_left); \ merge::NodeData right_node(id, this->_right); \ - return this->_visitor.template visit(id, left_node, right_node, ctx); \ + return this->_visitor.template visit(id, left_node, right_node, ctx); \ } ALP_GENERATE_CASE(Missing, Missing); diff --git a/src/sf_merger/merge/visitor/Masked.h b/src/sf_merger/merge/visitor/Masked.h index a577fde0..04580b03 100644 --- a/src/sf_merger/merge/visitor/Masked.h +++ b/src/sf_merger/merge/visitor/Masked.h @@ -50,11 +50,11 @@ class Masked { const NodeData &left, const NodeData &right, const Context &ctx) { - if constexpr (left.status() == Status::Inner || right.status() == Status::Inner) { + if constexpr (LeftStatus == Status::Inner || RightStatus == Status::Inner) { UNREACHABLE(); } - if constexpr (left.status() == Status::Missing && right.status() == Status::Missing) { + if constexpr (LeftStatus == Status::Missing && RightStatus == Status::Missing) { DEBUG_ASSERT(!(ctx.has_left_parent && ctx.has_right_parent)); if (!ctx.has_left_parent && !ctx.has_right_parent) { return Ignore{}; @@ -62,7 +62,7 @@ class Masked { } // If the parent mask is empty, we can just return whatevers on the left - if constexpr (left.status() != Status::Missing) { + if constexpr (LeftStatus != Status::Missing) { if (ctx.mask.mesh.is_empty()) { return Unchanged{Source::Left}; } @@ -73,20 +73,20 @@ class Masked { MeshMask mask(mesh::clip_on_bounds_and_cap(ctx.mask.mesh, bounds)); // Same when the current mask is empty - if constexpr (left.status() != Status::Missing) { + if constexpr (LeftStatus != Status::Missing) { if (mask.mesh.is_empty()) { return Unchanged{Source::Left}; } } // If we have two leaf nodes we can directly merge them. - if constexpr (left.status() == Status::Leaf && right.status() == Status::Leaf) { + if constexpr (LeftStatus == Status::Leaf && RightStatus == Status::Leaf) { return this->merge_meshes(left.mesh(), right.mesh(), true, true, mask); } // If we have a left leaf we either directly return it (after clipping) // or merge if right has a non-missing parent - if constexpr (left.status() == Status::Leaf && right.status() == Status::Missing) { + if constexpr (LeftStatus == Status::Leaf && RightStatus == Status::Missing) { if (ctx.has_right_parent) { return this->merge_meshes(left.mesh(), right.mesh().value(), true, false, mask); } @@ -104,7 +104,7 @@ class Masked { // If we have a right leaf we either directly return it (after clipping) // or merge if left has a non-missing parent - if constexpr (left.status() == Status::Missing && right.status() == Status::Leaf) { + if constexpr (LeftStatus == Status::Missing && RightStatus == Status::Leaf) { if (ctx.has_left_parent) { return this->merge_meshes(left.mesh().value(), right.mesh(), false, true, mask); } @@ -120,7 +120,7 @@ class Masked { } } - if constexpr (left.status() == Status::Missing && right.status() == Status::Missing) { + if constexpr (LeftStatus == Status::Missing && RightStatus == Status::Missing) { DEBUG_ASSERT(ctx.has_left_parent || ctx.has_right_parent); if (ctx.has_left_parent) { auto result = clip_on_mask(left.mesh().value(), mask, false); @@ -140,12 +140,12 @@ class Masked { } } - if constexpr (left.status() == Status::Virtual || right.status() == Status::Virtual) { + if constexpr (LeftStatus == Status::Virtual || RightStatus == Status::Virtual) { return Recurse{ Context{ .mask = mask, - .has_left_parent = ctx.has_left_parent || left.status() == Status::Leaf, - .has_right_parent = ctx.has_right_parent || right.status() == Status::Leaf}}; + .has_left_parent = ctx.has_left_parent || LeftStatus == Status::Leaf, + .has_right_parent = ctx.has_right_parent || RightStatus == Status::Leaf}}; } UNREACHABLE(); diff --git a/src/sf_merger/merge/visitor/Simple.h b/src/sf_merger/merge/visitor/Simple.h index 9dcec6a1..531cd686 100644 --- a/src/sf_merger/merge/visitor/Simple.h +++ b/src/sf_merger/merge/visitor/Simple.h @@ -26,15 +26,15 @@ class Simple { const NodeData &right, const Context& ctx) { - if constexpr (right.status() == Status::Missing) { + if constexpr (RightStatus == Status::Missing) { return Unchanged{Source::Left}; } - if constexpr (left.status() == Status::Missing) { + if constexpr (LeftStatus == Status::Missing) { return Unchanged{Source::Right}; } - if constexpr (left.status() == Status::Leaf && right.status() == Status::Leaf) { + if constexpr (LeftStatus == Status::Leaf && RightStatus == Status::Leaf) { return merge_meshes(right.mesh(), left.mesh()); } @@ -62,4 +62,4 @@ class Simple { static_assert(Visitor); -} \ No newline at end of file +} diff --git a/src/terrainlib/Dataset.cpp b/src/terrainlib/Dataset.cpp index fce50853..6542e687 100644 --- a/src/terrainlib/Dataset.cpp +++ b/src/terrainlib/Dataset.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include @@ -35,9 +36,17 @@ static GDALDataset *open_gdal_dataset(const std::filesystem::path &path, unsigned int flags) { initialize_gdal_once(); const std::string path_str = path.string(); + static std::mutex gdal_open_mutex; + const std::lock_guard lock(gdal_open_mutex); return static_cast(GDALOpenEx(path_str.c_str(), flags, nullptr, nullptr, nullptr)); } +void GdalDatasetDeleter::operator()(GDALDataset *dataset) const { + if (dataset) { + GDALClose(dataset); + } +} + std::optional Dataset::open_raster(std::filesystem::path path) { if (GDALDataset *dataset = open_gdal_dataset(path, GDAL_OF_RASTER)) { return std::optional(std::move(Dataset(path, dataset))); @@ -53,7 +62,7 @@ std::optional Dataset::open_vector(std::filesystem::path path) { return std::nullopt; } std::optional> Dataset::open_shared_raster(std::filesystem::path path) { - if (GDALDataset *dataset = open_gdal_dataset(path, GDAL_OF_RASTER | GDAL_OF_SHARED | GDAL_OF_THREAD_SAFE)) { + if (GDALDataset *dataset = open_gdal_dataset(path, GDAL_OF_RASTER)) { return std::make_shared(std::move(Dataset(path, dataset))); } LOG_ERROR("Couldn't open shared raster dataset {}.\n", path); diff --git a/src/terrainlib/Dataset.h b/src/terrainlib/Dataset.h index 0e9c96e7..5f31df4d 100644 --- a/src/terrainlib/Dataset.h +++ b/src/terrainlib/Dataset.h @@ -30,6 +30,10 @@ class GDALDataset; class OGRSpatialReference; +struct GdalDatasetDeleter { + void operator()(GDALDataset *dataset) const; +}; + class Dataset { public: Dataset(std::filesystem::path path); @@ -66,6 +70,6 @@ class Dataset { private: Dataset(const std::filesystem::path path, GDALDataset *dataset); - std::unique_ptr m_gdal_dataset; + std::unique_ptr m_gdal_dataset; std::optional m_path; }; diff --git a/src/terrainlib/io/Error.h b/src/terrainlib/io/Error.h index be85f515..831c9842 100644 --- a/src/terrainlib/io/Error.h +++ b/src/terrainlib/io/Error.h @@ -54,7 +54,7 @@ struct fmt::formatter { } template - auto format(const io::Error &error, FormatContext &ctx) { + auto format(const io::Error &error, FormatContext &ctx) const { const char *name = "Unknown"; switch (error) { diff --git a/src/terrainlib/mesh/io/error.h b/src/terrainlib/mesh/io/error.h index 4dfc9cd0..40df1267 100644 --- a/src/terrainlib/mesh/io/error.h +++ b/src/terrainlib/mesh/io/error.h @@ -111,7 +111,7 @@ struct fmt::formatter { } template - auto format(const mesh::io::SaveMeshError &error, FormatContext &ctx) { + auto format(const mesh::io::SaveMeshError &error, FormatContext &ctx) const { return fmt::format_to(ctx.out(), "{}", error.description()); } }; @@ -124,7 +124,7 @@ struct fmt::formatter { } template - auto format(const mesh::io::LoadMeshError &error, FormatContext &ctx) { + auto format(const mesh::io::LoadMeshError &error, FormatContext &ctx) const { return fmt::format_to(ctx.out(), "{}", error.description()); } }; diff --git a/src/terrainlib/mesh/io/texture.h b/src/terrainlib/mesh/io/texture.h index 7edb5582..c6849014 100644 --- a/src/terrainlib/mesh/io/texture.h +++ b/src/terrainlib/mesh/io/texture.h @@ -3,6 +3,7 @@ #include #include #include +#include #include @@ -23,7 +24,7 @@ std::vector write_texture_to_encoded_buffer(const ImageAndExt &item); #include -namespace zpp::bits { +namespace mesh::io { template auto serialize(Archive &archive, const mesh::io::ImageAndExt &item) { @@ -36,12 +37,17 @@ auto serialize(Archive &archive, mesh::io::ImageAndExt &item) { std::vector encoded; auto result = archive(item.ext, encoded); - if (failure(result)) { + using Result = std::remove_cvref_t; + if constexpr (!std::is_same_v && !std::is_same_v) { return result; - } + } else { + if (zpp::bits::failure(result)) { + return result; + } - item.image = mesh::io::read_texture_from_encoded_bytes(encoded); - return result; + item.image = mesh::io::read_texture_from_encoded_bytes(encoded); + return result; + } } -} // namespace zpp::bits +} // namespace mesh::io diff --git a/src/terrainlib/mesh/manifold.h b/src/terrainlib/mesh/manifold.h index be3c380b..bf524625 100644 --- a/src/terrainlib/mesh/manifold.h +++ b/src/terrainlib/mesh/manifold.h @@ -47,7 +47,7 @@ void duplicate_non_manifold_vertices( template void duplicate_non_manifold_vertices( std::span triangles, - const uint32_t vertex_count, + uint32_t vertex_count, Duplicate &&duplicate_vertex); template diff --git a/src/terrainlib/octree/Id.h b/src/terrainlib/octree/Id.h index b73b0d71..a8468aa3 100644 --- a/src/terrainlib/octree/Id.h +++ b/src/terrainlib/octree/Id.h @@ -6,6 +6,7 @@ #include #include #include +#include #include #include @@ -283,7 +284,7 @@ struct fmt::formatter { // Format the Id object. template - auto format(const octree::Id &id, FormatContext &ctx) { + auto format(const octree::Id &id, FormatContext &ctx) const { return fmt::format_to( ctx.out(), "Id(level={}, coords=({}, {}, {}), index={})", @@ -313,7 +314,7 @@ struct hash { } // namespace std #include -namespace zpp::bits { +namespace octree { namespace { constexpr zpp::bits::errc success() { return zpp::bits::errc(std::errc()); @@ -323,19 +324,24 @@ constexpr auto serialize(auto &archive, octree::Id &id) { octree::Id::Level level; octree::Id::Index index; auto result = archive(level, index); - if (failure(result)) { + using Result = std::remove_cvref_t; + if constexpr (!std::is_same_v && !std::is_same_v) { return result; - } + } else { + if (zpp::bits::failure(result)) { + return result; + } - auto maybe_id = octree::Id::try_make(level, index); - if (!maybe_id) { - return zpp::bits::errc(std::errc::bad_message); - } + auto maybe_id = octree::Id::try_make(level, index); + if (!maybe_id) { + return zpp::bits::errc(std::errc::bad_message); + } - id = *maybe_id; - return success(); + id = *maybe_id; + return success(); + } } constexpr auto serialize(auto &archive, const octree::Id &id) { return archive(id.level(), id.index_on_level()); } -} +} // namespace octree diff --git a/src/terrainlib/octree/NodeStatus.h b/src/terrainlib/octree/NodeStatus.h index 9187bc97..9e00d97c 100644 --- a/src/terrainlib/octree/NodeStatus.h +++ b/src/terrainlib/octree/NodeStatus.h @@ -56,7 +56,7 @@ struct fmt::formatter { } template - auto format(const octree::NodeStatus &status, FormatContext &ctx) { + auto format(const octree::NodeStatus &status, FormatContext &ctx) const { switch (status) { case octree::NodeStatus::Leaf: return fmt::format_to(ctx.out(), "Leaf"); diff --git a/src/terrainlib/octree/NodeStatusOrMissing.h b/src/terrainlib/octree/NodeStatusOrMissing.h index 803ebc0c..0868cb93 100644 --- a/src/terrainlib/octree/NodeStatusOrMissing.h +++ b/src/terrainlib/octree/NodeStatusOrMissing.h @@ -2,7 +2,6 @@ #include -#include #include #include "octree/NodeStatus.h" @@ -67,10 +66,6 @@ class NodeStatusOrMissing { public: Value _value; - -public: - using serialize = zpp::bits::members<1>; - friend zpp::bits::access; }; } @@ -84,7 +79,7 @@ struct fmt::formatter { } template - auto format(const octree::NodeStatusOrMissing &status, FormatContext &ctx) { + auto format(const octree::NodeStatusOrMissing &status, FormatContext &ctx) const { switch (status) { case octree::NodeStatusOrMissing::Leaf: return fmt::format_to(ctx.out(), "Leaf"); @@ -140,4 +135,4 @@ consteval bool _verify_enum() { } static_assert(_verify_enum()); } -} \ No newline at end of file +} diff --git a/src/tile_builder/srs.h b/src/tile_builder/srs.h deleted file mode 100644 index 5032536b..00000000 --- a/src/tile_builder/srs.h +++ /dev/null @@ -1,136 +0,0 @@ -/***************************************************************************** - * Alpine Terrain Builder - * Copyright (C) 2022 alpinemaps.org - * Copyright (C) 2022 Adam Celarek - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - *****************************************************************************/ - -#ifndef SRS_H -#define SRS_H - -#include -#include -#include - -#include -#include -#include - -#include "Exception.h" -#include - -namespace srs { - -inline std::unique_ptr transformation(const OGRSpatialReference& source, const OGRSpatialReference& targetSrs) -{ - const auto data_srs = source; - auto transformer = std::unique_ptr(OGRCreateCoordinateTransformation(&data_srs, &targetSrs)); - if (!transformer) - throw Exception("Couldn't create SRS transformation"); - return transformer; -} - -// this transform is non exact, because we are only transforming the corner vertices. however, due to projection warping, a rectangle can become an trapezoid with curved edges. -inline radix::tile::SrsBounds nonExactBoundsTransform(const radix::tile::SrsBounds& bounds, const OGRSpatialReference& sourceSrs, const OGRSpatialReference& targetSrs) -{ - const auto transform = transformation(sourceSrs, targetSrs); - std::array xes = { bounds.min.x, bounds.max.x }; - std::array yes = { bounds.min.y, bounds.max.y }; - if (!transform->Transform(2, xes.data(), yes.data())) - throw Exception("nonExactBoundsTransform failed"); - return { {xes[0], yes[0]}, {xes[1], yes[1]} }; -} - -template -inline glm::tvec3 to(const OGRSpatialReference& source_srs, const OGRSpatialReference& target_srs, glm::tvec3 p) -{ - const auto transform = transformation(source_srs, target_srs); - if (!transform->Transform(1, &p.x, &p.y, &p.z)) - throw Exception("srs::to(glm::tvec3) failed"); - return p; -} - -template -inline glm::tvec3 toECEF(const OGRSpatialReference& source_srs, const glm::tvec3& p) -{ - OGRSpatialReference ecef_srs; - ecef_srs.importFromEPSG(4978); - ecef_srs.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER); - return to(source_srs, ecef_srs, p); -} - -template -inline std::vector> toECEF(const OGRSpatialReference& source_srs, std::vector> points) -{ - std::vector xes; - std::vector ys; - std::vector zs; - xes.reserve(points.size()); - ys.reserve(points.size()); - zs.reserve(points.size()); - - for (const auto& p : points) { - xes.push_back(p.x); - ys.push_back(p.y); - zs.push_back(p.z); - } - - OGRSpatialReference ecef_srs; - ecef_srs.importFromEPSG(4978); - ecef_srs.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER); - const auto transform = transformation(source_srs, ecef_srs); - if (!transform->Transform(int(points.size()), xes.data(), ys.data(), zs.data())) - throw Exception("toECEF(glm::tvec3) failed"); - - for (size_t i = 0; i < points.size(); ++i) { - points[i] = { xes[i], ys[i], zs[i] }; - } - return points; -} - -template -inline std::array, n> toECEF(const OGRSpatialReference& source_srs, std::array, n> points) -{ - std::array xes; - std::array ys; - std::array zs; - - for (size_t i = 0; i < points.size(); ++i) { - xes[i] = points[i].x; - ys[i] = points[i].y; - zs[i] = points[i].z; - } - - OGRSpatialReference ecef_srs; - ecef_srs.importFromEPSG(4978); - ecef_srs.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER); - const auto transform = transformation(source_srs, ecef_srs); - if (!transform->Transform(points.size(), xes.data(), ys.data(), zs.data())) - throw Exception("toECEF(glm::tvec3) failed"); - - for (size_t i = 0; i < points.size(); ++i) { - points[i] = { xes[i], ys[i], zs[i] }; - } - return points; -} - -template -inline std::array, 2> toECEF(const OGRSpatialReference& source_srs, const glm::tvec3& p1, const glm::tvec3& p2) -{ - return toECEF(source_srs, { p1, p2 }); -} -} - -#endif // SRS_H diff --git a/unittests/terrainlib/mesh_holes.cpp b/unittests/terrainlib/mesh_holes.cpp index e9d158d0..76805864 100644 --- a/unittests/terrainlib/mesh_holes.cpp +++ b/unittests/terrainlib/mesh_holes.cpp @@ -1,6 +1,7 @@ #include #include +#include #include #include "../catch2_helpers.h" diff --git a/unittests/tilebuilder/dataset_reading.cpp b/unittests/tilebuilder/dataset_reading.cpp index fabbf5e2..b14f0e5e 100644 --- a/unittests/tilebuilder/dataset_reading.cpp +++ b/unittests/tilebuilder/dataset_reading.cpp @@ -18,6 +18,7 @@ *****************************************************************************/ #include +#include #include #include #include @@ -30,6 +31,22 @@ using namespace radix; +namespace { +void require_projection_available(const Dataset& dataset, const OGRSpatialReference& target_srs) +{ + const auto dataset_srs = dataset.srs(); + const auto source_bounds = dataset.bounds(); + + std::shared_ptr transform; + REQUIRE_NOTHROW(transform = srs::transformation(dataset_srs, target_srs)); + REQUIRE(transform != nullptr); + + std::array xs = { (source_bounds.min.x + source_bounds.max.x) / 2.0 }; + std::array ys = { (source_bounds.min.y + source_bounds.max.y) / 2.0 }; + REQUIRE(transform->Transform(static_cast(xs.size()), xs.data(), ys.data())); +} +} + TEST_CASE("reading") { const std::vector at100m = { @@ -95,8 +112,9 @@ TEST_CASE("reading") srs.importFromEPSG(test_srs); srs.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER); - const auto srs_bounds = srs::nonExactBoundsTransform(geodetic_bounds, geodetic_srs, srs); + const auto srs_bounds = srs::non_exact_bounds_transform(geodetic_bounds, geodetic_srs, srs); + require_projection_available(*dataset, srs); const DatasetReader reader(dataset, srs, 1); if (ATB_UNITTESTS_DEBUG_IMAGES) { const auto heights = reader.read(srs_bounds, 1000, 1000); @@ -148,6 +166,7 @@ TEST_CASE("reading") auto [test_name, datasets, ref_bounds, render_width, render_height, max_abs_diff, max_mse] = test; const auto ref_dataset = Dataset::open_shared_raster(ATB_TEST_DATA_DIR + std::string(datasets.front())).value(); + require_projection_available(*ref_dataset, geodetic_srs); const auto ref_reader = DatasetReader(ref_dataset, geodetic_srs, 1); const auto ref_heights = ref_reader.read(ref_bounds, render_width, render_height); if (ATB_UNITTESTS_DEBUG_IMAGES) @@ -155,6 +174,7 @@ TEST_CASE("reading") for (std::string dataset_name : datasets) { const auto dataset = Dataset::open_shared_raster(ATB_TEST_DATA_DIR + std::string(dataset_name)).value(); + require_projection_available(*dataset, geodetic_srs); const auto reader = DatasetReader(dataset, geodetic_srs, 1); const auto heights = reader.read(ref_bounds, render_width, render_height); @@ -205,7 +225,7 @@ TEST_CASE("reading") const auto pixel_width = low_res_ds->pixelWidthIn(srs); const auto pixel_height = low_res_ds->pixelHeightIn(srs); - auto srs_bounds = srs::nonExactBoundsTransform(low_res_ds->bounds(), low_res_ds->srs(), srs); + auto srs_bounds = srs::non_exact_bounds_transform(low_res_ds->bounds(), low_res_ds->srs(), srs); srs_bounds.min = { srs_bounds.min.x + border * pixel_width, srs_bounds.min.y + border * pixel_height }; srs_bounds.max = { srs_bounds.max.x - border * pixel_width, srs_bounds.max.y - border * pixel_height }; @@ -251,7 +271,7 @@ TEST_CASE("reading") const auto srs = low_res_ds->srs(); const auto low_res_reader = DatasetReader(low_res_ds, srs, 1); const auto high_res_reader = DatasetReader(high_res_ds, srs, 1); - const auto srs_bounds = srs::nonExactBoundsTransform(radix::tile::SrsBounds{{9.5, 46.4}, {17.1, 49.0}}, geodetic_srs, srs); + const auto srs_bounds = srs::non_exact_bounds_transform(radix::tile::SrsBounds{{9.5, 46.4}, {17.1, 49.0}}, geodetic_srs, srs); const auto render_width = unsigned(low_res_ds->widthInPixels(srs_bounds, srs)); const auto render_height = unsigned(low_res_ds->heightInPixels(srs_bounds, srs)); @@ -293,7 +313,7 @@ TEST_CASE("reading") const auto srs = low_res_ds->srs(); const auto low_res_reader = DatasetReader(low_res_ds, srs, 1); const auto high_res_reader = DatasetReader(high_res_ds, srs, 1); - const auto srs_bounds = srs::nonExactBoundsTransform(radix::tile::SrsBounds{{9.5, 46.4}, {17.1, 49.0}}, geodetic_srs, srs); + const auto srs_bounds = srs::non_exact_bounds_transform(radix::tile::SrsBounds{{9.5, 46.4}, {17.1, 49.0}}, geodetic_srs, srs); const auto render_width = unsigned(low_res_ds->widthInPixels(srs_bounds, srs)) / 10; const auto render_height = unsigned(low_res_ds->heightInPixels(srs_bounds, srs)) / 10; diff --git a/unittests/tilebuilder/parallel_tile_generator.cpp b/unittests/tilebuilder/parallel_tile_generator.cpp index ade4164e..350c046c 100644 --- a/unittests/tilebuilder/parallel_tile_generator.cpp +++ b/unittests/tilebuilder/parallel_tile_generator.cpp @@ -29,24 +29,24 @@ using namespace radix; TEST_CASE("parallel tile generator") { std::atomic tile_counter = 0; + std::atomic validation_error_counter = 0; std::filesystem::remove_all("./unittest_tiles/"); class MockTileWriter : public ParallelTileWriterInterface { std::atomic* m_tile_counter = nullptr; + std::atomic* m_validation_error_counter = nullptr; public: - MockTileWriter(std::atomic* tile_counter) + MockTileWriter(std::atomic* tile_counter, std::atomic* validation_error_counter) : ParallelTileWriterInterface(radix::tile::Border::No, "empty") , m_tile_counter(tile_counter) + , m_validation_error_counter(validation_error_counter) { } void write(const std::string& file_path, const radix::tile::Descriptor& tile, const HeightData& heights) const override { - CHECK(!file_path.empty()); - CHECK(tile.gridSize == 256); - CHECK(heights.width() == 256); - CHECK(heights.height() == 256); - REQUIRE(m_tile_counter != nullptr); + if (file_path.empty() || tile.gridSize != 256 || heights.width() != 256 || heights.height() != 256) + (*m_validation_error_counter)++; (*m_tile_counter)++; std::ofstream ofs(file_path); @@ -55,11 +55,12 @@ TEST_CASE("parallel tile generator") }; std::filesystem::path base_path = "./unittest_tiles/"; - auto generator = ParallelTileGenerator::make(ATB_TEST_DATA_DIR "/austria/at_mgi.tif", ctb::Grid::Srs::SphericalMercator, radix::tile::Scheme::Tms, std::make_unique(&tile_counter), base_path); + auto generator = ParallelTileGenerator::make(ATB_TEST_DATA_DIR "/austria/at_mgi.tif", ctb::Grid::Srs::SphericalMercator, radix::tile::Scheme::Tms, std::make_unique(&tile_counter, &validation_error_counter), base_path); generator.setWarnOnMissingOverviews(false); SECTION("dataset tiles only") { generator.process({ 0, 7 }); + CHECK(validation_error_counter == 0); CHECK(tile_counter == 27); CHECK(std::filesystem::exists(base_path / "0" / "0" / "0.empty")); CHECK(std::filesystem::exists(base_path / "1" / "1" / "1.empty")); @@ -72,6 +73,7 @@ TEST_CASE("parallel tile generator") SECTION("world wide tiles") { generator.process({ 0, 2 }, false, true); + CHECK(validation_error_counter == 0); CHECK(tile_counter == 21); CHECK(std::filesystem::exists(base_path / "0" / "0" / "0.empty"));