diff --git a/.gitmodules b/.gitmodules index aef07fd..bc20572 100644 --- a/.gitmodules +++ b/.gitmodules @@ -9,4 +9,4 @@ url = https://github.com/curl/curl [submodule "oss-internship-2020/gdal/gdal"] path = oss-internship-2020/gdal/gdal - url = https://github.com/OSGeo/gdal/tree/master/gdal + url = https://github.com/OSGeo/gdal/ diff --git a/oss-internship-2020/gdal/raster_to_gtiff/CMakeLists.txt b/oss-internship-2020/gdal/raster_to_gtiff/CMakeLists.txt new file mode 100644 index 0000000..95560fb --- /dev/null +++ b/oss-internship-2020/gdal/raster_to_gtiff/CMakeLists.txt @@ -0,0 +1,103 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +cmake_minimum_required(VERSION 3.10) + +project(GDALSandbox CXX C) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED True) + +set(SAPI_ROOT "" CACHE PATH "Path to the Sandboxed API source tree") +# cmake .. -G Ninja -DSAPI_ROOT=$HOME/sapi_root + +set(SAPI_ENABLE_EXAMPLES OFF CACHE BOOL "") +set(SAPI_ENABLE_TESTS OFF CACHE BOOL "") +add_subdirectory("${SAPI_ROOT}" + "${CMAKE_BINARY_DIR}/sandboxed-api-build" + # Omit this to have the full Sandboxed API in IDE + EXCLUDE_FROM_ALL) + +add_library(libgdal STATIC IMPORTED) +set_property(TARGET libgdal PROPERTY IMPORTED_LOCATION + "${CMAKE_CURRENT_SOURCE_DIR}/lib/libgdal.a") + + +# TODO: Build PROJ statically and link it as gdal +# TODO: Use environment variables to specify path prefix to libproj and gdal header +# TODO: Add environment variable to enable tests + +target_link_libraries(libgdal INTERFACE + crypto expat jpeg + /usr/local/lib/libproj.so + sqlite3 tiff z pthread m rt dl curl) + +add_sapi_library(gdal_sapi + FUNCTIONS + GDALOpen + GDALAllRegister + GDALGetDatasetDriver + GDALCreate + GDALGetDriverByName + GDALGetRasterBand + GDALSetRasterColorInterpretation + GDALSetProjection + GDALSetGeoTransform + GDALSetRasterNoDataValue + GDALRasterIO + GDALClose + + INPUTS "../gdal/gdal/gcore/gdal.h" + LIBRARY libgdal + LIBRARY_NAME gdal + + NAMESPACE "gdal::sandbox" +) + +target_include_directories(gdal_sapi INTERFACE + "${PROJECT_BINARY_DIR}" +) + +add_library(data_retriever STATIC + get_raster_data.h get_raster_data.cc +) + +target_link_libraries(data_retriever + libgdal +) + +add_executable(raster_to_gtiff + raster_to_gtiff.cc +) + +target_link_libraries(raster_to_gtiff + data_retriever + gdal_sapi + sapi::sapi +) + +find_package(GTest REQUIRED) +include_directories(${GTEST_INCLUDE_DIRS}) + +add_executable(tests tests.cc) +target_link_libraries(tests + PRIVATE + gdal_sapi + data_retriever + sapi::sapi + sandbox2::temp_file + sandbox2::fileops + ${GTEST_LIBRARIES} + ${GTEST_MAIN_LIBRARIES} +) diff --git a/oss-internship-2020/gdal/raster_to_gtiff/README.md b/oss-internship-2020/gdal/raster_to_gtiff/README.md new file mode 100644 index 0000000..e69de29 diff --git a/oss-internship-2020/gdal/raster_to_gtiff/gdal_sandbox.h b/oss-internship-2020/gdal/raster_to_gtiff/gdal_sandbox.h new file mode 100644 index 0000000..69a57af --- /dev/null +++ b/oss-internship-2020/gdal/raster_to_gtiff/gdal_sandbox.h @@ -0,0 +1,67 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef GDAL_SANDBOX_H_ +#define GDAL_SANDBOX_H_ + +#include + +#include + +#include "gdal_sapi.sapi.h" +#include "sandboxed_api/sandbox2/util/fileops.h" + +namespace gdal::sandbox { + +class GdalSapiSandbox : public gdalSandbox { + public: + GdalSapiSandbox(std::string path) + : gdalSandbox(), + path_(std::move(path)) + {} + + std::unique_ptr ModifyPolicy( + sandbox2::PolicyBuilder*) override { + + return sandbox2::PolicyBuilder() + .AllowDynamicStartup() + .AllowRead() + .AllowSystemMalloc() + .AllowWrite() + .AllowExit() + .AllowOpen() + .AllowSyscalls({ + __NR_futex, + __NR_getdents64, // DriverRegisterAll() + __NR_lseek, // GDALCreate() + __NR_getpid, // GDALCreate() + __NR_sysinfo, // VSI_TIFFOpen_common() + __NR_prlimit64, // CPLGetUsablePhysicalRAM() + __NR_ftruncate, // GTiffDataset::FillEmptyTiles() + __NR_unlink, // GDALDriver::Delete() + }) + // TODO: Deal with proj.db so you don't need to specify exact path ih the policy + .AddFile("/usr/local/share/proj/proj.db") // proj.db is required + .AddDirectory("/usr/local/lib") // To add libproj.so.19.1.1 + .AddDirectory(path_, /*is_ro=*/false) + .BuildOrDie(); + } + + private: + std::string path_; +}; + +} // namespace gdal::sandbox + +#endif // GDAL_SANDBOX_H_ diff --git a/oss-internship-2020/gdal/raster_to_gtiff/get_raster_data.cc b/oss-internship-2020/gdal/raster_to_gtiff/get_raster_data.cc new file mode 100644 index 0000000..c145dd1 --- /dev/null +++ b/oss-internship-2020/gdal/raster_to_gtiff/get_raster_data.cc @@ -0,0 +1,128 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "get_raster_data.h" + +#include +#include + +#include "gdal.h" + +namespace gdal::sandbox::tests::parser { + +namespace { + +inline constexpr int kGeoTransformSize = 6; + +void PrintInformationAboutDataset(GDALDatasetH dataset, GDALDriverH driver) { + double adfGeoTransform[6]; + printf("Driver: %s/%s\n", + GDALGetDriverShortName(driver), + GDALGetDriverLongName(driver)); + printf("Size is %dx%dx%d\n", + GDALGetRasterXSize(dataset), + GDALGetRasterYSize(dataset), + GDALGetRasterCount(dataset) ); + if(GDALGetProjectionRef(dataset) != NULL ) + printf("Projection is `%s'\n", GDALGetProjectionRef(dataset)); + if(GDALGetGeoTransform(dataset, adfGeoTransform) == CE_None) + { + printf("Origin = (%.6f,%.6f)\n", + adfGeoTransform[0], adfGeoTransform[3]); + printf("Pixel Size = (%.6f,%.6f)\n", + adfGeoTransform[1], adfGeoTransform[5]); + } +} + +} // namespace + +RasterDataset GetRasterBandsFromFile(const std::string& filename) { + GDALAllRegister(); + GDALDatasetH dataset = GDALOpen(filename.data(), GA_ReadOnly); + GDALDriverH driver = GDALGetDatasetDriver(dataset); + + RasterDataset result_dataset = { + GDALGetRasterXSize(dataset), + GDALGetRasterYSize(dataset) + }; + + if (GDALGetProjectionRef(dataset) != nullptr) { + result_dataset.wkt_projection = std::string(GDALGetProjectionRef(dataset)); + } + + std::vector geo_transform(kGeoTransformSize, 0.0); + + if (GDALGetGeoTransform(dataset, geo_transform.data()) == CE_None) { + result_dataset.geo_transform = std::move(geo_transform); + } + + int bands_count = GDALGetRasterCount(dataset); + + std::vector bands_data; + bands_data.reserve(bands_count); + + for (int i = 1; i <= bands_count; ++i) { + GDALRasterBandH band = GDALGetRasterBand(dataset, i); + int width = GDALGetRasterBandXSize(band); + int height = GDALGetRasterBandYSize(band); + + std::unique_ptr no_data_result = nullptr; + double no_data_value = GDALGetRasterNoDataValue(band, no_data_result.get()); + std::optional no_data_value_holder = + no_data_result.get() == nullptr ? std::nullopt + : std::make_optional(no_data_value); + + int data_type = static_cast(GDALGetRasterDataType(band)); + int color_interp = static_cast(GDALGetRasterColorInterpretation(band)); + + // Use std::variant or void* and reinterpet casts for the runtime template deduction + std::vector band_raster_data; + band_raster_data.resize(width * height); + + // GDALRasterIO with GF_Read should use the same type (GDT_Int32) + GDALRasterIO(band, GF_Read, 0, 0, width, height, band_raster_data.data(), + width, height, GDT_Int32, 0, 0); + + bands_data.push_back( + { + width, + height, + std::move(band_raster_data), + data_type, + color_interp, + std::move(no_data_value_holder) + } + ); + } + + result_dataset.bands = std::move(bands_data); + + return result_dataset; +} + +bool operator==(const RasterBandData& lhs, const RasterBandData& rhs) { + return lhs.width == rhs.width && lhs.height == rhs.height + && lhs.data == rhs.data && lhs.data_type == rhs.data_type + && lhs.color_interp == rhs.color_interp + && lhs.no_data_value == rhs.no_data_value; +} + +bool operator==(const RasterDataset& lhs, const RasterDataset& rhs) { + return lhs.width == rhs.width && lhs.height == rhs.height + && lhs.bands == rhs.bands + && lhs.wkt_projection == rhs.wkt_projection + && lhs.geo_transform == rhs.geo_transform; +} + +} // namespace gdal::sandbox::tests:parser diff --git a/oss-internship-2020/gdal/raster_to_gtiff/get_raster_data.h b/oss-internship-2020/gdal/raster_to_gtiff/get_raster_data.h new file mode 100644 index 0000000..aad38b2 --- /dev/null +++ b/oss-internship-2020/gdal/raster_to_gtiff/get_raster_data.h @@ -0,0 +1,47 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef GET_RASTER_DATA_H_ +#define GET_RASTER_DATA_H_ + +#include +#include +#include + +namespace gdal::sandbox::tests::parser { + +struct RasterBandData { + int width; + int height; + std::vector data; + int data_type; // Corresponds to the GDALDataType enum + int color_interp; // Corresponds to the + std::optional no_data_value; +}; + +struct RasterDataset { + int width; + int height; + std::vector bands; + std::string wkt_projection; // OpenGIS WKT format + std::vector geo_transform; +}; + +RasterDataset GetRasterBandsFromFile(const std::string& filename); +bool operator==(const RasterBandData& lhs, const RasterBandData& rhs); +bool operator==(const RasterDataset& lhs, const RasterDataset& rhs); + +} // namespace gdal::sandbox::tests::parser + +#endif // GET_RASTER_DATA_H diff --git a/oss-internship-2020/gdal/raster_to_gtiff/raster_to_gtiff.cc b/oss-internship-2020/gdal/raster_to_gtiff/raster_to_gtiff.cc new file mode 100644 index 0000000..8278d6b --- /dev/null +++ b/oss-internship-2020/gdal/raster_to_gtiff/raster_to_gtiff.cc @@ -0,0 +1,166 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include +#include + +#include "sandboxed_api/sandbox2/util/fileops.h" + +#include "gdal_sandbox.h" +#include "get_raster_data.h" + +namespace { + +#define SAPI_FAIL_IF_NOT(x, y) \ + if (!(x)) { \ + return absl::FailedPreconditionError(y); \ + } + +inline constexpr absl::string_view kDriverName = "GTiff"; + +absl::Status SaveToGTiff(gdal::sandbox::tests::parser::RasterDataset bands_data, + std::string out_file) { + using namespace gdal::sandbox; + + std::string output_data_folder = ""; + + if (!sandbox2::file_util::fileops::RemoveLastPathComponent(out_file, + &output_data_folder)) { + return absl::FailedPreconditionError("Error getting output file directory"); + } + + GdalSapiSandbox sandbox(output_data_folder); + SAPI_RETURN_IF_ERROR(sandbox.Init()); + gdalApi api(&sandbox); + SAPI_RETURN_IF_ERROR(api.GDALAllRegister()); + + sapi::v::ConstCStr driver_name_ptr(kDriverName.data()); + + SAPI_ASSIGN_OR_RETURN(sapi::StatusOr driver, + api.GDALGetDriverByName(driver_name_ptr.PtrBefore())); + + SAPI_FAIL_IF_NOT(driver.value() != nullptr, + "Error getting GTiff driver"); + sapi::v::RemotePtr driver_ptr(driver.value()); + + sapi::v::ConstCStr out_file_full_path_ptr(out_file.data()); + sapi::v::NullPtr create_options; + + GDALDataType type = bands_data.bands.size() > 0 ? + static_cast(bands_data.bands[0].data_type) + : GDALDataType::GDT_Unknown; + + SAPI_ASSIGN_OR_RETURN(sapi::StatusOr dataset, + api.GDALCreate(&driver_ptr, out_file_full_path_ptr.PtrBefore(), + bands_data.width, bands_data.height, bands_data.bands.size(), type, + &create_options)); + + SAPI_FAIL_IF_NOT(dataset.value(), + "Error creating dataset"); + sapi::v::RemotePtr dataset_ptr(dataset.value()); + + for (int i = 0; i < bands_data.bands.size(); ++i) { + SAPI_ASSIGN_OR_RETURN(sapi::StatusOr band, + api.GDALGetRasterBand(&dataset_ptr, i + 1)); + SAPI_FAIL_IF_NOT(band.value() != nullptr, + "Error getting band from dataset"); + sapi::v::RemotePtr band_ptr(band.value()); + + sapi::v::Array data_array(bands_data.bands[i].data.data(), + bands_data.bands[i].data.size()); + + SAPI_ASSIGN_OR_RETURN(sapi::StatusOr result, + api.GDALRasterIO(&band_ptr, GF_Write, 0, 0, + bands_data.bands[i].width, bands_data.bands[i].height, + data_array.PtrBefore(), bands_data.bands[i].width, + bands_data.bands[i].height, GDALDataType::GDT_Int32, 0, 0)); + + SAPI_FAIL_IF_NOT(result.value() == CPLErr::CE_None, + "Error writing band to dataset"); + + SAPI_ASSIGN_OR_RETURN(result, api.GDALSetRasterColorInterpretation( + &band_ptr, static_cast( + bands_data.bands[i].color_interp))); + + SAPI_FAIL_IF_NOT(result.value() == CPLErr::CE_None, + "Error setting color interpretation"); + + if (bands_data.bands[i].no_data_value.has_value()) { + SAPI_ASSIGN_OR_RETURN(result, + api.GDALSetRasterNoDataValue(&band_ptr, + bands_data.bands[i].no_data_value.value())); + + SAPI_FAIL_IF_NOT(result.value() == CPLErr::CE_None, + "Error setting no data value for the band"); + } + } + + if (bands_data.wkt_projection.length() > 0) { + sapi::v::ConstCStr wkt_projection_ptr(bands_data.wkt_projection.data()); + SAPI_ASSIGN_OR_RETURN(sapi::StatusOr result, + api.GDALSetProjection(&dataset_ptr, wkt_projection_ptr.PtrBefore())); + SAPI_FAIL_IF_NOT(result.value() == CPLErr::CE_None, + "Error setting wkt projection"); + } + + if (bands_data.geo_transform.size() > 0) { + sapi::v::Array geo_transform_ptr(bands_data.geo_transform.data(), + bands_data.geo_transform.size()); + SAPI_ASSIGN_OR_RETURN(sapi::StatusOr result, + api.GDALSetGeoTransform(&dataset_ptr, geo_transform_ptr.PtrBefore())); + + SAPI_FAIL_IF_NOT(result.value() == CPLErr::CE_None, + "Error setting geo transform"); + } + + SAPI_RETURN_IF_ERROR(api.GDALClose(&dataset_ptr)); + + return absl::OkStatus(); +} + +void Usage() { + std::cerr << "Example application that converts raster data to GTiff" + " format inside the sandbox. Usage:\n" + "raster_to_gtiff input_filename output_filename\n" + "output_filename must be absolute" << std::endl; +} + +} // namespace + +int main(int argc, char** argv) { + using namespace gdal::sandbox; + + if (argc < 3 + || !std::filesystem::path(std::string(argv[2])).is_absolute()) { + Usage(); + return EXIT_FAILURE; + } + + std::string input_data_path = std::string(argv[1]); + std::string output_data_path = std::string(argv[2]); + + tests::parser::RasterDataset bands_data + = tests::parser::GetRasterBandsFromFile(std::move(input_data_path)); + + if (absl::Status status = + SaveToGTiff(std::move(bands_data), std::move(output_data_path)); + !status.ok()) { + std::cerr << status.ToString() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} diff --git a/oss-internship-2020/gdal/raster_to_gtiff/testdata/earth_map.tif b/oss-internship-2020/gdal/raster_to_gtiff/testdata/earth_map.tif new file mode 100644 index 0000000..79c4e3c Binary files /dev/null and b/oss-internship-2020/gdal/raster_to_gtiff/testdata/earth_map.tif differ diff --git a/oss-internship-2020/gdal/raster_to_gtiff/testdata/map.tif b/oss-internship-2020/gdal/raster_to_gtiff/testdata/map.tif new file mode 100644 index 0000000..f9d2ef0 Binary files /dev/null and b/oss-internship-2020/gdal/raster_to_gtiff/testdata/map.tif differ diff --git a/oss-internship-2020/gdal/raster_to_gtiff/testdata/map_large.tif b/oss-internship-2020/gdal/raster_to_gtiff/testdata/map_large.tif new file mode 100644 index 0000000..76c8cf3 Binary files /dev/null and b/oss-internship-2020/gdal/raster_to_gtiff/testdata/map_large.tif differ diff --git a/oss-internship-2020/gdal/raster_to_gtiff/tests.cc b/oss-internship-2020/gdal/raster_to_gtiff/tests.cc new file mode 100644 index 0000000..4f90ef5 --- /dev/null +++ b/oss-internship-2020/gdal/raster_to_gtiff/tests.cc @@ -0,0 +1,224 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include + +#include "gtest/gtest.h" +#include "sandboxed_api/transaction.h" +#include "sandboxed_api/sandbox2/util/temp_file.h" +#include "sandboxed_api/sandbox2/util/fileops.h" + +#include "gdal_sandbox.h" +#include "get_raster_data.h" + +namespace gdal::sandbox::tests { +namespace { + +inline constexpr absl::string_view kDriverName = "GTiff"; +inline constexpr absl::string_view kTempFilePrefix = "temp_data"; +inline constexpr absl::string_view kFirstTestDataPath = + "../testdata/map.tif"; +inline constexpr absl::string_view kSecondTestDataPath = + "../testdata/map_large.tif"; +inline constexpr absl::string_view kThirdTestDataPath = + "../testdata/earth_map.tif"; + +// RAII wrapper that creates temporary file and automatically unlinks it +class TempFile { + public: + explicit TempFile(absl::string_view prefix) + { + auto file_data = sandbox2::CreateNamedTempFile(prefix); + + if (file_data.ok()) { + file_data_ = std::move(file_data.value()); + } + } + + ~TempFile() { + if (file_data_.has_value()) { + unlink(file_data_.value().first.c_str()); + } + } + + bool HasValue() const { + return file_data_.has_value(); + } + + int GetFd() const { + return file_data_.value().second; + } + + std::string GetPath() const { + return file_data_.value().first; + } + + private: + std::optional> file_data_ = std::nullopt; +}; + +// Wrapper around raster to GTiff workflow +class RasterToGTiffProcessor : public sapi::Transaction { + public: + explicit RasterToGTiffProcessor(std::string out_filename, + std::string out_path, + parser::RasterDataset data, + int retry_count = 0) + : sapi::Transaction(std::make_unique(out_path)), + out_filename_(std::move(out_filename)), + out_path_(std::move(out_path)), + data_(std::move(data)) + { + set_retry_count(retry_count); + SetTimeLimit(absl::InfiniteDuration()); + } + + private: + absl::Status Main() final; + + const std::string out_filename_; + const std::string out_path_; + parser::RasterDataset data_; +}; + +absl::Status RasterToGTiffProcessor::Main() { + gdalApi api(sandbox()); + SAPI_RETURN_IF_ERROR(api.GDALAllRegister()); + + sapi::v::ConstCStr driver_name_ptr(kDriverName.data()); + + SAPI_ASSIGN_OR_RETURN(sapi::StatusOr driver, + api.GDALGetDriverByName(driver_name_ptr.PtrBefore())); + + TRANSACTION_FAIL_IF_NOT(driver.value() != nullptr, + "Error getting GTiff driver"); + sapi::v::RemotePtr driver_ptr(driver.value()); + + std::string out_file_full_path = absl::StrCat(out_path_, "/", out_filename_); + sapi::v::ConstCStr out_file_full_path_ptr(out_file_full_path.data()); + sapi::v::NullPtr create_options; + + GDALDataType type = data_.bands.size() > 0 ? + static_cast(data_.bands[0].data_type) + : GDALDataType::GDT_Unknown; + + SAPI_ASSIGN_OR_RETURN(sapi::StatusOr dataset, + api.GDALCreate(&driver_ptr, out_file_full_path_ptr.PtrBefore(), + data_.width, data_.height, data_.bands.size(), type, + &create_options)); + + TRANSACTION_FAIL_IF_NOT(dataset.value(), + "Error creating dataset"); + sapi::v::RemotePtr dataset_ptr(dataset.value()); + + for (int i = 0; i < data_.bands.size(); ++i) { + SAPI_ASSIGN_OR_RETURN(sapi::StatusOr band, + api.GDALGetRasterBand(&dataset_ptr, i + 1)); + TRANSACTION_FAIL_IF_NOT(band.value() != nullptr, + "Error getting band from dataset"); + sapi::v::RemotePtr band_ptr(band.value()); + + sapi::v::Array data_array(data_.bands[i].data.data(), + data_.bands[i].data.size()); + + SAPI_ASSIGN_OR_RETURN(sapi::StatusOr result, + api.GDALRasterIO(&band_ptr, GF_Write, 0, 0, + data_.bands[i].width, data_.bands[i].height, data_array.PtrBefore(), + data_.bands[i].width, data_.bands[i].height, GDT_Int32, 0, 0)); + + TRANSACTION_FAIL_IF_NOT(result.value() == CPLErr::CE_None, + "Error writing band to dataset"); + + SAPI_ASSIGN_OR_RETURN(result, api.GDALSetRasterColorInterpretation( + &band_ptr, static_cast( + data_.bands[i].color_interp))); + + TRANSACTION_FAIL_IF_NOT(result.value() == CPLErr::CE_None, + "Error setting color interpretation"); + + if (data_.bands[i].no_data_value.has_value()) { + SAPI_ASSIGN_OR_RETURN(result, + api.GDALSetRasterNoDataValue(&band_ptr, + data_.bands[i].no_data_value.value())); + + TRANSACTION_FAIL_IF_NOT(result.value() == CPLErr::CE_None, + "Error setting no data value for the band"); + } + } + + if (data_.wkt_projection.length() > 0) { + sapi::v::ConstCStr wkt_projection_ptr(data_.wkt_projection.data()); + SAPI_ASSIGN_OR_RETURN(sapi::StatusOr result, + api.GDALSetProjection(&dataset_ptr, wkt_projection_ptr.PtrBefore())); + TRANSACTION_FAIL_IF_NOT(result.value() == CPLErr::CE_None, + "Error setting wkt projection"); + } + + if (data_.geo_transform.size() > 0) { + sapi::v::Array geo_transform_ptr(data_.geo_transform.data(), + data_.geo_transform.size()); + SAPI_ASSIGN_OR_RETURN(sapi::StatusOr result, + api.GDALSetGeoTransform(&dataset_ptr, geo_transform_ptr.PtrBefore())); + + TRANSACTION_FAIL_IF_NOT(result.value() == CPLErr::CE_None, + "Error setting geo transform"); + } + + SAPI_RETURN_IF_ERROR(api.GDALClose(&dataset_ptr)); + + return absl::OkStatus(); +} + +} // namespace + +class TestGTiffProcessor : public testing::TestWithParam { + public: + TestGTiffProcessor() + : tempfile_(kTempFilePrefix) + {} + + protected: + const TempFile tempfile_; +}; + +TEST_P(TestGTiffProcessor, TestProcessorOnGTiffData) { + std::string filename = std::string(GetParam()); + + ASSERT_TRUE(tempfile_.HasValue()) + << "Error creating temporary output file"; + + parser::RasterDataset original_bands_data = + parser::GetRasterBandsFromFile(filename); + + RasterToGTiffProcessor processor(tempfile_.GetPath(), + sandbox2::file_util::fileops::GetCWD(), original_bands_data); + ASSERT_EQ(processor.Run(), absl::OkStatus()) + << "Error creating new GTiff dataset inside sandbox"; + + ASSERT_EQ(original_bands_data, + parser::GetRasterBandsFromFile(tempfile_.GetPath())) + << "New dataset doesn't match the original one"; +} + +INSTANTIATE_TEST_CASE_P( + GDALTests, + TestGTiffProcessor, + ::testing::Values(kFirstTestDataPath, + kSecondTestDataPath, + kThirdTestDataPath) +); + +} // namespace gdal::sandbox::tests