Updated sandbox construction logic and CMakeLists

More flexible CMake file with variables
Added logic to check whether proj.db exists and fetch it from the environment variable
This commit is contained in:
Bohdan Tyshchenko 2020-10-05 11:01:15 -07:00
parent b06d020f32
commit 5442d8c6e0
7 changed files with 137 additions and 63 deletions

View File

@ -20,28 +20,44 @@ 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(ENABLE_TESTS OFF CACHE BOOL "Enable GDAL sandbox tests")
set(GDAL_HEADER_PREFIX "/usr/local/include" CACHE PATH "Prefix of the path to gdal.h")
set(LIBGDAL_PREFIX "${CMAKE_CURRENT_SOURCE_DIR}/lib" CACHE PATH "Prefix of the path to libgdal.a")
set(LIBPROJ_PREFIX "${CMAKE_CURRENT_SOURCE_DIR}/lib" CACHE PATH "Prefix of the path to libproj.a")
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")
"${LIBGDAL_PREFIX}/libgdal.a")
add_library(libproj STATIC IMPORTED)
set_property(TARGET libproj PROPERTY IMPORTED_LOCATION
"${LIBPROJ_PREFIX}/libproj.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
message("${LIBGDAL_PREFIX}/libgdal.a")
message("${LIBPROJ_PREFIX}/libproj.a")
# TODO: Try using libgdal-dev and libgdal-proj with SAPI inside Docker or other other isolated environment
# TODO: Document both install from sources and install from package workflow
# TODO: Use environment variables to distinguish between those workflows on compile time
target_link_libraries(libgdal INTERFACE
crypto expat jpeg
/usr/local/lib/libproj.so
sqlite3 tiff z pthread m rt dl curl)
crypto
expat
jpeg
libproj
sqlite3
tiff
z
pthread
m
rt
dl
curl
)
add_sapi_library(gdal_sapi
FUNCTIONS
@ -58,7 +74,7 @@ add_sapi_library(gdal_sapi
GDALRasterIO
GDALClose
INPUTS "../gdal/gdal/gcore/gdal.h"
INPUTS "${GDAL_HEADER_PREFIX}/gdal.h"
LIBRARY libgdal
LIBRARY_NAME gdal
@ -87,17 +103,18 @@ target_link_libraries(raster_to_gtiff
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}
)
if (ENABLE_TESTS)
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}
)
endif()

View File

@ -0,0 +1,21 @@
# GDAL Raster to GeoTIFF Workflow Sandbox
This repository is an example of how Sandboxed API can be used with GDAL C Raster API to implement the creation of the GeoTIFF dataset inside the sandbox.
## Workflow details
Implemented workflow consists of a few steps:
1. Register needed drivers inside the sandbox
2. Get specific driver by name (GTiff)
3. Map output file inside the sandbox and create GeoTIFF dataset backed by this file
4. Set affine transformation coefficients if needed
5. Set projection reference string if needed
6. Write raster bands data to the dataset using RasterIO
1. Set No data value if needed
7. Clean up data and close the dataset
## Implementation details
## Build details
### Build GDAL and PROJ from sources
## Examples

View File

@ -15,6 +15,7 @@
#ifndef GDAL_SANDBOX_H_
#define GDAL_SANDBOX_H_
#include <iostream>
#include <string>
#include <syscall.h>
@ -26,11 +27,17 @@ namespace gdal::sandbox {
class GdalSapiSandbox : public gdalSandbox {
public:
GdalSapiSandbox(std::string path)
GdalSapiSandbox(std::string out_directory_path,
std::string proj_db_path,
time_t time_limit = 0)
: gdalSandbox(),
path_(std::move(path))
{}
out_directory_path_(std::move(out_directory_path)),
proj_db_path_(std::move(proj_db_path))
{
SetWallTimeLimit(time_limit).IgnoreError();
}
private:
std::unique_ptr<sandbox2::Policy> ModifyPolicy(
sandbox2::PolicyBuilder*) override {
@ -52,14 +59,16 @@ class GdalSapiSandbox : public gdalSandbox {
__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)
// Add proj path a an environment variable, check it before calling constructor
// Use default path if there is no variable
// If there is no file on the default path return an error
.AddFile(proj_db_path_) // proj.db is required for some projections
.AddDirectory(out_directory_path_, /*is_ro=*/false)
.BuildOrDie();
}
}
private:
std::string path_;
std::string out_directory_path_;
std::string proj_db_path_;
};
} // namespace gdal::sandbox

View File

@ -25,26 +25,6 @@ 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) {
@ -90,7 +70,7 @@ RasterDataset GetRasterBandsFromFile(const std::string& filename) {
std::vector<int> band_raster_data;
band_raster_data.resize(width * height);
// GDALRasterIO with GF_Read should use the same type (GDT_Int32)
// GDALRasterIO with GF_Write 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);

View File

@ -26,7 +26,7 @@ struct RasterBandData {
int height;
std::vector<int> data;
int data_type; // Corresponds to the GDALDataType enum
int color_interp; // Corresponds to the
int color_interp; // Corresponds to the GDALColorInterp enum
std::optional<double> no_data_value;
};
@ -44,4 +44,4 @@ bool operator==(const RasterDataset& lhs, const RasterDataset& rhs);
} // namespace gdal::sandbox::tests::parser
#endif // GET_RASTER_DATA_H
#endif // GET_RASTER_DATA_H_

View File

@ -13,9 +13,11 @@
// limitations under the License.
#include <iostream>
#include <cstdlib>
#include <vector>
#include <string>
#include <filesystem>
#include <optional>
#include "sandboxed_api/sandbox2/util/fileops.h"
@ -30,6 +32,22 @@ namespace {
}
inline constexpr absl::string_view kDriverName = "GTiff";
inline constexpr absl::string_view kProjDbEnvVariableName = "PROJ_PATH";
inline constexpr absl::string_view kDefaultProjDbPath
= "/usr/local/share/proj/proj.db";
std::optional<std::string> GetProjDbPath() {
const char* proj_db_path_ptr = std::getenv(kProjDbEnvVariableName.data());
std::string proj_db_path = proj_db_path_ptr == nullptr ?
std::string(kDefaultProjDbPath) : std::string(proj_db_path_ptr);
if (!std::filesystem::exists(proj_db_path)) {
return std::nullopt;
}
return proj_db_path;
}
absl::Status SaveToGTiff(gdal::sandbox::tests::parser::RasterDataset bands_data,
std::string out_file) {
@ -42,7 +60,11 @@ absl::Status SaveToGTiff(gdal::sandbox::tests::parser::RasterDataset bands_data,
return absl::FailedPreconditionError("Error getting output file directory");
}
GdalSapiSandbox sandbox(output_data_folder);
std::optional<std::string> proj_db_path = GetProjDbPath();
SAPI_FAIL_IF_NOT(proj_db_path != std::nullopt,
"Specified proj.db does not exist");
GdalSapiSandbox sandbox(output_data_folder, std::move(proj_db_path.value()));
SAPI_RETURN_IF_ERROR(sandbox.Init());
gdalApi api(&sandbox);
SAPI_RETURN_IF_ERROR(api.GDALAllRegister());
@ -111,7 +133,7 @@ absl::Status SaveToGTiff(gdal::sandbox::tests::parser::RasterDataset bands_data,
if (bands_data.wkt_projection.length() > 0) {
sapi::v::ConstCStr wkt_projection_ptr(bands_data.wkt_projection.data());
SAPI_ASSIGN_OR_RETURN(sapi::StatusOr<CPLErr> result,
api.GDALSetProjection(&dataset_ptr, wkt_projection_ptr.PtrBefore()));
api.GDALSetProjection(&dataset_ptr, wkt_projection_ptr.PtrBefore()));
SAPI_FAIL_IF_NOT(result.value() == CPLErr::CE_None,
"Error setting wkt projection");
}

View File

@ -14,6 +14,7 @@
#include <iostream>
#include <optional>
#include <filesystem>
#include <string>
#include "gtest/gtest.h"
@ -29,6 +30,9 @@ namespace {
inline constexpr absl::string_view kDriverName = "GTiff";
inline constexpr absl::string_view kTempFilePrefix = "temp_data";
inline constexpr absl::string_view kProjDbEnvVariableName = "PROJ_PATH";
inline constexpr absl::string_view kDefaultProjDbPath
= "/usr/local/share/proj/proj.db";
inline constexpr absl::string_view kFirstTestDataPath =
"../testdata/map.tif";
inline constexpr absl::string_view kSecondTestDataPath =
@ -36,6 +40,19 @@ inline constexpr absl::string_view kSecondTestDataPath =
inline constexpr absl::string_view kThirdTestDataPath =
"../testdata/earth_map.tif";
std::optional<std::string> GetProjDbPath() {
const char* proj_db_path_ptr = std::getenv(kProjDbEnvVariableName.data());
std::string proj_db_path = proj_db_path_ptr == nullptr ?
std::string(kDefaultProjDbPath) : std::string(proj_db_path_ptr);
if (!std::filesystem::exists(proj_db_path)) {
return std::nullopt;
}
return proj_db_path;
}
// RAII wrapper that creates temporary file and automatically unlinks it
class TempFile {
public:
@ -75,9 +92,11 @@ class RasterToGTiffProcessor : public sapi::Transaction {
public:
explicit RasterToGTiffProcessor(std::string out_filename,
std::string out_path,
parser::RasterDataset data,
std::string proj_db_path,
parser::RasterDataset data,
int retry_count = 0)
: sapi::Transaction(std::make_unique<GdalSapiSandbox>(out_path)),
: sapi::Transaction(std::make_unique<GdalSapiSandbox>(out_path,
std::move(proj_db_path))),
out_filename_(std::move(out_filename)),
out_path_(std::move(out_path)),
data_(std::move(data))
@ -203,8 +222,14 @@ TEST_P(TestGTiffProcessor, TestProcessorOnGTiffData) {
parser::RasterDataset original_bands_data =
parser::GetRasterBandsFromFile(filename);
std::optional<std::string> proj_db_path = GetProjDbPath();
ASSERT_TRUE(proj_db_path != std::nullopt)
<< "Specified proj.db does not exist";
RasterToGTiffProcessor processor(tempfile_.GetPath(),
sandbox2::file_util::fileops::GetCWD(), original_bands_data);
sandbox2::file_util::fileops::GetCWD(), std::move(proj_db_path.value()),
original_bands_data);
ASSERT_EQ(processor.Run(), absl::OkStatus())
<< "Error creating new GTiff dataset inside sandbox";