Skip to content

Commit

Permalink
Add a CMake EMBED_GRIDS_DIRECTORY option to embed .tif/.json files in…
Browse files Browse the repository at this point in the history
…to libproj

and also generalize the existing EMBED_RESOURCE_FILES option to embed the ITRFxxxx
resource files.

```
.. option:: EMBED_GRIDS_DIRECTORY=<directory>

    .. versionadded:: 9.6

    Embed files from <directory> ending with .tif or .json in the PROJ library itself.

    The pointed directory can potentially be the full PROJ-data package (uncompressed).
    In that case, about 6 GB of free disk and 16 GB of RAM are required to build PROJ.

    When using this parameter, EMBED_RESOURCE_FILES must be set to ON.

    If the content of the directory changes, you need to run CMake again to
    update the list of files.
```
  • Loading branch information
rouault committed Dec 13, 2024
1 parent 14f5080 commit 556d3d2
Show file tree
Hide file tree
Showing 8 changed files with 256 additions and 73 deletions.
25 changes: 23 additions & 2 deletions .github/workflows/fedora_rawhide/start.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

set -e

dnf install -y cmake clang ccache ninja-build sqlite-devel libtiff-devel libcurl-devel diffutils
dnf install -y cmake clang ccache ninja-build sqlite-devel libtiff-devel libcurl-devel diffutils wget

cd "$WORK_DIR"

Expand All @@ -19,9 +19,30 @@ ccache -s
mkdir build
cd build
CC=clang CXX=clang++ cmake .. \
-DEMBED_RESOURCE_FILES=ON -DUSE_ONLY_EMBEDDED_RESOURCE_FILES=ON -DUSE_CCACHE=ON -DPROJ_DB_CACHE_DIR=$HOME/.ccache ..
-DEMBED_RESOURCE_FILES=ON -DUSE_CCACHE=ON -DPROJ_DB_CACHE_DIR=$HOME/.ccache ..
make -j$(nproc)
ctest -j$(nproc)

# Try EMBED_GRIDS_DIRECTORY option
wget https://raw.githubusercontent.com/OSGeo/PROJ-data/refs/heads/master/us_nga/us_nga_egm96_15.tif
mkdir grids
mv us_nga_egm96_15.tif grids
CC=clang CXX=clang++ cmake .. -DEMBED_GRIDS_DIRECTORY=$PWD/grids
make -j$(nproc)
rm -rf data
echo 49 2 0 | bin/cs2cs "WGS84 + EGM96 height" EPSG:4979
echo 49 2 0 | bin/cs2cs "WGS84 + EGM96 height" EPSG:4979 | grep 44.643 >/dev/null || (echo "Expected 49dN 2dE 44.643 as a result" && /bin/false)
echo 0 0 0 | bin/cct +init=ITRF2000:ITRF96
echo 0 0 0 | bin/cct +init=ITRF2000:ITRF96 | grep 0.0067 >/dev/null || (echo "Expected 0.0067 0.0061 -0.0185 as a result" && /bin/false)

CC=clang CXX=clang++ cmake .. -DEMBED_GRIDS_DIRECTORY=$PWD/grids -DUSE_ONLY_EMBEDDED_RESOURCE_FILES=ON
make -j$(nproc)
rm -rf data
echo 49 2 0 | bin/cs2cs "WGS84 + EGM96 height" EPSG:4979
echo 49 2 0 | bin/cs2cs "WGS84 + EGM96 height" EPSG:4979 | grep 44.643 >/dev/null || (echo "Expected 49dN 2dE 44.643 as a result" && /bin/false)
echo 0 0 0 | bin/cct +init=ITRF2000:ITRF96
echo 0 0 0 | bin/cct +init=ITRF2000:ITRF96 | grep 0.0067 >/dev/null || (echo "Expected 0.0067 0.0061 -0.0185 as a result" && /bin/false)

cd ..

ccache -s
Expand Down
15 changes: 15 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,21 @@ endif()
################################################################################
# Build configured components
################################################################################

set(PROJ_DICTIONARY
world
other.extra
nad27
GL27
nad83
nad.lst
CH
ITRF2000
ITRF2008
ITRF2014
ITRF2020
)

include_directories(${PROJ_SOURCE_DIR}/src)

add_subdirectory(data)
Expand Down
17 changes: 1 addition & 16 deletions data/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,6 @@ set(PROJ_INI
proj.ini
)

set(PROJ_DICTIONARY
world
other.extra
nad27
GL27
nad83
nad.lst
CH
ITRF2000
ITRF2008
ITRF2014
ITRF2020
)

#
# gridshift file
#
Expand Down Expand Up @@ -111,14 +97,13 @@ configure_file(${CMAKE_CURRENT_SOURCE_DIR}/tests/conus "${CMAKE_CURRENT_BINARY_D
#install
#
set(ALL_DATA_FILE
${PROJ_DICTIONARY}
${GRIDSHIFT_FILES}
${SCHEMA_FILES}
${GEOTIFF_FILES}
)

if (NOT USE_ONLY_EMBEDDED_RESOURCE_FILES)
list(APPEND ALL_DATA_FILE ${PROJ_INI} ${PROJ_DB})
list(APPEND ALL_DATA_FILE ${PROJ_INI} ${PROJ_DB} ${PROJ_DICTIONARY})
endif()

install(
Expand Down
25 changes: 21 additions & 4 deletions docs/source/install.rst
Original file line number Diff line number Diff line change
Expand Up @@ -450,19 +450,36 @@ All cached entries can be viewed using ``cmake -LAH`` from a build directory.

.. versionadded:: 9.6

When ON, :file:`proj.db`, :file:`proj.ini` and ITRF resource files will be
embedded into the PROJ library.
Default is OFF for shared library builds (BUILD_SHARED_LIBS=ON), and ON
for static library builds (BUILD_SHARED_LIBS=OFF).
When ON, :file:`proj.db` and :file:`proj.ini` will be embedded into the PROJ library.

.. option:: EMBED_GRIDS_DIRECTORY=<directory>

.. versionadded:: 9.6

Embed files from <directory> ending with .tif or .json in the PROJ library itself.

The pointed directory can potentially be the full PROJ-data package (uncompressed).
In that case, about 6 GB of free disk and 16 GB of RAM are required to build PROJ.

When using this parameter, EMBED_RESOURCE_FILES must be set to ON.

If the content of the directory changes, you need to run CMake again to
update the list of files.

.. option:: USE_ONLY_EMBEDDED_RESOURCE_FILES=ON/OFF

.. versionadded:: 9.6

Even if EMBED_RESOURCE_FILES=ON, by default PROJ will still try to locate
:file:`proj.db` and :file:`proj.ini` on the file system, and fallback to the
embedded version if not found.
:file:`proj.db`, :file:`proj.ini` and ITRF resource files on the file system,
and fallback to the embedded version if not found.
By setting USE_ONLY_EMBEDDED_RESOURCE_FILES=ON, no attempt at locating
those files on the file system is made. Default is OFF.
those files on the file system is made. And if EMBED_GRIDS_DIRECTORY has also
been set, no attempt at locating grid files on the file system is made.
Default is OFF.
Users will also typically want to set EMBED_PROJ_DATA_PATH=OFF if setting
USE_ONLY_EMBEDDED_RESOURCE_FILES=OFF.

Expand Down
19 changes: 5 additions & 14 deletions src/embedded_resources.c
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
#include <stdint.h>
#include <string.h>

#include "embedded_resources.h"

#if USE_SHARP_EMBED
Expand All @@ -9,14 +12,6 @@ const unsigned char *pj_get_embedded_proj_db(unsigned int *pnSize) {
return proj_db;
}

const char *pj_get_embedded_proj_ini(unsigned int *pnSize) {
static const char proj_ini[] = {
#embed PROJ_INI
};
*pnSize = (unsigned int)sizeof(proj_ini);
return proj_ini;
}

#else

#include "file_embed/proj_db.h"
Expand All @@ -25,10 +20,6 @@ const unsigned char *pj_get_embedded_proj_db(unsigned int *pnSize) {
return proj_db_data;
}

#include "file_embed/proj_ini.h"
const char *pj_get_embedded_proj_ini(unsigned int *pnSize) {
*pnSize = proj_ini_size;
return (const char *)proj_ini_data;
}

#endif

#include "file_embed/embedded_resources.c"
4 changes: 3 additions & 1 deletion src/embedded_resources.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ extern "C" {
#endif

const unsigned char *pj_get_embedded_proj_db(unsigned int *pnSize);
const char *pj_get_embedded_proj_ini(unsigned int *pnSize);

const unsigned char *pj_get_embedded_resource(const char *filename,
unsigned int *pnSize);

#ifdef __cplusplus
}
Expand Down
127 changes: 111 additions & 16 deletions src/filemanager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,8 @@ std::string File::read_line(size_t maxLen, bool &maxLenReached,

// ---------------------------------------------------------------------------

#if !USE_ONLY_EMBEDDED_RESOURCE_FILES

#ifdef _WIN32

/* The bulk of utf8towc()/utf8fromwc() is derived from the utf.c module from
Expand Down Expand Up @@ -805,6 +807,8 @@ std::unique_ptr<File> FileStdio::open(PJ_CONTEXT *ctx, const char *filename,

#endif // _WIN32

#endif // !USE_ONLY_EMBEDDED_RESOURCE_FILES

// ---------------------------------------------------------------------------

class FileApiAdapter : public File {
Expand Down Expand Up @@ -893,6 +897,80 @@ std::unique_ptr<File> FileApiAdapter::open(PJ_CONTEXT *ctx,

// ---------------------------------------------------------------------------

#if EMBED_RESOURCE_FILES

class FileMemory : public File {
PJ_CONTEXT *m_ctx;
const unsigned char *const m_data;
const size_t m_size;
size_t m_pos = 0;

FileMemory(const FileMemory &) = delete;
FileMemory &operator=(const FileMemory &) = delete;

protected:
FileMemory(const std::string &filename, PJ_CONTEXT *ctx,
const unsigned char *data, size_t size)
: File(filename), m_ctx(ctx), m_data(data), m_size(size) {}

public:
size_t read(void *buffer, size_t sizeBytes) override;
size_t write(const void *, size_t) override;
bool seek(unsigned long long offset, int whence = SEEK_SET) override;
unsigned long long tell() override { return m_pos; }
void reassign_context(PJ_CONTEXT *ctx) override { m_ctx = ctx; }

bool hasChanged() const override { return false; }

static std::unique_ptr<File> open(PJ_CONTEXT *ctx, const char *filename,
FileAccess access,
const unsigned char *data, size_t size) {
if (access != FileAccess::READ_ONLY)
return nullptr;
return std::unique_ptr<File>(new FileMemory(filename, ctx, data, size));
}
};

size_t FileMemory::read(void *buffer, size_t sizeBytes) {
if (m_pos >= m_size)
return 0;
if (sizeBytes >= m_size - m_pos) {
const size_t bytesToCopy = m_size - m_pos;
memcpy(buffer, m_data + m_pos, bytesToCopy);
m_pos = m_size;
return bytesToCopy;
}
memcpy(buffer, m_data + m_pos, sizeBytes);
m_pos += sizeBytes;
return sizeBytes;
}

size_t FileMemory::write(const void *, size_t) {
// shouldn't happen given we have bailed out in open() in non read-only
// modes
return 0;
}

bool FileMemory::seek(unsigned long long offset, int whence) {
if (whence == SEEK_SET) {
m_pos = static_cast<size_t>(offset);
return m_pos == offset;
} else if (whence == SEEK_CUR) {
const unsigned long long newPos = m_pos + offset;
m_pos = static_cast<size_t>(newPos);
return m_pos == newPos;
} else {
if (offset != 0)
return false;
m_pos = m_size;
return true;
}
}

#endif

// ---------------------------------------------------------------------------

std::unique_ptr<File> FileManager::open(PJ_CONTEXT *ctx, const char *filename,
FileAccess access) {
if (starts_with(filename, "http://") || starts_with(filename, "https://")) {
Expand All @@ -909,11 +987,31 @@ std::unique_ptr<File> FileManager::open(PJ_CONTEXT *ctx, const char *filename,
if (ctx->fileApi.open_cbk != nullptr) {
return FileApiAdapter::open(ctx, filename, access);
}

std::unique_ptr<File> ret;
#if !(EMBED_RESOURCE_FILES && USE_ONLY_EMBEDDED_RESOURCE_FILES)
#ifdef _WIN32
return FileWin32::open(ctx, filename, access);
ret = FileWin32::open(ctx, filename, access);
#else
return FileStdio::open(ctx, filename, access);
ret = FileStdio::open(ctx, filename, access);
#endif
#endif

#if EMBED_RESOURCE_FILES
#if USE_ONLY_EMBEDDED_RESOURCE_FILES
if (!ret)
#endif
{
unsigned int size = 0;
const unsigned char *in_memory_data =
pj_get_embedded_resource(filename, &size);
if (in_memory_data) {
ret = FileMemory::open(ctx, filename, access, in_memory_data, size);
}
}
#endif

return ret;
}

// ---------------------------------------------------------------------------
Expand Down Expand Up @@ -1563,6 +1661,14 @@ static void *pj_open_lib_internal(
fname = name;
}

#if EMBED_RESOURCE_FILES
if (!fid && name[0] != '.' && name[0] != '/' && name[0] != '~') {
fid = open_file(ctx, name, mode);
if (fid)
fname = name;
}
#endif

if (fid != nullptr ||
(fid = open_file(ctx, fname.c_str(), mode)) != nullptr) {
if (out_full_filename != nullptr && out_full_filename_size > 0) {
Expand Down Expand Up @@ -1870,20 +1976,9 @@ void pj_load_ini(PJ_CONTEXT *ctx) {

ctx->iniFileLoaded = true;
std::string content;
std::unique_ptr<NS_PROJ::File> file;
#ifndef USE_ONLY_EMBEDDED_RESOURCE_FILES
file.reset(reinterpret_cast<NS_PROJ::File *>(pj_open_lib_internal(
ctx, "proj.ini", "rb", pj_open_file_with_manager, nullptr, 0)));
#endif
if (!file) {
#ifdef EMBED_RESOURCE_FILES
unsigned int content_size = 0;
const char *c_content = pj_get_embedded_proj_ini(&content_size);
content.assign(c_content, content_size);
#else
return;
#endif
}
auto file = std::unique_ptr<NS_PROJ::File>(
reinterpret_cast<NS_PROJ::File *>(pj_open_lib_internal(
ctx, "proj.ini", "rb", pj_open_file_with_manager, nullptr, 0)));
if (file) {
file->seek(0, SEEK_END);
const auto filesize = file->tell();
Expand Down
Loading

0 comments on commit 556d3d2

Please sign in to comment.